From 5c9d9d1598d8af06b027bbcd6e9509bbf62cf292 Mon Sep 17 00:00:00 2001 From: Man Date: Tue, 17 Sep 2024 13:13:15 -0700 Subject: [PATCH] Update Patched VS Code version to 1.93.1 with patches applied --- patched-vscode/ThirdPartyNotices.txt | 77 +- patched-vscode/build/.cachesalt | 2 +- patched-vscode/build/.moduleignore | 5 +- patched-vscode/build/.webignore | 8 +- .../build/azure-pipelines/cli/cli-compile.yml | 8 +- .../build/azure-pipelines/common/publish.js | 6 - .../build/azure-pipelines/common/publish.ts | 6 - .../darwin/helper-plugin-entitlements.plist | 2 - .../darwin/product-build-darwin-test.yml | 130 +- .../darwin/product-build-darwin.yml | 17 + .../product-build-linux-legacy-server.yml | 46 +- .../linux/product-build-linux-test.yml | 146 +- .../linux/product-build-linux.yml | 46 +- .../build/azure-pipelines/linux/setup-env.sh | 30 +- .../linux/verify-glibc-requirements.sh | 12 +- .../build/azure-pipelines/product-build.yml | 249 +- .../build/azure-pipelines/product-compile.yml | 18 +- .../build/azure-pipelines/sdl-scan.yml | 296 - .../azure-pipelines/upload-configuration.js | 112 - .../azure-pipelines/upload-nlsmetadata.js | 30 +- .../azure-pipelines/upload-nlsmetadata.ts | 138 +- .../azure-pipelines/web/product-build-web.yml | 13 +- .../win32/product-build-win32-test.yml | 131 +- .../win32/product-build-win32.yml | 10 +- .../azure-pipelines/win32/sdl-scan-win32.yml | 162 + patched-vscode/build/buildfile.js | 120 + patched-vscode/build/checksums/electron.txt | 150 +- patched-vscode/build/checksums/nodejs.txt | 14 +- .../build/darwin/create-universal-app.js | 34 +- .../build/darwin/create-universal-app.ts | 36 +- patched-vscode/build/filters.js | 6 +- patched-vscode/build/gulpfile.cli.js | 3 - patched-vscode/build/gulpfile.compile.js | 12 +- patched-vscode/build/gulpfile.editor.js | 20 +- patched-vscode/build/gulpfile.extensions.js | 6 +- patched-vscode/build/gulpfile.js | 4 + patched-vscode/build/gulpfile.reh.js | 167 +- patched-vscode/build/gulpfile.scan.js | 8 +- patched-vscode/build/gulpfile.vscode.js | 187 +- patched-vscode/build/gulpfile.vscode.linux.js | 52 +- patched-vscode/build/gulpfile.vscode.web.js | 61 +- patched-vscode/build/lib/asar.js | 13 +- patched-vscode/build/lib/asar.ts | 14 +- patched-vscode/build/lib/bundle.js | 112 +- patched-vscode/build/lib/bundle.ts | 115 +- patched-vscode/build/lib/compilation.js | 49 +- patched-vscode/build/lib/compilation.ts | 61 +- patched-vscode/build/lib/date.js | 32 + patched-vscode/build/lib/date.ts | 33 + patched-vscode/build/lib/electron.js | 2 +- patched-vscode/build/lib/electron.ts | 2 +- patched-vscode/build/lib/esm.js | 41 + patched-vscode/build/lib/esm.ts | 40 + patched-vscode/build/lib/extensions.js | 5 +- patched-vscode/build/lib/extensions.ts | 5 +- patched-vscode/build/lib/fetch.js | 2 +- patched-vscode/build/lib/fetch.ts | 2 +- patched-vscode/build/lib/i18n.js | 172 +- patched-vscode/build/lib/i18n.resources.json | 8 + patched-vscode/build/lib/i18n.ts | 181 +- patched-vscode/build/lib/inlineMeta.js | 48 + patched-vscode/build/lib/inlineMeta.ts | 60 + patched-vscode/build/lib/layersChecker.js | 57 +- patched-vscode/build/lib/layersChecker.ts | 63 +- patched-vscode/build/lib/mangle/index.js | 80 +- patched-vscode/build/lib/mangle/index.ts | 89 +- patched-vscode/build/lib/nls.js | 146 +- patched-vscode/build/lib/nls.ts | 204 +- patched-vscode/build/lib/optimize.js | 147 +- patched-vscode/build/lib/optimize.ts | 178 +- patched-vscode/build/lib/standalone.js | 7 +- patched-vscode/build/lib/standalone.ts | 7 +- .../lib/stylelint/vscode-known-variables.json | 57 +- patched-vscode/build/lib/treeshaking.js | 16 +- patched-vscode/build/lib/treeshaking.ts | 18 +- patched-vscode/build/lib/tsb/transpiler.js | 4 +- patched-vscode/build/lib/tsb/transpiler.ts | 4 +- .../build/linux/debian/dep-lists.js | 9 +- .../build/linux/debian/dep-lists.ts | 9 +- .../build/linux/dependencies-generator.js | 6 +- .../build/linux/dependencies-generator.ts | 6 +- patched-vscode/build/linux/rpm/dep-lists.js | 17 +- patched-vscode/build/linux/rpm/dep-lists.ts | 17 +- .../build/monaco/monaco.d.ts.recipe | 2 +- patched-vscode/build/npm/dirs.js | 4 - patched-vscode/build/npm/gyp/package.json | 2 +- patched-vscode/build/npm/gyp/yarn.lock | 505 +- patched-vscode/build/npm/postinstall.js | 17 +- patched-vscode/build/npm/preinstall.js | 17 +- patched-vscode/build/package.json | 10 +- patched-vscode/build/yarn.lock | 716 +- patched-vscode/cglicenses.json | 32 +- patched-vscode/cgmanifest.json | 47 +- patched-vscode/cli/Cargo.lock | 8 +- patched-vscode/cli/ThirdPartyNotices.txt | 391 +- patched-vscode/cli/src/async_pipe.rs | 2 +- patched-vscode/cli/src/auth.rs | 2 +- patched-vscode/cli/src/commands/args.rs | 8 +- patched-vscode/cli/src/commands/serve_web.rs | 75 +- patched-vscode/cli/src/commands/tunnels.rs | 94 +- patched-vscode/cli/src/constants.rs | 2 +- .../cli/src/tunnels/control_server.rs | 9 +- patched-vscode/cli/src/tunnels/dev_tunnels.rs | 10 +- .../cli/src/tunnels/local_forwarding.rs | 62 +- .../cli/src/tunnels/port_forwarder.rs | 9 +- patched-vscode/cli/src/tunnels/protocol.rs | 33 +- patched-vscode/cli/src/update_service.rs | 17 +- patched-vscode/cli/src/util/errors.rs | 2 + patched-vscode/cli/src/util/prereqs.rs | 14 +- patched-vscode/cli/src/util/tar.rs | 21 +- patched-vscode/cli/src/util/zipper.rs | 9 +- .../configuration-editing/package.json | 4 +- .../devContainer.codespaces.schema.json | 5 + patched-vscode/extensions/cpp/package.json | 4 + .../extensions/csharp/cgmanifest.json | 2 +- .../csharp/syntaxes/csharp.tmLanguage.json | 5 +- .../client/src/browser/cssClientMain.ts | 11 +- .../src/dropOrPaste/dropOrPasteResource.ts | 152 + .../client/src/dropOrPaste/shared.ts | 42 + .../client/src/dropOrPaste/uriList.ts | 38 + .../client/src/node/cssClientMain.ts | 13 +- .../client/tsconfig.json | 8 +- .../css-language-features/package.json | 5 +- .../schemas/package.schema.json | 1 - .../css-language-features/server/package.json | 4 +- .../css-language-features/server/yarn.lock | 56 +- .../css-language-features/yarn.lock | 38 +- .../extension-editing/src/extensionLinter.ts | 3 +- .../extensions/fsharp/cgmanifest.json | 2 +- .../fsharp/syntaxes/fsharp.tmLanguage.json | 15 +- patched-vscode/extensions/git/package.json | 87 +- .../extensions/git/package.nls.json | 52 +- patched-vscode/extensions/git/src/api/api1.ts | 5 + .../extensions/git/src/api/git.d.ts | 4 + patched-vscode/extensions/git/src/commands.ts | 229 +- .../extensions/git/src/decorationProvider.ts | 6 +- patched-vscode/extensions/git/src/encoding.ts | 27 +- patched-vscode/extensions/git/src/git.ts | 83 +- .../extensions/git/src/historyProvider.ts | 202 +- patched-vscode/extensions/git/src/main.ts | 21 +- patched-vscode/extensions/git/src/model.ts | 217 +- .../extensions/git/src/operation.ts | 10 +- .../extensions/git/src/protocolHandler.ts | 14 +- .../extensions/git/src/repository.ts | 48 +- patched-vscode/extensions/git/src/terminal.ts | 44 +- patched-vscode/extensions/git/tsconfig.json | 1 + patched-vscode/extensions/git/yarn.lock | 8 +- .../github-authentication/package.json | 12 +- .../github-authentication/src/flows.ts | 4 + .../github-authentication/src/github.ts | 46 +- .../github-authentication/src/githubServer.ts | 2 +- patched-vscode/extensions/go/cgmanifest.json | 4 +- .../extensions/go/syntaxes/go.tmLanguage.json | 167 +- patched-vscode/extensions/grunt/src/main.ts | 4 +- patched-vscode/extensions/gulp/src/main.ts | 4 +- .../client/src/browser/htmlClientMain.ts | 9 +- .../client/tsconfig.json | 5 +- .../html-language-features/package.json | 2 +- .../schemas/package.schema.json | 1 - .../server/package.json | 8 +- .../html-language-features/server/yarn.lock | 66 +- .../html-language-features/yarn.lock | 38 +- patched-vscode/extensions/ipynb/package.json | 20 +- .../extensions/ipynb/package.nls.json | 1 + .../extensions/ipynb/src/ipynbMain.ts | 7 - .../extensions/ipynb/src/serializers.ts | 5 +- patched-vscode/extensions/jake/src/main.ts | 4 +- .../snippets/javascript.code-snippets | 146 +- .../client/src/browser/jsonClientMain.ts | 8 +- .../client/src/jsonClient.ts | 87 +- .../client/tsconfig.json | 5 +- .../json-language-features/package.json | 4 +- .../server/package.json | 8 +- .../json-language-features/server/yarn.lock | 74 +- .../json-language-features/yarn.lock | 46 +- .../extensions/julia/cgmanifest.json | 4 +- .../julia/syntaxes/julia.tmLanguage.json | 4 +- .../extensions/latex/cgmanifest.json | 4 +- .../latex/syntaxes/LaTeX.tmLanguage.json | 147 +- .../latex/syntaxes/TeX.tmLanguage.json | 22 +- .../extensions/less/cgmanifest.json | 2 +- .../less/syntaxes/less.tmLanguage.json | 759 +- .../markdown-basics/cgmanifest.json | 2 +- .../language-configuration.json | 4 + .../syntaxes/markdown.tmLanguage.json | 32 +- .../markdown-language-features/.vscodeignore | 9 +- .../extension-browser.webpack.config.js | 18 +- .../extension.webpack.config.js | 14 +- .../media/markdown.css | 2 +- .../markdown-language-features/package.json | 14 +- .../package.nls.json | 2 +- .../schemas/package.schema.json | 1 - .../server/.npmignore | 12 - .../server/.vscode/launch.json | 16 - .../server/.vscode/settings.json | 2 - .../server/.vscode/tasks.json | 27 - .../server/CHANGELOG.md | 7 - .../server/README.md | 131 - .../server/build/pipeline.yml | 35 - .../extension-browser.webpack.config.js | 24 - .../server/package.json | 34 - .../server/src/browser/main.ts | 14 - .../server/src/browser/workerMain.ts | 38 - .../server/src/config.ts | 32 - .../server/src/configuration.ts | 74 - .../src/languageFeatures/diagnostics.ts | 114 - .../server/src/logging.ts | 81 - .../server/src/node/main.ts | 19 - .../server/src/node/workerMain.ts | 23 - .../server/src/protocol.ts | 33 - .../server/src/server.ts | 399 - .../server/src/util/dispose.ts | 54 - .../server/src/util/file.ts | 16 - .../server/src/util/limiter.ts | 67 - .../server/src/util/resourceMap.ts | 69 - .../server/src/workspace.ts | 433 - .../server/tsconfig.json | 14 - .../server/yarn.lock | 176 - .../src/extension.browser.ts | 2 +- .../src/extension.ts | 13 +- .../languageFeatures/updateLinksOnPaste.ts | 14 +- .../src/markdownEngine.ts | 17 +- .../markdown-language-features/yarn.lock | 155 +- .../markdown-math/notebook/katex.ts | 3 +- .../extensions/markdown-math/package.json | 12 +- .../extensions/markdown-math/src/extension.ts | 8 +- .../syntaxes/md-math-block.tmLanguage.json | 2 +- .../syntaxes/md-math-fence.tmLanguage.json | 28 + .../extensions/markdown-math/yarn.lock | 8 +- .../extension-browser.webpack.config.js | 3 +- .../microsoft-authentication/package.json | 14 +- .../microsoft-authentication/package.nls.json | 1 + .../microsoft-authentication/src/AADHelper.ts | 88 +- .../src/UriEventHandler.ts | 9 +- .../src/browser/authProvider.ts | 29 + .../src/common/async.ts | 477 +- .../src/common/cachePlugin.ts | 55 + .../src/common/experimentation.ts | 26 + .../src/common/loggerOptions.ts | 61 + .../src/common/loopbackClientAndOpener.ts | 51 + .../src/common/publicClientCache.ts | 21 + .../src/common/scopeData.ts | 78 + .../src/common/telemetryReporter.ts | 128 + .../test/loopbackClientAndOpener.test.ts | 83 + .../src/common/test/scopeData.test.ts | 54 + .../src/cryptoUtils.ts | 1 - .../microsoft-authentication/src/extension.ts | 228 +- .../src/extensionV1.ts | 178 + .../src/extensionV2.ts | 97 + .../src/node/authProvider.ts | 242 + .../src/node/authServer.ts | 15 +- .../src/node/loopbackTemplate.ts | 140 + .../src/node/publicClientCache.ts | 252 + .../microsoft-authentication/yarn.lock | 141 +- .../notebook-renderers/src/index.ts | 6 +- .../notebook-renderers/src/textHelper.ts | 10 + .../extensions/notebook-renderers/yarn.lock | 6 +- .../src/features/packageJSONContribution.ts | 8 +- patched-vscode/extensions/npm/yarn.lock | 36 +- patched-vscode/extensions/package.json | 4 +- .../sagemaker-extension/.vscodeignore | 12 - .../extensions/sagemaker-extension/README.md | 5 - .../extension-browser.webpack.config.js | 17 - .../extension.webpack.config.js | 20 - .../sagemaker-extension/package.json | 46 - .../sagemaker-extension/src/constant.ts | 72 - .../sagemaker-extension/src/extension.ts | 137 - .../sagemaker-extension/src/sessionWarning.ts | 44 - .../sagemaker-extension/tsconfig.json | 10 - .../extensions/sagemaker-extension/yarn.lock | 4 - .../sagemaker-idle-extension/.vscodeignore | 12 - .../sagemaker-idle-extension/CONTRIBUTING.md | 40 - .../sagemaker-idle-extension/README.md | 3 - .../extension-browser.webpack.config.js | 17 - .../extension.webpack.config.js | 20 - .../sagemaker-idle-extension/package.json | 43 - .../sagemaker-idle-extension/src/extension.ts | 116 - .../sagemaker-idle-extension/tsconfig.json | 10 - .../sagemaker-idle-extension/yarn.lock | 4 - .../.vscodeignore | 12 - .../README.md | 2 - .../extension-browser.webpack.config.js | 17 - .../extension.webpack.config.js | 20 - .../package.json | 43 - .../src/extension.ts | 103 - .../tsconfig.json | 10 - .../yarn.lock | 4 - .../extensions/shellscript/cgmanifest.json | 2 +- .../extensions/shellscript/package.json | 1 + .../syntaxes/shell-unix-bash.tmLanguage.json | 4 +- patched-vscode/extensions/sql/package.json | 3 +- .../extensions/swift/cgmanifest.json | 2 +- .../swift/syntaxes/swift.tmLanguage.json | 8 +- .../theme-defaults/themes/dark_vs.json | 1 + .../extensions/theme-seti/cgmanifest.json | 2 +- .../extensions/theme-seti/icons/seti.woff | Bin 37200 -> 37200 bytes .../theme-seti/icons/vs-seti-icon-theme.json | 34 +- .../tunnel-forwarding/src/extension.ts | 6 +- .../snippets/typescript.code-snippets | 13 +- .../typescript-language-features/package.json | 189 +- .../package.nls.json | 19 +- .../schemas/package.schema.json | 1 - .../src/configuration/configuration.ts | 10 +- .../src/extension.browser.ts | 18 +- .../src/filesystems/ata.ts | 34 + .../src/filesystems/autoInstallerFs.ts | 142 +- .../src/filesystems/memFs.ts | 19 +- .../src/languageFeatures/completions.ts | 104 +- .../src/languageFeatures/copyPaste.ts | 19 +- .../src/languageFeatures/diagnostics.ts | 76 +- .../fileConfigurationManager.ts | 29 +- .../src/languageFeatures/fixAll.ts | 4 +- .../src/languageFeatures/refactor.ts | 14 +- .../src/languageFeatures/rename.ts | 4 - .../src/languageFeatures/semanticTokens.ts | 6 +- .../src/languageFeatures/smartSelect.ts | 9 +- .../src/languageFeatures/tagClosing.ts | 11 +- .../languageFeatures/updatePathsOnRename.ts | 5 +- .../languageFeatures/util/textRendering.ts | 40 +- .../src/languageProvider.ts | 8 +- .../src/test/unit/textRendering.test.ts | 26 +- .../src/tsServer/api.ts | 10 +- .../src/tsServer/bufferSyncSupport.ts | 100 +- .../src/tsServer/protocol/protocol.const.ts | 2 + .../src/tsServer/protocol/protocol.d.ts | 65 - .../src/tsServer/server.ts | 4 + .../src/tsServer/serverProcess.browser.ts | 8 +- .../src/tsServer/serverProcess.electron.ts | 3 +- .../src/tsServer/spawner.ts | 11 +- .../src/tsconfig.ts | 4 + .../src/typeConverters.ts | 14 +- .../src/typeScriptServiceClientHost.ts | 10 +- .../src/typescriptServiceClient.ts | 136 +- .../src/ui/activeJsTsEditorTracker.ts | 1 + .../src/ui/intellisenseStatus.ts | 10 +- .../src/ui/managedFileContext.ts | 2 +- .../src/utils/async.ts | 91 + .../src/utils/hash.ts | 58 + .../web/src/fileWatcherManager.ts | 4 +- .../web/src/serverHost.ts | 14 +- .../src/typingsInstaller/typingsInstaller.ts | 4 +- .../extensions/vscode-api-tests/package.json | 32 +- .../src/singlefolder-tests/chat.test.ts | 33 +- .../src/singlefolder-tests/lm.test.ts | 153 + .../singlefolder-tests/notebook.api.test.ts | 26 + .../src/singlefolder-tests/proxy.test.ts | 151 + .../terminal.shellIntegration.test.ts | 3 +- .../src/singlefolder-tests/workspace.test.ts | 1 + .../extensions/vscode-api-tests/yarn.lock | 145 + .../colorize-results/issue-1550_yaml.json | 2 +- .../colorize-results/issue-4008_yaml.json | 2 +- .../colorize-results/issue-6303_yaml.json | 2 +- .../test-cssvariables_less.json | 30 +- .../test/colorize-results/test_less.json | 18 +- .../test/colorize-results/test_yaml.json | 2 +- .../vscode-test-resolver/src/extension.ts | 8 +- .../extensions/yaml/cgmanifest.json | 2 +- .../yaml/syntaxes/yaml.tmLanguage.json | 2 +- patched-vscode/extensions/yarn.lock | 312 +- patched-vscode/migrate.mjs | 364 + patched-vscode/package.json | 51 +- patched-vscode/product.json | 64 +- patched-vscode/remote/.yarnrc | 4 +- patched-vscode/remote/package.json | 27 +- patched-vscode/remote/web/package.json | 18 +- patched-vscode/remote/web/yarn.lock | 93 +- patched-vscode/remote/yarn.lock | 221 +- .../resources/linux/debian/postinst.template | 42 +- .../resources/linux/debian/postrm.template | 19 + .../resources/linux/debian/templates.template | 6 + .../resources/linux/snap/electron-launch | 2 +- .../resources/linux/snap/snapcraft.yaml | 7 +- .../bin/helpers/check-requirements-linux.sh | 10 +- patched-vscode/resources/server/favicon.ico | Bin 1150 -> 34494 bytes patched-vscode/scripts/code-server.js | 1 - patched-vscode/scripts/code-web.js | 4 + patched-vscode/scripts/code.bat | 9 +- patched-vscode/scripts/code.sh | 7 +- patched-vscode/scripts/playground-server.ts | 216 +- patched-vscode/scripts/test-esm.bat | 31 + patched-vscode/scripts/test-esm.sh | 43 + .../scripts/test-integration-esm.bat | 119 + .../scripts/test-integration-esm.sh | 136 + patched-vscode/scripts/xterm-update.js | 1 + patched-vscode/scripts/xterm-update.ps1 | 3 +- patched-vscode/src/bootstrap-amd.js | 206 +- .../bootstrap-cli.js} | 20 +- patched-vscode/src/bootstrap-fork.js | 25 +- patched-vscode/src/bootstrap-import.js | 78 + patched-vscode/src/bootstrap-meta.js | 41 + patched-vscode/src/bootstrap-node.js | 140 +- ...esktop.main.nls.js => bootstrap-server.js} | 6 +- patched-vscode/src/bootstrap-window.js | 175 +- patched-vscode/src/bootstrap.js | 258 - patched-vscode/src/buildfile.js | 84 - patched-vscode/src/cli.js | 49 +- patched-vscode/src/main.js | 195 +- patched-vscode/src/server-cli.js | 46 +- patched-vscode/src/server-main.js | 77 +- patched-vscode/src/tsconfig.json | 16 +- patched-vscode/src/tsconfig.monaco.json | 5 +- patched-vscode/src/tsconfig.tsec.json | 1 + patched-vscode/src/tsec.exemptions.json | 3 + .../src/typings/vscode-globals-modules.d.ts | 4 +- .../src/typings/vscode-globals-nls.d.ts | 40 + .../src/typings/vscode-globals-product.d.ts | 13 +- .../vscode-globals-ttp.d.ts} | 13 +- patched-vscode/src/vs/amdX.ts | 99 +- patched-vscode/src/vs/base/browser/browser.ts | 6 + .../vs/base/browser/defaultWorkerFactory.ts | 144 +- patched-vscode/src/vs/base/browser/dnd.ts | 2 +- patched-vscode/src/vs/base/browser/dom.ts | 159 +- .../src/vs/base/browser/domObservable.ts | 17 + .../src/vs/base/browser/markdownRenderer.ts | 132 +- .../src/vs/base/browser/trustedTypes.ts | 3 +- .../browser/ui/actionbar/actionViewItems.ts | 21 +- .../vs/base/browser/ui/actionbar/actionbar.ts | 11 +- .../src/vs/base/browser/ui/button/button.ts | 39 +- .../browser/ui/centered/centeredViewLayout.ts | 6 +- .../browser/ui/contextview/contextview.ts | 4 +- .../src/vs/base/browser/ui/dialog/dialog.css | 1 + .../src/vs/base/browser/ui/dialog/dialog.ts | 2 +- .../vs/base/browser/ui/dropdown/dropdown.ts | 6 +- .../ui/dropdown/dropdownActionViewItem.ts | 2 +- .../src/vs/base/browser/ui/grid/gridview.ts | 4 +- .../ui/highlightedlabel/highlightedLabel.ts | 6 +- .../src/vs/base/browser/ui/hover/hover.ts | 62 +- .../vs/base/browser/ui/hover/hoverDelegate.ts | 11 +- .../base/browser/ui/hover/hoverDelegate2.ts | 4 +- .../vs/base/browser/ui/hover/hoverWidget.css | 9 + .../vs/base/browser/ui/hover/hoverWidget.ts | 6 + .../vs/base/browser/ui/iconLabel/iconLabel.ts | 28 +- .../browser/ui/iconLabel/simpleIconLabel.ts | 6 +- .../vs/base/browser/ui/inputbox/inputBox.ts | 6 +- .../ui/keybindingLabel/keybindingLabel.ts | 6 +- .../src/vs/base/browser/ui/list/listView.ts | 18 +- .../src/vs/base/browser/ui/list/rowCache.ts | 10 +- .../src/vs/base/browser/ui/radio/radio.css | 69 + .../src/vs/base/browser/ui/radio/radio.ts | 114 + .../src/vs/base/browser/ui/sash/sash.ts | 2 +- .../browser/ui/selectBox/selectBoxCustom.ts | 27 +- .../vs/base/browser/ui/splitview/paneview.ts | 2 +- .../vs/base/browser/ui/splitview/splitview.ts | 2 +- .../vs/base/browser/ui/table/tableWidget.ts | 13 +- .../src/vs/base/browser/ui/toggle/toggle.css | 6 + .../src/vs/base/browser/ui/toggle/toggle.ts | 6 +- .../src/vs/base/browser/ui/toolbar/toolbar.ts | 16 +- .../vs/base/browser/ui/tree/abstractTree.ts | 14 +- .../vs/base/browser/ui/tree/asyncDataTree.ts | 4 - patched-vscode/src/vs/base/common/amd.ts | 2 + patched-vscode/src/vs/base/common/arrays.ts | 15 + .../src/vs/base/common/collections.ts | 58 + patched-vscode/src/vs/base/common/date.ts | 21 + patched-vscode/src/vs/base/common/equals.ts | 31 +- patched-vscode/src/vs/base/common/errors.ts | 13 + patched-vscode/src/vs/base/common/event.ts | 64 +- patched-vscode/src/vs/base/common/history.ts | 14 +- .../src/vs/base/common/hotReload.ts | 16 +- .../src/vs/base/common/hotReloadHelpers.ts | 30 + patched-vscode/src/vs/base/common/iterator.ts | 12 +- patched-vscode/src/vs/base/common/json.ts | 34 - .../src/vs/base/common/jsonSchema.ts | 147 + .../common/{stripComments.d.ts => jsonc.d.ts} | 10 + .../common/{stripComments.js => jsonc.js} | 38 +- .../src/vs/base/common/marked/cgmanifest.json | 4 +- .../src/vs/base/common/marked/marked.d.ts | 1244 +- .../src/vs/base/common/marked/marked.js | 5562 +- patched-vscode/src/vs/base/common/network.ts | 44 +- patched-vscode/src/vs/base/common/numbers.ts | 27 + .../src/vs/base/common/observable.ts | 3 + .../vs/base/common/observableInternal/api.ts | 31 + .../base/common/observableInternal/autorun.ts | 4 +- .../vs/base/common/observableInternal/base.ts | 37 +- .../common/observableInternal/debugName.ts | 8 +- .../base/common/observableInternal/derived.ts | 33 +- .../observableInternal/lazyObservableValue.ts | 146 + .../base/common/observableInternal/logging.ts | 4 +- .../base/common/observableInternal/promise.ts | 8 +- .../base/common/observableInternal/utils.ts | 125 +- patched-vscode/src/vs/base/common/path.ts | 44 +- .../src/vs/base/common/performance.js | 15 +- patched-vscode/src/vs/base/common/platform.ts | 44 +- .../src/vs/base/common/prefixTree.ts | 5 + patched-vscode/src/vs/base/common/process.ts | 2 +- .../src/vs/base/common/processes.ts | 9 +- patched-vscode/src/vs/base/common/product.ts | 12 +- .../src/vs/base/common/semver/semver.d.ts | 7 +- .../src/vs/base/common/semver/semver.js | 45 + patched-vscode/src/vs/base/common/types.ts | 10 + patched-vscode/src/vs/base/common/uri.ts | 4 + .../src/vs/base/common/worker/simpleWorker.ts | 339 +- .../common/worker/simpleWorkerBootstrap.ts | 42 + patched-vscode/src/vs/base/node/extpath.ts | 2 +- .../src/vs/base/node/languagePacks.d.ts | 25 - .../src/vs/base/node/languagePacks.js | 264 - patched-vscode/src/vs/base/node/nls.d.ts | 38 + patched-vscode/src/vs/base/node/nls.js | 274 + .../src/vs/base/node/osDisplayProtocolInfo.ts | 5 +- .../src/vs/base/node/osReleaseInfo.ts | 7 +- patched-vscode/src/vs/base/node/pfs.ts | 94 +- patched-vscode/src/vs/base/node/processes.ts | 6 +- patched-vscode/src/vs/base/node/unc.js | 17 +- patched-vscode/src/vs/base/node/zip.ts | 6 +- .../src/vs/base/parts/ipc/common/ipc.net.ts | 12 +- .../src/vs/base/parts/ipc/common/ipc.ts | 12 +- .../base/parts/ipc/electron-main/ipcMain.ts | 20 +- .../src/vs/base/parts/ipc/node/ipc.net.ts | 14 + .../parts/ipc/test/browser/ipc.mp.test.ts | 2 +- .../vs/base/parts/ipc/test/common/ipc.test.ts | 2 +- .../ipc/test/electron-sandbox/ipc.mp.test.ts | 2 +- .../ipc/test/node/ipc.cp.integrationTest.ts | 2 +- .../base/parts/ipc/test/node/ipc.net.test.ts | 27 +- .../vs/base/parts/request/browser/request.ts | 122 +- .../vs/base/parts/request/common/request.ts | 9 +- .../request/test/browser/request.test.ts | 117 + .../parts/sandbox/common/electronTypes.ts | 18 - .../base/parts/sandbox/common/sandboxTypes.ts | 22 + .../sandbox/electron-sandbox/electronTypes.ts | 16 + .../parts/sandbox/electron-sandbox/globals.ts | 3 +- .../parts/sandbox/electron-sandbox/preload.js | 31 +- .../test/electron-sandbox/globals.test.ts | 9 +- .../src/vs/base/parts/storage/node/storage.ts | 13 +- .../test/node/storage.integrationTest.ts | 11 +- .../vs/base/test/browser/actionbar.test.ts | 2 +- .../src/vs/base/test/browser/browser.test.ts | 2 +- .../vs/base/test/browser/comparers.test.ts | 2 +- .../src/vs/base/test/browser/dom.test.ts | 37 +- .../browser/formattedTextRenderer.test.ts | 2 +- .../src/vs/base/test/browser/hash.test.ts | 2 +- .../test/browser/highlightedLabel.test.ts | 2 +- .../vs/base/test/browser/iconLabels.test.ts | 2 +- .../vs/base/test/browser/indexedDB.test.ts | 2 +- .../test/browser/markdownRenderer.test.ts | 287 +- .../vs/base/test/browser/progressBar.test.ts | 4 +- .../ui/contextview/contextview.test.ts | 2 +- .../vs/base/test/browser/ui/grid/grid.test.ts | 2 +- .../test/browser/ui/grid/gridview.test.ts | 2 +- .../src/vs/base/test/browser/ui/grid/util.ts | 2 +- .../test/browser/ui/list/listView.test.ts | 2 +- .../test/browser/ui/list/listWidget.test.ts | 2 +- .../test/browser/ui/list/rangeMap.test.ts | 2 +- .../base/test/browser/ui/menu/menubar.test.ts | 2 +- .../ui/scrollbar/scrollableElement.test.ts | 2 +- .../ui/scrollbar/scrollbarState.test.ts | 2 +- .../browser/ui/splitview/splitview.test.ts | 2 +- .../browser/ui/tree/asyncDataTree.test.ts | 2 +- .../ui/tree/compressedObjectTreeModel.test.ts | 2 +- .../test/browser/ui/tree/dataTree.test.ts | 2 +- .../browser/ui/tree/indexTreeModel.test.ts | 2 +- .../test/browser/ui/tree/objectTree.test.ts | 2 +- .../browser/ui/tree/objectTreeModel.test.ts | 2 +- .../src/vs/base/test/common/arrays.test.ts | 2 +- .../vs/base/test/common/arraysFind.test.ts | 2 +- .../src/vs/base/test/common/assert.test.ts | 2 +- .../src/vs/base/test/common/async.test.ts | 2 +- .../src/vs/base/test/common/buffer.test.ts | 2 +- .../src/vs/base/test/common/cache.test.ts | 2 +- .../vs/base/test/common/cancellation.test.ts | 2 +- .../src/vs/base/test/common/charCode.test.ts | 2 +- .../vs/base/test/common/collections.test.ts | 68 +- .../src/vs/base/test/common/color.test.ts | 2 +- .../src/vs/base/test/common/console.test.ts | 2 +- .../src/vs/base/test/common/date.test.ts | 19 +- .../vs/base/test/common/decorators.test.ts | 2 +- .../src/vs/base/test/common/diff/diff.test.ts | 2 +- .../src/vs/base/test/common/errors.test.ts | 2 +- .../src/vs/base/test/common/event.test.ts | 2 +- .../src/vs/base/test/common/extpath.test.ts | 2 +- .../src/vs/base/test/common/filters.test.ts | 2 +- .../vs/base/test/common/fuzzyScorer.test.ts | 2 +- .../src/vs/base/test/common/glob.test.ts | 2 +- .../src/vs/base/test/common/history.test.ts | 2 +- .../vs/base/test/common/iconLabels.test.ts | 2 +- .../src/vs/base/test/common/iterator.test.ts | 2 +- .../src/vs/base/test/common/json.test.ts | 2 +- .../src/vs/base/test/common/jsonEdit.test.ts | 2 +- .../vs/base/test/common/jsonFormatter.test.ts | 2 +- ...tripComments.test.ts => jsonParse.test.ts} | 72 +- .../vs/base/test/common/jsonSchema.test.ts | 574 + .../src/vs/base/test/common/keyCodes.test.ts | 2 +- .../vs/base/test/common/keybindings.test.ts | 2 +- .../src/vs/base/test/common/labels.test.ts | 2 +- .../src/vs/base/test/common/lazy.test.ts | 2 +- .../src/vs/base/test/common/lifecycle.test.ts | 2 +- .../vs/base/test/common/linkedList.test.ts | 2 +- .../vs/base/test/common/linkedText.test.ts | 2 +- .../src/vs/base/test/common/map.test.ts | 2 +- .../base/test/common/markdownString.test.ts | 2 +- .../vs/base/test/common/marshalling.test.ts | 2 +- .../src/vs/base/test/common/mime.test.ts | 2 +- .../src/vs/base/test/common/network.test.ts | 2 +- .../vs/base/test/common/normalization.test.ts | 2 +- .../src/vs/base/test/common/numbers.test.ts | 27 + .../src/vs/base/test/common/objects.test.ts | 2 +- .../vs/base/test/common/observable.test.ts | 58 +- .../src/vs/base/test/common/paging.test.ts | 2 +- .../src/vs/base/test/common/path.test.ts | 2 +- .../vs/base/test/common/prefixTree.test.ts | 2 +- .../src/vs/base/test/common/processes.test.ts | 2 +- .../vs/base/test/common/resourceTree.test.ts | 2 +- .../src/vs/base/test/common/resources.test.ts | 2 +- .../vs/base/test/common/scrollable.test.ts | 2 +- .../src/vs/base/test/common/skipList.test.ts | 2 +- .../src/vs/base/test/common/stream.test.ts | 2 +- .../src/vs/base/test/common/strings.test.ts | 2 +- .../test/common/ternarySearchtree.test.ts | 2 +- .../src/vs/base/test/common/tfIdf.test.ts | 2 +- .../src/vs/base/test/common/types.test.ts | 2 +- .../src/vs/base/test/common/uri.test.ts | 2 +- .../src/vs/base/test/common/uuid.test.ts | 2 +- .../src/vs/base/test/node/crypto.test.ts | 3 +- .../src/vs/base/test/node/css.build.test.ts | 2 +- .../src/vs/base/test/node/extpath.test.ts | 5 +- .../src/vs/base/test/node/id.test.ts | 2 +- .../src/vs/base/test/node/nodeStreams.test.ts | 2 +- .../src/vs/base/test/node/pfs/pfs.test.ts | 22 +- .../src/vs/base/test/node/port.test.ts | 2 +- .../src/vs/base/test/node/powershell.test.ts | 2 +- .../processes/processes.integrationTest.ts | 2 +- .../src/vs/base/test/node/snapshot.test.ts | 7 +- .../src/vs/base/test/node/uri.perf.test.ts | 2 +- .../src/vs/base/test/node/zip/zip.test.ts | 5 +- .../src/vs/base/worker/workerMain.ts | 55 +- .../browser/workbench/workbench-dev.esm.html | 67 + .../code/browser/workbench/workbench-dev.html | 2 +- .../code/browser/workbench/workbench.esm.html | 48 + .../vs/code/browser/workbench/workbench.html | 27 +- .../vs/code/browser/workbench/workbench.ts | 3 +- .../src/vs/code/electron-main/app.ts | 125 +- .../src/vs/code/electron-main/main.ts | 6 +- .../processExplorer-dev.esm.html | 42 + .../processExplorer/processExplorer-dev.html | 4 +- .../processExplorer/processExplorer.esm.html | 42 + .../processExplorer/processExplorer.js | 11 +- .../workbench/workbench-dev.esm.html | 75 + .../workbench/workbench-dev.html | 3 +- .../workbench/workbench.esm.html | 72 + .../electron-sandbox/workbench/workbench.js | 33 +- patched-vscode/src/vs/code/node/cli.ts | 24 +- .../src/vs/code/node/cliProcessMain.ts | 4 +- .../sharedProcess/contrib/codeCacheCleaner.ts | 3 +- .../contrib/languagePackCachedDataCleaner.ts | 5 +- .../node/sharedProcess/sharedProcessMain.ts | 4 +- .../editor/browser/config/charWidthReader.ts | 2 +- .../browser/controller/textAreaHandler.ts | 2 +- .../src/vs/editor/browser/coreCommands.ts | 7 +- .../vs/editor/browser/observableCodeEditor.ts | 284 + .../vs/editor/browser/observableUtilities.ts | 71 - .../services/abstractCodeEditorService.ts | 2 +- .../browser/services/editorWorkerService.ts | 374 +- .../services/hoverService/hoverService.ts | 58 +- .../services/hoverService/hoverWidget.ts | 2 +- .../hoverService/updatableHoverWidget.ts | 17 +- .../treeSitter/treeSitterParserService.ts | 471 + patched-vscode/src/vs/editor/browser/view.ts | 2 +- .../browser/view/domLineBreaksComputer.ts | 2 +- .../src/vs/editor/browser/view/viewLayer.ts | 77 +- .../vs/editor/browser/view/viewOverlays.ts | 24 +- .../contentWidgets/contentWidgets.ts | 2 +- .../viewParts/glyphMargin/glyphMargin.ts | 2 +- .../browser/viewParts/lines/viewLines.ts | 33 +- .../browser/viewParts/minimap/minimap.ts | 6 +- .../overviewRuler/decorationsOverviewRuler.ts | 4 +- .../browser/viewParts/viewZones/viewZones.ts | 4 +- .../widget/codeEditor/codeEditorWidget.ts | 19 +- .../browser/widget/codeEditor/editor.css | 1 + .../components/diffEditorDecorations.ts | 22 +- .../components/diffEditorEditors.ts | 19 +- .../diffEditorViewZones.ts | 27 +- .../widget/diffEditor/diffEditorOptions.ts | 59 +- .../widget/diffEditor/diffEditorViewModel.ts | 4 +- .../widget/diffEditor/diffEditorWidget.ts | 77 +- .../diffEditor/diffProviderFactoryService.ts | 2 +- .../diffEditor/features/gutterFeature.ts | 2 +- .../features/hideUnchangedRegionsFeature.ts | 140 +- .../features/movedBlocksLinesFeature.ts | 4 +- .../features/overviewRulerFeature.ts | 2 +- .../diffEditor/registrations.contribution.ts | 4 +- .../browser/widget/diffEditor/style.css | 32 +- .../editor/browser/widget/diffEditor/utils.ts | 133 +- .../widget/diffEditor/utils/editorGutter.ts | 14 +- .../browser/widget/multiDiffEditor/colors.ts | 4 +- .../multiDiffEditor/diffEditorItemTemplate.ts | 72 +- .../browser/widget/multiDiffEditor/model.ts | 26 +- .../multiDiffEditorViewModel.ts | 68 +- .../multiDiffEditor/multiDiffEditorWidget.ts | 8 +- .../multiDiffEditorWidgetImpl.ts | 16 +- .../src/vs/editor/common/config/diffEditor.ts | 2 + .../config/editorConfigurationSchema.ts | 15 +- .../vs/editor/common/config/editorOptions.ts | 75 +- .../editor/common/core/editorColorRegistry.ts | 92 +- .../src/vs/editor/common/core/textEdit.ts | 9 + .../src/vs/editor/common/cursor/cursor.ts | 7 +- .../common/cursor/cursorTypeEditOperations.ts | 1030 + .../common/cursor/cursorTypeOperations.ts | 984 +- .../common/cursor/cursorWordOperations.ts | 11 +- .../algorithms/diffAlgorithm.ts | 12 + .../computeMovedLines.ts | 6 +- .../defaultLinesDiffComputer.ts | 29 +- .../linesSliceCharSequence.ts | 82 +- .../src/vs/editor/common/diff/rangeMapping.ts | 78 + .../editor/common/languageFeatureRegistry.ts | 48 +- .../src/vs/editor/common/languages.ts | 71 +- .../vs/editor/common/languages/autoIndent.ts | 27 +- .../languages/highlights/typescript.scm | 271 + .../languages/supports/richEditBrackets.ts | 3 +- patched-vscode/src/vs/editor/common/model.ts | 7 + .../bracketPairsImpl.ts | 18 +- .../pieceTreeTextBuffer/pieceTreeBase.ts | 21 + .../pieceTreeTextBuffer.ts | 4 + .../src/vs/editor/common/model/textModel.ts | 59 +- .../common/model/tokenizationTextModelPart.ts | 155 +- .../src/vs/editor/common/model/tokens.ts | 138 + .../editor/common/model/treeSitterTokens.ts | 100 + .../common/services/editorSimpleWorker.esm.ts | 9 + .../common/services/editorSimpleWorker.ts | 415 +- .../vs/editor/common/services/editorWorker.ts | 9 +- .../common/services/editorWorkerBootstrap.ts | 51 + .../common/services/editorWorkerHost.ts | 14 +- .../editor/common/services/getIconClasses.ts | 2 +- .../editor/common/services/languageService.ts | 51 +- .../vs/editor/common/services/modelService.ts | 14 +- .../services/semanticTokensProviderStyling.ts | 10 +- .../textModelSync/textModelSync.impl.ts | 427 + .../textModelSync/textModelSync.protocol.ts | 21 + .../services/treeSitterParserService.ts | 25 + .../common/standalone/standaloneEnums.ts | 125 +- .../src/vs/editor/common/standaloneStrings.ts | 19 +- .../src/vs/editor/common/textModelEvents.ts | 3 + .../vs/editor/common/tokenizationRegistry.ts | 22 +- .../editor/common/viewModel/viewModelImpl.ts | 22 +- .../browser/bracketMatching.ts | 2 +- .../test/browser/bracketMatching.test.ts | 2 +- .../browser/codeActionContributions.ts | 12 + .../browser/codeActionController.ts | 59 +- .../codeAction/browser/codeActionModel.ts | 33 +- .../codeAction/browser/lightBulbWidget.css | 21 +- .../codeAction/browser/lightBulbWidget.ts | 230 +- .../test/browser/codeAction.test.ts | 2 +- .../codeActionKeybindingResolver.test.ts | 3 +- .../test/browser/codeActionModel.test.ts | 2 +- .../contrib/codelens/browser/codeLensCache.ts | 11 +- .../colorPicker/browser/colorContributions.ts | 4 +- .../browser/colorHoverParticipant.ts | 46 +- .../colorPicker/browser/colorPickerWidget.ts | 13 +- .../browser/defaultDocumentColorProvider.ts | 20 +- .../browser/standaloneColorPickerWidget.ts | 39 +- .../test/browser/lineCommentCommand.test.ts | 2 +- .../contextmenu/browser/contextmenu.ts | 6 +- .../test/browser/cursorUndo.test.ts | 2 +- .../test/browser/outlineModel.test.ts | 2 +- .../browser/copyPasteController.ts | 162 +- .../browser/defaultProviders.ts | 14 +- .../browser/dropIntoEditorController.ts | 29 +- .../test/browser/editSort.test.ts | 2 +- .../test/browser/editorState.test.ts | 2 +- .../contrib/find/browser/findController.ts | 19 +- .../editor/contrib/find/browser/findModel.ts | 7 +- .../editor/contrib/find/browser/findWidget.ts | 6 +- .../contrib/find/test/browser/find.test.ts | 2 +- .../find/test/browser/findController.test.ts | 2 +- .../find/test/browser/findModel.test.ts | 2 +- .../find/test/browser/replacePattern.test.ts | 2 +- .../contrib/folding/browser/folding.css | 3 +- .../editor/contrib/folding/browser/folding.ts | 52 +- .../folding/browser/foldingDecorations.ts | 3 +- .../contrib/folding/browser/foldingModel.ts | 21 +- .../contrib/folding/browser/foldingRanges.ts | 10 +- .../folding/test/browser/foldingModel.test.ts | 6 +- .../test/browser/foldingRanges.test.ts | 2 +- .../test/browser/hiddenRangeModel.test.ts | 2 +- .../folding/test/browser/indentFold.test.ts | 2 +- .../test/browser/indentRangeProvider.test.ts | 2 +- .../folding/test/browser/syntaxFold.test.ts | 2 +- .../gotoError/browser/gotoErrorWidget.ts | 7 +- .../gotoSymbol/browser/goToCommands.ts | 14 +- .../contrib/gotoSymbol/browser/goToSymbol.ts | 64 +- .../browser/link/goToDefinitionAtPosition.ts | 2 +- .../browser/peek/referencesController.ts | 7 +- .../browser/peek/referencesWidget.ts | 18 +- .../test/browser/referencesModel.test.ts | 2 +- .../hover/browser/contentHoverController.ts | 456 - ...ntroller.ts => contentHoverController2.ts} | 149 +- .../hover/browser/contentHoverRendered.ts | 436 + .../hover/browser/contentHoverStatusBar.ts | 6 +- .../hover/browser/contentHoverTypes.ts | 31 +- .../hover/browser/contentHoverWidget.ts | 89 +- .../browser/contentHoverWidgetWrapper.ts | 406 + .../editor/contrib/hover/browser/getHover.ts | 13 +- .../hover/browser/hoverAccessibleViews.ts | 151 +- .../contrib/hover/browser/hoverActions.ts | 36 +- .../hover/browser/hoverContribution.ts | 6 +- .../contrib/hover/browser/hoverTypes.ts | 57 +- .../contrib/hover/browser/hoverUtils.ts | 17 + .../hover/browser/marginHoverController.ts | 232 + .../hover/browser/marginHoverWidget.ts | 17 +- .../hover/browser/markdownHoverParticipant.ts | 273 +- .../hover/browser/markerHoverParticipant.ts | 35 +- .../hover/test/browser/contentHover.test.ts | 14 +- .../indentation/browser/indentation.ts | 35 +- .../test/browser/indentation.test.ts | 325 +- .../browser/indentationLineProcessor.test.ts | 48 +- .../inlayHints/browser/inlayHintsHover.ts | 4 +- .../browser/{ => controller}/commandIds.ts | 0 .../browser/{ => controller}/commands.ts | 6 +- .../inlineCompletionContextKeys.ts | 2 +- .../inlineCompletionsController.ts | 265 +- .../{ => hintsWidget}/hoverParticipant.ts | 39 +- .../inlineCompletionsHintsWidget.css | 0 .../inlineCompletionsHintsWidget.ts | 6 +- .../browser/inlineCompletions.contribution.ts | 6 +- .../inlineCompletionsAccessibleView.ts | 99 +- .../browser/{ => model}/ghostText.ts | 0 .../{ => model}/inlineCompletionsModel.ts | 85 +- .../{ => model}/inlineCompletionsSource.ts | 14 +- .../{ => model}/provideInlineCompletions.ts | 15 +- .../browser/{ => model}/singleTextEdit.ts | 2 +- .../suggestWidgetAdaptor.ts} | 31 +- .../{ghostText.css => view/ghostTextView.css} | 0 .../ghostTextView.ts} | 8 +- .../browser/inlineCompletionsModel.test.ts | 4 +- .../browser/inlineCompletionsProvider.test.ts | 8 +- .../test/browser/suggestWidgetModel.test.ts | 6 +- .../inlineCompletions/test/browser/utils.ts | 2 +- .../inlineEdit/browser/ghostTextWidget.ts | 37 +- .../inlineEdit/browser/hoverParticipant.ts | 23 +- .../browser/inlineEdit.contribution.ts | 6 +- .../contrib/inlineEdit/browser/inlineEdit.css | 5 - .../browser/inlineEditController.ts | 90 +- .../browser/inlineEditHintsWidget.ts | 8 +- .../browser/inlineEditSideBySideWidget.css | 12 + .../browser/inlineEditSideBySideWidget.ts | 355 + .../contrib/inlineEdits/browser/commands.ts | 185 + .../contrib/inlineEdits/browser/consts.ts | 16 + .../browser/inlineEdits.contribution.ts | 19 + .../browser/inlineEditsController.ts | 97 + .../inlineEdits/browser/inlineEditsModel.ts | 289 + .../inlineEdits/browser/inlineEditsWidget.css | 49 + .../inlineEdits/browser/inlineEditsWidget.ts | 400 + .../inlineProgress/browser/inlineProgress.ts | 17 +- .../test/browser/lineSelection.test.ts | 2 +- .../browser/linesOperations.ts | 5 +- .../browser/moveLinesCommand.ts | 1 + .../test/browser/copyLinesCommand.test.ts | 2 +- .../test/browser/linesOperations.test.ts | 2 +- .../test/browser/linkedEditing.test.ts | 2 +- .../test/browser/multicursor.test.ts | 2 +- .../parameterHints/browser/parameterHints.css | 4 + .../browser/parameterHintsWidget.ts | 16 +- .../test/browser/parameterHintsModel.test.ts | 2 +- .../contrib/peekView/browser/peekView.ts | 4 +- .../browser/placeholderText.contribution.ts | 16 + .../browser/placeholderText.css | 15 +- .../browser/placeholderTextContribution.ts | 80 +- .../browser/editorNavigationQuickAccess.ts | 8 +- .../browser/gotoLineQuickAccess.ts | 4 +- .../browser/gotoSymbolQuickAccess.ts | 12 +- .../contrib/rename/browser/renameWidget.ts | 22 +- .../browser/documentSemanticTokens.test.ts | 10 +- .../test/browser/getSemanticTokens.test.ts | 2 +- .../test/browser/smartSelect.test.ts | 2 +- .../browser/snippetController2.old.test.ts | 2 +- .../test/browser/snippetController2.test.ts | 2 +- .../test/browser/snippetParser.test.ts | 2 +- .../test/browser/snippetSession.test.ts | 2 +- .../test/browser/snippetVariables.test.ts | 2 +- .../stickyScroll/browser/stickyScroll.css | 2 +- .../browser/stickyScrollController.ts | 69 +- .../browser/stickyScrollWidget.ts | 12 +- .../test/browser/stickyScroll.test.ts | 2 +- .../editor/contrib/suggest/browser/suggest.ts | 6 +- .../suggest/browser/suggestController.ts | 32 +- .../contrib/suggest/browser/suggestModel.ts | 7 +- .../contrib/suggest/browser/suggestWidget.ts | 21 +- .../suggest/browser/suggestWidgetStatus.ts | 23 +- .../test/browser/completionModel.test.ts | 2 +- .../suggest/test/browser/suggest.test.ts | 2 +- .../test/browser/suggestController.test.ts | 2 +- .../browser/suggestInlineCompletions.test.ts | 2 +- .../test/browser/suggestMemory.test.ts | 2 +- .../suggest/test/browser/suggestModel.test.ts | 2 +- .../suggest/test/browser/wordDistance.test.ts | 15 +- .../symbolIcons/browser/symbolIcons.ts | 191 +- .../browser/unicodeHighlighter.ts | 10 +- .../browser/highlightDecorations.ts | 10 +- .../browser/textualHighlightProvider.ts | 29 +- .../browser/wordHighlighter.ts | 170 +- .../wordOperations/browser/wordOperations.ts | 28 +- .../test/browser/wordOperations.test.ts | 49 +- .../browser/wordPartOperations.ts | 6 +- .../test/browser/wordPartOperations.test.ts | 2 +- patched-vscode/src/vs/editor/editor.all.ts | 2 + patched-vscode/src/vs/editor/editor.worker.ts | 28 +- .../quickInput/standaloneQuickInputService.ts | 8 +- .../browser/standaloneCodeEditor.ts | 1 - .../standalone/browser/standaloneEditor.ts | 5 +- .../standalone/browser/standaloneServices.ts | 29 +- .../browser/standaloneTreeSitterService.ts | 26 + .../browser/standaloneWebWorker.ts} | 22 +- .../common/monarch/monarchCompile.ts | 30 +- .../standalone/test/browser/monarch.test.ts | 4 +- .../test/browser/standaloneLanguages.test.ts | 2 +- .../test/browser/standaloneServices.test.ts | 2 +- .../browser/commands/shiftCommand.test.ts | 2 +- .../test/browser/commands/sideEditing.test.ts | 2 +- .../trimTrailingWhitespaceCommand.test.ts | 2 +- .../config/editorConfiguration.test.ts | 2 +- .../config/editorLayoutProvider.test.ts | 2 +- .../controller/cursor.integrationTest.ts | 2 +- .../test/browser/controller/cursor.test.ts | 2 +- .../controller/cursorMoveCommand.test.ts | 2 +- .../browser/controller/textAreaInput.test.ts | 2 +- .../browser/controller/textAreaState.test.ts | 2 +- .../services/decorationRenderOptions.test.ts | 2 +- .../browser/services/openerService.test.ts | 2 +- .../services/treeSitterParserService.test.ts | 151 + .../vs/editor/test/browser/testCodeEditor.ts | 3 + .../src/vs/editor/test/browser/testCommand.ts | 2 +- .../browser/view/minimapCharRenderer.test.ts | 2 +- .../test/browser/view/viewLayer.test.ts | 10 +- .../viewModel/modelLineProjection.test.ts | 2 +- .../test/browser/viewModel/testViewModel.ts | 2 + .../viewModel/viewModelDecorations.test.ts | 2 +- .../browser/viewModel/viewModelImpl.test.ts | 2 +- .../browser/widget/codeEditorWidget.test.ts | 2 +- .../browser/widget/diffEditorWidget.test.ts | 2 +- .../widget/observableCodeEditor.test.ts | 226 + .../cursorAtomicMoveOperations.test.ts | 2 +- .../controller/cursorMoveHelper.test.ts | 2 +- .../common/core/characterClassifier.test.ts | 2 +- .../editor/test/common/core/lineRange.test.ts | 2 +- .../test/common/core/lineTokens.test.ts | 2 +- .../core/positionOffsetTransformer.test.ts | 2 +- .../vs/editor/test/common/core/range.test.ts | 2 +- .../test/common/core/stringBuilder.test.ts | 2 +- .../editor/test/common/core/textEdit.test.ts | 2 +- .../test/common/diff/diffComputer.test.ts | 2 +- .../beforeEditPositionMapper.test.ts | 2 +- .../bracketPairColorizer/brackets.test.ts | 2 +- .../combineTextEditInfos.test.ts | 2 +- .../concat23Trees.test.ts | 2 +- .../getBracketPairsInRange.test.ts | 2 +- .../model/bracketPairColorizer/length.test.ts | 2 +- .../smallImmutableSet.test.ts | 2 +- .../bracketPairColorizer/tokenizer.test.ts | 2 +- .../test/common/model/editStack.test.ts | 2 +- .../common/model/editableTextModel.test.ts | 2 +- .../model/editableTextModelTestUtils.ts | 2 +- .../test/common/model/intervalTree.test.ts | 3 +- .../linesTextBuffer/linesTextBuffer.test.ts | 2 +- .../linesTextBufferBuilder.test.ts | 2 +- .../test/common/model/model.line.test.ts | 2 +- .../test/common/model/model.modes.test.ts | 2 +- .../vs/editor/test/common/model/model.test.ts | 2 +- .../common/model/modelDecorations.test.ts | 2 +- .../common/model/modelEditOperation.test.ts | 2 +- .../common/model/modelInjectedText.test.ts | 2 +- .../pieceTreeTextBuffer.test.ts | 18 +- .../test/common/model/textChange.test.ts | 2 +- .../test/common/model/textModel.test.ts | 2 +- .../test/common/model/textModelSearch.test.ts | 2 +- .../test/common/model/textModelTokens.test.ts | 2 +- .../common/model/textModelWithTokens.test.ts | 2 +- .../test/common/model/tokensStore.test.ts | 2 +- .../modes/languageConfiguration.test.ts | 2 +- .../common/modes/languageSelector.test.ts | 2 +- .../test/common/modes/linkComputer.test.ts | 2 +- .../modes/supports/autoClosingPairsRules.ts | 12 +- .../modes/supports/characterPair.test.ts | 2 +- .../modes/supports/electricCharacter.test.ts | 2 +- .../common/modes/supports/onEnter.test.ts | 2 +- .../modes/supports/richEditBrackets.test.ts | 2 +- .../modes/supports/tokenization.test.ts | 2 +- .../common/modes/textToHtmlTokenizer.test.ts | 2 +- .../services/editorSimpleWorker.test.ts | 28 +- .../common/services/languageService.test.ts | 2 +- .../services/languagesAssociations.test.ts | 2 +- .../common/services/languagesRegistry.test.ts | 2 +- .../test/common/services/modelService.test.ts | 2 +- .../common/services/semanticTokensDto.test.ts | 2 +- .../semanticTokensProviderStyling.test.ts | 2 +- .../services/testEditorWorkerService.ts | 3 +- .../common/services/testTreeSitterService.ts | 24 + .../textResourceConfigurationService.test.ts | 2 +- .../unicodeTextModelHighlighter.test.ts | 2 +- .../vs/editor/test/common/testTextModel.ts | 3 + .../common/view/overviewZoneManager.test.ts | 2 +- .../common/viewLayout/lineDecorations.test.ts | 2 +- .../common/viewLayout/linesLayout.test.ts | 2 +- .../viewLayout/viewLineRenderer.test.ts | 2 +- .../common/viewModel/glyphLanesModel.test.ts | 2 +- .../common/viewModel/lineBreakData.test.ts | 2 +- .../monospaceLineBreaksComputer.test.ts | 2 +- .../viewModel/prefixSumComputer.test.ts | 2 +- .../node/classification/typescript.test.ts | 2 +- .../diffing/defaultLinesDiffComputer.test.ts | 8 +- .../editor/test/node/diffing/fixtures.test.ts | 33 +- .../advanced.expected.diff.json | 2 +- .../node/diffing/fixtures/issue-214049/1.txt | 2 + .../node/diffing/fixtures/issue-214049/2.txt | 3 + .../issue-214049/advanced.expected.diff.json | 26 + .../issue-214049/legacy.expected.diff.json | 17 + .../diffing/fixtures/sorted-offsets/1.tst | 102 + .../diffing/fixtures/sorted-offsets/2.tst | 87 + .../advanced.expected.diff.json | 42 + .../sorted-offsets/legacy.expected.diff.json | 74 + patched-vscode/src/vs/monaco.d.ts | 163 +- patched-vscode/src/vs/nls.build.ts | 88 - patched-vscode/src/vs/nls.messages.ts | 17 + patched-vscode/src/vs/nls.mock.ts | 43 - patched-vscode/src/vs/nls.ts | 325 +- .../browser/accessibilityService.ts | 29 + .../accessibility/browser/accessibleView.ts | 46 +- .../browser/accessibleViewRegistry.ts | 21 +- .../browser/accessibilitySignalService.ts | 17 +- .../browser/media/voiceRecordingStarted.mp3 | Bin 27712 -> 33472 bytes .../browser/media/voiceRecordingStopped.mp3 | Bin 27712 -> 25408 bytes .../actionWidget/browser/actionWidget.css | 25 +- .../actionWidget/browser/actionWidget.ts | 2 +- .../vs/platform/actions/browser/buttonbar.ts | 66 +- .../platform/actions/browser/floatingMenu.ts | 2 +- .../browser/menuEntryActionViewItem.css | 14 + .../browser/menuEntryActionViewItem.ts | 101 +- .../vs/platform/actions/browser/toolbar.ts | 4 +- .../src/vs/platform/actions/common/actions.ts | 26 + .../vs/platform/actions/common/menuService.ts | 101 +- .../actions/test/common/menuService.test.ts | 2 +- .../electron-main/auxiliaryWindow.ts | 10 +- .../auxiliaryWindowsMainService.ts | 16 +- .../electron-main/backupMainService.test.ts | 8 +- .../test/node/checksumService.test.ts | 2 +- .../clipboard/browser/clipboardService.ts | 73 +- .../clipboard/common/clipboardService.ts | 7 + .../commands/test/common/commands.test.ts | 2 +- .../common/configurationModels.ts | 13 +- .../common/configurationRegistry.ts | 276 +- .../configuration/common/configurations.ts | 4 +- .../test/common/configuration.test.ts | 2 +- .../test/common/configurationModels.test.ts | 2 +- .../test/common/configurationRegistry.test.ts | 116 +- .../test/common/configurationService.test.ts | 2 +- .../test/common/configurations.test.ts | 67 +- .../test/common/policyConfiguration.test.ts | 2 +- .../test/browser/contextkey.test.ts | 2 +- .../contextkey/test/common/contextkey.test.ts | 2 +- .../contextkey/test/common/parser.test.ts | 2 +- .../contextkey/test/common/scanner.test.ts | 2 +- .../contextview/browser/contextMenuService.ts | 5 +- .../vs/platform/cssDev/node/cssDevService.ts | 73 + .../diagnostics/common/diagnostics.ts | 2 + .../diagnostics/node/diagnosticsService.ts | 43 +- .../electron-main/dialogMainService.ts | 72 +- .../electron-main/encryptionMainService.ts | 15 +- .../vs/platform/environment/common/argv.ts | 2 - .../electron-main/environmentMainService.ts | 6 - .../src/vs/platform/environment/node/argv.ts | 4 +- .../platform/environment/node/argvHelper.ts | 2 +- .../src/vs/platform/environment/node/stdin.ts | 8 +- .../platform/environment/node/userDataPath.js | 23 +- .../environmentMainService.test.ts | 2 +- .../environment/test/node/argv.test.ts | 2 +- .../test/node/environmentService.test.ts | 2 +- .../node/nativeModules.integrationTest.ts | 133 +- .../test/node/userDataPath.test.ts | 2 +- .../abstractExtensionManagementService.ts | 153 +- .../common/extensionEnablementService.ts | 10 +- .../common/extensionGalleryService.ts | 126 +- .../common/extensionManagement.ts | 38 +- .../common/extensionManagementCLI.ts | 6 +- .../common/extensionManagementIpc.ts | 57 +- .../common/extensionsScannerService.ts | 31 +- .../node/extensionManagementService.ts | 193 +- .../extensionSignatureVerificationService.ts | 27 +- .../node/extensionsWatcher.ts | 3 +- .../test/common/configRemotes.test.ts | 2 +- .../common/extensionGalleryService.test.ts | 3 +- .../test/common/extensionManagement.test.ts | 2 +- .../test/common/extensionNls.test.ts | 11 +- .../extensionsProfileScannerService.test.ts | 2 +- .../test/node/extensionDownloader.test.ts | 2 +- .../node/extensionsScannerService.test.ts | 2 +- .../common/extensionResourceLoader.ts | 7 +- .../extensions/common/extensionValidator.ts | 47 +- .../platform/extensions/common/extensions.ts | 33 +- .../common/extensionsApiProposals.ts | 412 + .../electron-main/extensionHostStarter.ts | 24 +- .../test/common/extensionValidator.test.ts | 21 +- .../extensions/test/common/extensions.test.ts | 22 + .../node/externalTerminalService.ts | 5 +- .../src/vs/platform/files/common/files.ts | 3 +- .../src/vs/platform/files/common/watcher.ts | 68 +- .../files/node/diskFileSystemProvider.ts | 34 +- .../files/node/watcher/baseWatcher.ts | 20 +- .../node/watcher/nodejs/nodejsWatcherLib.ts | 4 +- .../node/watcher/parcel/parcelWatcher.ts | 48 +- .../files/test/browser/fileService.test.ts | 2 +- .../indexedDBFileService.integrationTest.ts | 4 +- .../platform/files/test/common/files.test.ts | 2 +- .../files/test/common/watcher.test.ts | 2 +- .../node/diskFileService.integrationTest.ts | 36 +- .../node/nodejsWatcher.integrationTest.ts | 51 +- .../node/parcelWatcher.integrationTest.ts | 58 +- .../hover/test/browser/nullHoverService.ts | 4 +- .../common/instantiationService.ts | 7 + .../instantiation/test/common/graph.test.ts | 2 +- .../test/common/instantiationService.test.ts | 2 +- .../src/vs/platform/issue/common/issue.ts | 42 +- .../issue/electron-main/issueMainService.ts | 256 +- .../issue/electron-main/processMainService.ts | 383 + .../common/jsonContributionRegistry.ts | 23 +- .../common/abstractKeybindingService.test.ts | 2 +- .../test/common/keybindingLabels.test.ts | 2 +- .../test/common/keybindingResolver.test.ts | 2 +- .../languagePacks/node/languagePacks.ts | 3 +- .../electron-main/lifecycleMainService.ts | 36 +- .../vs/platform/list/browser/listService.ts | 2 +- .../platform/markers/common/markerService.ts | 1 - .../markers/test/common/markerService.test.ts | 2 +- .../platform/menubar/electron-main/menubar.ts | 13 +- .../electron-main/menubarMainService.ts | 9 +- .../src/vs/platform/native/common/native.ts | 10 +- .../native}/electron-main/auth.ts | 195 +- .../electron-main/nativeHostMainService.ts | 118 +- .../common/platformObservableUtils.ts | 7 +- .../common/wrapInReloadableClass.ts | 62 + .../src/vs/platform/opener/browser/link.ts | 6 +- .../opener/test/common/opener.test.ts | 2 +- .../src/vs/platform/product/common/product.ts | 12 +- .../profileAnalysisWorker.esm.ts | 9 + .../electron-sandbox/profileAnalysisWorker.ts | 12 +- .../profileAnalysisWorkerService.ts | 29 +- .../vs/platform/progress/common/progress.ts | 4 +- .../progress/test/common/progress.test.ts | 2 +- .../electron-main/protocolMainService.ts | 3 +- .../quickinput/browser/helpQuickAccess.ts | 2 +- .../quickinput/browser/media/quickInput.css | 10 +- .../quickinput/browser/pickerQuickAccess.ts | 2 +- .../quickinput/browser/quickAccess.ts | 8 +- .../platform/quickinput/browser/quickInput.ts | 55 +- .../browser/quickInputController.ts | 27 +- .../quickinput/browser/quickInputService.ts | 8 +- .../quickinput/browser/quickInputTree.ts | 341 +- .../quickinput/browser/quickPickPin.ts | 15 +- .../platform/quickinput/common/quickAccess.ts | 3 +- .../platform/quickinput/common/quickInput.ts | 29 +- .../test/browser/quickinput.test.ts | 4 +- .../registry/test/common/platform.test.ts | 2 +- .../remote/browser/browserSocketFactory.ts | 1 - .../remote/common/remoteExtensionsScanner.ts | 2 - .../src/vs/platform/remote/node/wsl.ts | 10 +- .../remote/test/common/remoteHosts.test.ts | 2 +- .../remoteAuthorityResolverService.test.ts | 2 +- .../request/browser/requestService.ts | 10 +- .../src/vs/platform/request/common/request.ts | 24 + .../vs/platform/request/common/requestIpc.ts | 12 +- .../src/vs/platform/request/node/proxy.ts | 20 +- .../platform/request/node/requestService.ts | 24 +- .../secrets/test/common/secrets.test.ts | 2 +- .../vs/platform/sign/browser/signService.ts | 10 +- .../src/vs/platform/sign/node/signService.ts | 14 +- .../vs/platform/state/test/node/state.test.ts | 6 +- .../storage/electron-main/storageMain.ts | 3 +- .../platform/telemetry/common/1dsAppender.ts | 15 +- .../telemetry/common/telemetryService.ts | 4 +- .../vs/platform/telemetry/node/telemetry.ts | 9 +- .../test/browser/1dsAppender.test.ts | 2 +- .../test/browser/telemetryService.test.ts | 4 +- .../test/common/telemetryLogAppender.test.ts | 2 +- .../common/capabilities/capabilities.ts | 8 +- .../commandDetection/promptInputModel.ts | 79 +- .../commandDetectionCapability.ts | 26 +- .../vs/platform/terminal/common/terminal.ts | 16 +- .../terminal/common/terminalRecorder.ts | 3 +- .../common/xterm/shellIntegrationAddon.ts | 3 +- .../electron-main/electronPtyHostStarter.ts | 2 +- .../platform/terminal/node/ptyHostService.ts | 8 +- .../vs/platform/terminal/node/ptyService.ts | 9 +- .../terminal/node/terminalEnvironment.ts | 4 + .../platform/terminal/node/terminalProcess.ts | 37 +- .../terminal/node/terminalProfiles.ts | 3 +- .../terminal/node/windowsShellHelper.ts | 10 +- .../commandDetection/promptInputModel.test.ts | 31 +- .../test/common/terminalRecorder.test.ts | 2 +- .../test/node/terminalEnvironment.test.ts | 12 +- .../platform/theme/browser/defaultStyles.ts | 16 +- .../vs/platform/theme/common/colorUtils.ts | 51 +- .../theme/common/colors/baseColors.ts | 2 +- .../theme/common/colors/chartsColors.ts | 12 +- .../theme/common/colors/editorColors.ts | 72 +- .../theme/common/colors/inputColors.ts | 47 +- .../theme/common/colors/listColors.ts | 42 +- .../theme/common/colors/menuColors.ts | 8 +- .../theme/common/colors/minimapColors.ts | 10 +- .../theme/common/colors/miscColors.ts | 2 +- .../theme/common/colors/quickpickColors.ts | 10 +- .../theme/electron-main/themeMainService.ts | 32 +- .../src/vs/platform/tunnel/common/tunnel.ts | 9 +- .../tunnel/test/common/tunnel.test.ts | 2 +- .../test/common/undoRedoService.test.ts | 2 +- .../common/update.config.contribution.ts | 4 +- .../electron-main/updateService.win32.ts | 4 +- .../test/common/uriIdentityService.test.ts | 2 +- .../test/browser/fileUserDataProvider.test.ts | 2 +- .../userDataProfile/common/userDataProfile.ts | 107 +- .../common/userDataProfileStorageService.ts | 65 +- .../userDataProfileStorageService.ts | 2 +- .../userDataProfile/node/userDataProfile.ts | 2 +- .../node/userDataProfileStorageService.ts | 4 +- .../common/userDataProfileService.test.ts | 2 +- .../userDataProfileStorageService.test.ts | 5 +- .../userDataProfileMainService.test.ts | 2 +- .../common/abstractSynchronizer.ts | 4 +- .../userDataSync/common/extensionsSync.ts | 2 +- .../userDataSync/common/globalStateSync.ts | 2 +- .../userDataSync/common/settingsSync.ts | 34 +- .../userDataSync/common/userDataSync.ts | 41 +- .../common/userDataSyncLocalStoreService.ts | 2 +- .../common/userDataSyncService.ts | 3 +- .../common/userDataSyncStoreService.ts | 2 +- .../test/common/extensionsMerge.test.ts | 2 +- .../test/common/globalStateMerge.test.ts | 2 +- .../test/common/globalStateSync.test.ts | 2 +- .../test/common/keybindingsMerge.test.ts | 2 +- .../test/common/keybindingsSync.test.ts | 2 +- .../test/common/settingsMerge.test.ts | 2 +- .../test/common/settingsSync.test.ts | 2 +- .../test/common/snippetsMerge.test.ts | 2 +- .../test/common/snippetsSync.test.ts | 2 +- .../test/common/synchronizer.test.ts | 2 +- .../test/common/tasksSync.test.ts | 2 +- .../common/userDataAutoSyncService.test.ts | 2 +- .../userDataProfilesManifestMerge.test.ts | 2 +- .../userDataProfilesManifestSync.test.ts | 2 +- .../test/common/userDataSyncClient.ts | 8 +- .../test/common/userDataSyncService.test.ts | 2 +- .../common/userDataSyncStoreService.test.ts | 4 +- .../electron-main/utilityProcess.ts | 17 +- .../src/vs/platform/window/common/window.ts | 20 +- .../platform/window/electron-main/window.ts | 10 +- .../windows/electron-main/windowImpl.ts | 90 +- .../platform/windows/electron-main/windows.ts | 52 +- .../electron-main/windowsMainService.ts | 48 +- .../electron-main/windowsStateHandler.ts | 26 +- .../test/electron-main/windowsFinder.test.ts | 2 +- .../electron-main/windowsStateHandler.test.ts | 2 +- .../workspace/test/common/workspace.test.ts | 2 +- .../workspacesManagementMainService.ts | 9 +- .../workspaces/test/common/workspaces.test.ts | 2 +- .../test/electron-main/workspaces.test.ts | 4 +- .../workspacesHistoryStorage.test.ts | 2 +- .../workspacesManagementMainService.test.ts | 4 +- .../vs/server/node/extensionHostConnection.ts | 36 +- .../server/node/extensionsScannerService.ts | 6 +- .../node/remoteExtensionHostAgentServer.ts | 7 +- .../vs/server/node/remoteExtensionsScanner.ts | 31 - .../src/vs/server/node/remoteLanguagePacks.ts | 61 +- .../src/vs/server/node/server.cli.ts | 39 +- .../vs/server/node/serverConnectionToken.ts | 2 +- .../server/node/serverEnvironmentService.ts | 6 - .../src/vs/server/node/serverServices.ts | 4 + .../src/vs/server/node/webClientServer.ts | 158 +- .../test/node/serverConnectionToken.test.ts | 2 +- .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadAuthentication.ts | 93 +- .../api/browser/mainThreadChatAgents2.ts | 31 +- .../api/browser/mainThreadChatVariables.ts | 7 - .../api/browser/mainThreadComments.ts | 59 +- .../api/browser/mainThreadDebugService.ts | 1 + .../api/browser/mainThreadEditorTabs.ts | 6 +- .../workbench/api/browser/mainThreadErrors.ts | 8 +- .../api/browser/mainThreadExtensionService.ts | 12 +- .../api/browser/mainThreadLanguageFeatures.ts | 36 +- .../browser/mainThreadLanguageModelTools.ts | 69 + .../api/browser/mainThreadLanguageModels.ts | 91 +- .../api/browser/mainThreadNotebook.ts | 2 +- .../browser/mainThreadNotebookDocuments.ts | 47 +- .../api/browser/mainThreadNotebookEditors.ts | 1 + .../api/browser/mainThreadQuickOpen.ts | 41 +- .../vs/workbench/api/browser/mainThreadSCM.ts | 139 +- .../mainThreadTerminalShellIntegration.ts | 2 +- .../api/browser/mainThreadTesting.ts | 67 +- .../api/browser/mainThreadWorkspace.ts | 15 +- .../api/common/configurationExtensionPoint.ts | 52 +- .../workbench/api/common/extHost.api.impl.ts | 110 +- .../api/common/extHost.common.services.ts | 2 + .../workbench/api/common/extHost.protocol.ts | 136 +- .../api/common/extHostApiCommands.ts | 30 + .../api/common/extHostAuthentication.ts | 18 +- .../api/common/extHostChatAgents2.ts | 222 +- .../api/common/extHostChatVariables.ts | 4 - .../workbench/api/common/extHostComments.ts | 37 +- .../api/common/extHostDebugService.ts | 39 +- .../api/common/extHostDiagnostics.ts | 2 +- .../vs/workbench/api/common/extHostDialogs.ts | 4 +- .../api/common/extHostDocumentData.ts | 3 + .../workbench/api/common/extHostEmbedding.ts | 1 + .../api/common/extHostExtensionActivator.ts | 14 +- .../api/common/extHostExtensionService.ts | 40 +- .../api/common/extHostLanguageFeatures.ts | 100 +- .../api/common/extHostLanguageModelTools.ts | 116 + .../api/common/extHostLanguageModels.ts | 166 +- .../workbench/api/common/extHostNotebook.ts | 27 +- .../api/common/extHostNotebookDocument.ts | 3 + .../api/common/extHostNotebookEditor.ts | 3 + .../workbench/api/common/extHostQuickOpen.ts | 19 +- .../src/vs/workbench/api/common/extHostSCM.ts | 91 +- .../vs/workbench/api/common/extHostSearch.ts | 91 +- .../vs/workbench/api/common/extHostSecrets.ts | 22 +- .../vs/workbench/api/common/extHostTask.ts | 3 +- .../api/common/extHostTerminalService.ts | 13 +- .../common/extHostTerminalShellIntegration.ts | 13 +- .../vs/workbench/api/common/extHostTesting.ts | 297 +- .../workbench/api/common/extHostTextEditor.ts | 3 + .../api/common/extHostTextEditors.ts | 9 +- .../api/common/extHostTypeConverters.ts | 283 +- .../vs/workbench/api/common/extHostTypes.ts | 166 +- .../workbench/api/common/extHostWorkspace.ts | 396 +- .../workbench/api/common/extensionHostMain.ts | 4 +- .../workbench/api/node/extHostDebugService.ts | 8 +- .../api/node/extHostExtensionService.ts | 6 +- .../vs/workbench/api/node/extHostSearch.ts | 52 +- .../workbench/api/node/extHostStoragePaths.ts | 12 +- .../api/node/extHostTunnelService.ts | 11 +- .../api/node/extensionHostProcess.ts | 27 +- .../vs/workbench/api/node/proxyResolver.ts | 90 +- .../api/test/browser/extHost.api.impl.test.ts | 2 +- .../test/browser/extHostApiCommands.test.ts | 18 +- .../extHostAuthentication.integrationTest.ts | 2 +- .../api/test/browser/extHostBulkEdits.test.ts | 2 +- .../api/test/browser/extHostCommands.test.ts | 2 +- .../test/browser/extHostConfiguration.test.ts | 2 +- .../test/browser/extHostDecorations.test.ts | 2 +- .../test/browser/extHostDiagnostics.test.ts | 2 +- .../extHostDocumentContentProvider.test.ts | 2 +- .../test/browser/extHostDocumentData.test.ts | 2 +- .../extHostDocumentSaveParticipant.test.ts | 2 +- .../extHostDocumentsAndEditors.test.ts | 2 +- .../test/browser/extHostEditorTabs.test.ts | 2 +- .../extHostFileSystemEventService.test.ts | 2 +- .../browser/extHostLanguageFeatures.test.ts | 24 +- .../browser/extHostMessagerService.test.ts | 2 +- .../api/test/browser/extHostNotebook.test.ts | 2 +- .../browser/extHostNotebookKernel.test.ts | 3 +- .../api/test/browser/extHostTelemetry.test.ts | 5 +- .../api/test/browser/extHostTesting.test.ts | 35 +- .../test/browser/extHostTextEditor.test.ts | 2 +- .../api/test/browser/extHostTreeViews.test.ts | 2 +- .../test/browser/extHostTypeConverter.test.ts | 2 +- .../api/test/browser/extHostTypes.test.ts | 2 +- .../api/test/browser/extHostWebview.test.ts | 2 +- .../api/test/browser/extHostWorkspace.test.ts | 17 +- .../test/browser/mainThreadBulkEdits.test.ts | 2 +- .../test/browser/mainThreadCommands.test.ts | 2 +- .../browser/mainThreadConfiguration.test.ts | 2 +- .../browser/mainThreadDiagnostics.test.ts | 2 +- ...mainThreadDocumentContentProviders.test.ts | 2 +- .../test/browser/mainThreadDocuments.test.ts | 2 +- .../mainThreadDocumentsAndEditors.test.ts | 17 +- .../test/browser/mainThreadEditors.test.ts | 26 +- .../browser/mainThreadManagedSockets.test.ts | 2 +- .../test/browser/mainThreadTreeViews.test.ts | 2 +- .../test/browser/mainThreadWorkspace.test.ts | 13 +- .../common/extHostExtensionActivator.test.ts | 14 +- .../api/test/common/extensionHostMain.test.ts | 6 +- .../api/test/node/extHostSearch.test.ts | 103 +- .../test/node/extHostTunnelService.test.ts | 2 +- .../api/worker/extensionHostWorker.esm.ts | 9 + .../api/worker/extensionHostWorker.ts | 4 + .../src/vs/workbench/browser/actions.ts | 5 +- .../browser/actions/developerActions.ts | 2 +- .../browser/actions/layoutActions.ts | 92 +- .../workbench/browser/actions/listCommands.ts | 2 +- .../browser/actions/quickAccessActions.ts | 9 +- .../src/vs/workbench/browser/client.ts | 61 - .../src/vs/workbench/browser/contextkeys.ts | 24 +- .../src/vs/workbench/browser/layout.ts | 142 +- .../vs/workbench/browser/media/code-icon.svg | 5 +- .../src/vs/workbench/browser/media/style.css | 9 + .../parts/activitybar/activitybarPart.ts | 5 +- .../activitybar/media/activityaction.css | 3 +- .../parts/auxiliarybar/auxiliaryBarPart.ts | 13 +- .../auxiliarybar/media/auxiliaryBarPart.css | 2 +- .../browser/parts/compositeBarActions.ts | 9 +- .../workbench/browser/parts/compositePart.ts | 2 +- .../parts/editor/breadcrumbsControl.ts | 3 +- .../parts/editor/editor.contribution.ts | 5 +- .../browser/parts/editor/editorActions.ts | 74 +- .../browser/parts/editor/editorAutoSave.ts | 4 +- .../browser/parts/editor/editorCommands.ts | 575 +- .../parts/editor/editorCommandsContext.ts | 200 + .../browser/parts/editor/editorDropTarget.ts | 2 +- .../browser/parts/editor/editorGroupView.ts | 16 +- .../browser/parts/editor/editorPanes.ts | 2 +- .../browser/parts/editor/editorPart.ts | 43 +- .../browser/parts/editor/editorParts.ts | 37 +- .../browser/parts/editor/editorPlaceholder.ts | 4 +- .../browser/parts/editor/editorQuickAccess.ts | 2 +- .../browser/parts/editor/editorStatus.ts | 51 +- .../parts/editor/media/letterpress-dark.svg | 35 +- .../parts/editor/media/letterpress-hcDark.svg | 35 +- .../editor/media/letterpress-hcLight.svg | 35 +- .../parts/editor/media/letterpress-light.svg | 33 +- .../parts/editor/multiEditorTabsControl.ts | 28 +- .../parts/editor/multiRowEditorTabsControl.ts | 38 +- .../browser/parts/editor/sideBySideEditor.ts | 2 +- .../browser/parts/globalCompositeBar.ts | 10 +- .../browser/parts/media/paneCompositePart.css | 12 +- .../media/notificationsCenter.css | 8 +- .../notifications/media/notificationsList.css | 6 - .../media/notificationsToasts.css | 7 + .../notificationAccessibleView.ts | 51 +- .../notifications/notificationsCommands.ts | 4 +- .../notifications/notificationsToasts.ts | 4 +- .../notifications/notificationsViewer.ts | 10 +- .../browser/parts/paneCompositePart.ts | 4 +- .../browser/parts/panel/media/panelpart.css | 17 + .../browser/parts/panel/panelActions.ts | 20 +- .../browser/parts/panel/panelPart.ts | 25 +- .../parts/sidebar/media/sidebarpart.css | 2 +- .../parts/statusbar/media/statusbarpart.css | 5 + .../browser/parts/statusbar/statusbarItem.ts | 12 +- .../browser/parts/statusbar/statusbarModel.ts | 32 +- .../browser/parts/statusbar/statusbarPart.ts | 27 +- .../parts/titlebar/commandCenterControl.ts | 4 +- .../parts/titlebar/media/titlebarpart.css | 51 +- .../browser/parts/titlebar/titlebarPart.ts | 99 +- .../workbench/browser/parts/views/checkbox.ts | 8 +- .../workbench/browser/parts/views/treeView.ts | 120 +- .../browser/parts/views/viewFilter.ts | 2 + .../workbench/browser/parts/views/viewPane.ts | 24 +- .../browser/parts/views/viewPaneContainer.ts | 12 +- .../src/vs/workbench/browser/web.api.ts | 5 - .../src/vs/workbench/browser/web.main.ts | 13 +- .../src/vs/workbench/browser/window.ts | 12 +- .../browser/workbench.contribution.ts | 21 +- .../src/vs/workbench/browser/workbench.ts | 30 +- .../src/vs/workbench/common/configuration.ts | 4 +- .../src/vs/workbench/common/contextkeys.ts | 1 + .../src/vs/workbench/common/contributions.ts | 11 +- .../src/vs/workbench/common/editor.ts | 5 +- .../src/vs/workbench/common/theme.ts | 579 +- .../src/vs/workbench/common/views.ts | 4 +- .../browser/accessibilityConfiguration.ts | 279 +- .../accessibility/browser/accessibleView.ts | 306 +- .../browser/accessibleViewActions.ts | 47 +- .../browser/accessibleViewContributions.ts | 13 +- .../accessibleViewKeybindingResolver.ts | 16 +- .../browser/editorAccessibilityHelp.ts | 13 +- .../extensionAccesibilityHelp.contribution.ts | 1 - .../common/accessibilityCommands.ts | 1 + .../accessibilitySignal.contribution.ts | 4 +- ...accessibilitySignalDebuggerContribution.ts | 2 +- .../accessibilitySignals/browser/commands.ts | 29 +- .../editorTextPropertySignalsContribution.ts | 4 +- .../reloadableWorkbenchContribution.ts | 46 - .../accountsEntitlements.contribution.ts | 35 +- ...manageTrustedExtensionsForAccountAction.ts | 152 +- .../bulkEdit/browser/preview/bulkEditPane.ts | 81 +- .../browser/preview/bulkEditPreview.ts | 2 +- .../bulkEdit/browser/preview/bulkEditTree.ts | 10 +- .../test/browser/bulkCellEdits.test.ts | 2 +- .../test/browser/bulkEditPreview.test.ts | 2 +- .../browser/actions/chatAccessibilityHelp.ts | 46 +- .../chat/browser/actions/chatActions.ts | 163 +- .../contrib/chat/browser/actions/chatClear.ts | 14 +- .../chat/browser/actions/chatClearActions.ts | 2 +- .../browser/actions/chatCodeblockActions.ts | 465 +- .../browser/actions/chatContextActions.ts | 154 +- .../browser/actions/chatDeveloperActions.ts | 35 + .../browser/actions/chatExecuteActions.ts | 1 + .../chat/browser/actions/chatImportExport.ts | 3 +- .../chat/browser/actions/chatMoveActions.ts | 18 +- .../chat/browser/actions/chatTitleActions.ts | 57 +- .../contrib/chat/browser/chat.contribution.ts | 44 +- .../vs/workbench/contrib/chat/browser/chat.ts | 34 +- .../chat/browser/chatAccessibilityProvider.ts | 8 +- .../chat/browser/chatAccessibilityService.ts | 15 +- .../contrib/chat/browser/chatAgentHover.ts | 32 +- .../chatAttachmentsContentPart.ts | 114 + .../chatCodeCitationContentPart.ts | 61 + .../chatContentParts/chatCollections.ts | 43 + .../chatCommandContentPart.ts | 46 + .../chatConfirmationContentPart.ts | 71 + .../chatConfirmationWidget.ts | 0 .../chatContentParts/chatContentParts.ts | 26 + .../chatMarkdownContentPart.ts | 176 + .../chatProgressContentPart.ts | 65 + .../chatReferencesContentPart.ts | 339 + .../chatContentParts/chatTaskContentPart.ts | 54 + .../chatTextEditContentPart.ts | 255 + .../chatContentParts/chatTreeContentPart.ts | 225 + .../chatWarningContentPart.ts | 53 + .../media/chatConfirmationWidget.css | 5 +- .../contrib/chat/browser/chatEditor.ts | 20 +- .../contrib/chat/browser/chatFollowups.ts | 17 +- .../contrib/chat/browser/chatInputPart.ts | 256 +- .../contrib/chat/browser/chatListRenderer.ts | 1662 +- .../chatMarkdownDecorationsRenderer.ts | 60 +- .../chat/browser/chatMarkdownRenderer.ts | 31 +- .../browser/chatParticipantContributions.ts | 235 +- .../contrib/chat/browser/chatQuick.ts | 2 +- .../browser/chatResponseAccessibleView.ts | 150 +- .../contrib/chat/browser/chatVariables.ts | 36 +- .../contrib/chat/browser/chatViewPane.ts | 33 +- .../contrib/chat/browser/chatWidget.ts | 172 +- .../contrib/chat/browser/codeBlockPart.css | 16 +- .../contrib/chat/browser/codeBlockPart.ts | 192 +- .../browser/contrib/chatContextAttachments.ts | 23 +- .../browser/contrib/chatDynamicVariables.ts | 33 +- .../browser/contrib/chatInputCompletions.ts | 104 +- .../browser/contrib/chatInputEditorContrib.ts | 54 +- .../browser/contrib/chatInputEditorHover.ts | 100 + .../browser/contrib/editorHoverWrapper.ts | 52 + .../contrib/media/editorHoverWrapper.css} | 4 +- .../contrib/chat/browser/media/chat.css | 146 +- .../chat/browser/media/chatAgentHover.css | 10 +- .../contrib/chat/common/annotations.ts | 2 +- .../contrib/chat/common/chatAgents.ts | 141 +- .../contrib/chat/common/chatColors.ts | 2 +- .../contrib/chat/common/chatContextKeys.ts | 6 +- .../contrib/chat/common/chatModel.ts | 324 +- .../contrib/chat/common/chatParserTypes.ts | 45 +- .../common/chatParticipantContribTypes.ts | 3 + .../contrib/chat/common/chatRequestParser.ts | 35 +- .../contrib/chat/common/chatService.ts | 71 +- .../contrib/chat/common/chatServiceImpl.ts | 489 +- .../chat/common/chatServiceTelemetry.ts | 169 + .../contrib/chat/common/chatSlashCommands.ts | 17 +- .../contrib/chat/common/chatVariables.ts | 3 +- .../contrib/chat/common/chatViewModel.ts | 127 +- .../contrib/chat/common/chatWordCounter.ts | 48 +- .../chat/common/codeBlockModelCollection.ts | 30 + .../chat/common/languageModelToolsService.ts | 178 + .../contrib/chat/common/languageModels.ts | 47 +- .../tools/languageModelToolsContribution.ts | 169 + .../contrib/chat/common/voiceChatService.ts | 8 +- .../actions/voiceChatActions.ts | 37 +- .../ChatMarkdownRenderer_invalid_HTML.0.snap | 2 +- ...nderer_invalid_HTML_with_attributes.0.snap | 2 +- .../ChatMarkdownRenderer_remote_images.0.snap | 2 +- ...kdownRenderer_self-closing_elements.0.snap | 2 +- ..._supportHtml_with_one-line_markdown.0.snap | 1 + ..._supportHtml_with_one-line_markdown.1.snap | 4 + .../test/browser/chatMarkdownRenderer.test.ts | 12 + .../chat/test/browser/chatVariables.test.ts | 7 +- .../browser/languageModelToolsService.test.ts | 115 + ..._agent_and_subcommand_after_newline.0.snap | 3 +- ..._subcommand_with_leading_whitespace.0.snap | 3 +- ...uestParser_agent_with_question_mark.0.snap | 3 +- ...er_agent_with_subcommand_after_text.0.snap | 3 +- ...hatRequestParser_agents__subCommand.0.snap | 3 +- ..._agents_and_variables_and_multiline.0.snap | 3 +- ..._and_variables_and_multiline__part2.0.snap | 3 +- .../ChatService_can_deserialize.0.snap | 20 +- .../ChatService_can_serialize.1.snap | 77 +- .../ChatService_sendRequest_fails.0.snap | 10 +- .../Response_inline_reference.0.snap | 10 +- .../chat/test/common/chatAgents.test.ts | 3 +- .../chat/test/common/chatModel.test.ts | 114 +- .../test/common/chatRequestParser.test.ts | 5 +- .../chat/test/common/chatService.test.ts | 79 +- .../chat/test/common/chatWordCounter.test.ts | 75 +- .../chat/test/common/languageModels.test.ts | 61 +- .../chat/test/common/mockChatService.ts | 4 + .../common/mockLanguageModelToolsService.ts | 43 + .../chat/test/common/voiceChatService.test.ts | 28 +- .../electron-sandbox/voiceChatActions.test.ts | 2 +- .../browser/codeActions.contribution.ts | 5 +- .../browser/codeActionsContribution.ts | 99 +- .../browser/diffEditorAccessibilityHelp.ts | 23 +- .../codeEditor/browser/diffEditorHelper.ts | 2 +- .../emptyTextEditorHint.ts | 76 +- .../browser/find/simpleFindWidget.ts | 4 +- .../inspectEditorTokens.ts | 77 +- .../quickaccess/gotoSymbolQuickAccess.ts | 4 +- .../codeEditor/browser/saveParticipants.ts | 71 +- .../codeEditor/browser/simpleEditorOptions.ts | 35 + .../suggestEnabledInput.ts | 60 +- .../browser/toggleMultiCursorModifier.ts | 16 +- .../codeEditor/browser/toggleWordWrap.ts | 16 +- .../browser/workbenchEditorWorkerService.ts | 25 + .../test/browser/saveParticipant.test.ts | 2 +- .../codeEditor/test/node/autoindent.test.ts | 5 +- .../test/node/language-configuration.json | 250 + .../contrib/comments/browser/commentColors.ts | 6 +- .../comments/browser/commentGlyphWidget.ts | 6 +- .../contrib/comments/browser/commentNode.ts | 2 +- .../contrib/comments/browser/commentReply.ts | 35 +- .../comments/browser/commentThreadBody.ts | 24 +- .../comments/browser/commentThreadHeader.ts | 19 +- .../comments/browser/commentThreadWidget.ts | 20 +- .../browser/commentThreadZoneWidget.ts | 81 +- .../comments/browser/commentsAccessibility.ts | 19 +- .../browser/commentsAccessibleView.ts | 103 +- .../comments/browser/commentsController.ts | 16 +- .../contrib/comments/browser/commentsModel.ts | 18 +- .../comments/browser/commentsTreeViewer.ts | 27 +- .../contrib/comments/browser/commentsView.ts | 76 +- .../comments/browser/commentsViewActions.ts | 86 +- .../contrib/comments/browser/media/panel.css | 5 +- .../contrib/comments/browser/media/review.css | 5 - .../comments/browser/simpleCommentEditor.ts | 11 +- .../contrib/comments/browser/timestamp.ts | 6 +- .../contrib/comments/common/commentModel.ts | 37 + .../test/browser/commentsView.test.ts | 2 +- .../browser/contextmenu.contribution.ts | 12 +- .../customEditor/browser/customEditors.ts | 2 +- .../contrib/debug/browser/baseDebugView.ts | 51 +- .../browser/breakpointEditorContribution.ts | 77 +- .../contrib/debug/browser/breakpointWidget.ts | 27 +- .../contrib/debug/browser/breakpointsView.ts | 52 +- .../browser/callStackEditorContribution.ts | 22 +- .../contrib/debug/browser/callStackView.ts | 19 +- .../contrib/debug/browser/callStackWidget.ts | 738 + .../debug/browser/debug.contribution.ts | 39 +- .../debug/browser/debugActionViewItems.ts | 25 +- .../debug/browser/debugAdapterManager.ts | 52 +- .../contrib/debug/browser/debugColors.ts | 25 +- .../contrib/debug/browser/debugCommands.ts | 21 +- .../browser/debugConfigurationManager.ts | 17 +- .../debug/browser/debugEditorActions.ts | 30 +- .../debug/browser/debugEditorContribution.ts | 78 +- .../contrib/debug/browser/debugHover.ts | 75 +- .../contrib/debug/browser/debugService.ts | 21 +- .../contrib/debug/browser/debugSession.ts | 65 +- .../debug/browser/debugSessionPicker.ts | 2 +- .../debug/browser/debugSettingMigration.ts | 22 + .../contrib/debug/browser/debugTaskRunner.ts | 191 +- .../contrib/debug/browser/debugToolBar.ts | 10 +- .../contrib/debug/browser/disassemblyView.ts | 5 +- .../contrib/debug/browser/exceptionWidget.ts | 6 +- .../contrib/debug/browser/linkDetector.ts | 84 +- .../debug/browser/media/callStackWidget.css | 71 + .../debug/browser/media/debugToolBar.css | 2 +- .../contrib/debug/browser/media/repl.css | 5 +- .../contrib/debug/browser/rawDebugSession.ts | 4 + .../workbench/contrib/debug/browser/repl.ts | 141 +- .../debug/browser/replAccessibilityHelp.ts | 61 + .../debug/browser/replAccessibleView.ts | 129 + .../contrib/debug/browser/replViewer.ts | 121 +- .../browser/runAndDebugAccessibilityHelp.ts | 92 + .../debug/browser/statusbarColorProvider.ts | 18 +- .../contrib/debug/browser/variablesView.ts | 52 +- .../debug/browser/watchExpressionsView.ts | 48 +- .../workbench/contrib/debug/common/debug.ts | 50 +- .../common/debugAccessibilityAnnouncer.ts | 48 + .../contrib/debug/common/debugLifecycle.ts | 9 +- .../contrib/debug/common/debugModel.ts | 56 +- .../contrib/debug/common/debugProtocol.d.ts | 85 +- .../contrib/debug/common/debugUtils.ts | 5 +- .../contrib/debug/common/debugVisualizers.ts | 4 +- .../debug/common/loadedScriptsPicker.ts | 2 +- .../common/replAccessibilityAnnouncer.ts | 36 + .../contrib/debug/common/replModel.ts | 17 +- .../contrib/debug/node/debugAdapter.ts | 15 +- .../debug/test/browser/baseDebugView.test.ts | 140 +- .../debug/test/browser/breakpoints.test.ts | 2 +- .../debug/test/browser/callStack.test.ts | 7 +- .../test/browser/debugANSIHandling.test.ts | 2 +- .../browser/debugConfigurationManager.test.ts | 5 +- .../debug/test/browser/debugHover.test.ts | 2 +- .../debug/test/browser/debugMemory.test.ts | 2 +- .../debug/test/browser/debugSession.test.ts | 2 +- .../debug/test/browser/debugSource.test.ts | 2 +- .../debug/test/browser/debugUtils.test.ts | 5 +- .../debug/test/browser/debugViewModel.test.ts | 2 +- .../debug/test/browser/linkDetector.test.ts | 2 +- .../test/browser/rawDebugSession.test.ts | 2 +- .../contrib/debug/test/browser/repl.test.ts | 2 +- .../debug/test/browser/variablesView.test.ts | 118 + .../contrib/debug/test/browser/watch.test.ts | 2 +- .../test/browser/watchExpressionView.test.ts | 114 + .../test/common/abstractDebugAdapter.test.ts | 2 +- .../debug/test/common/debugModel.test.ts | 2 +- .../contrib/debug/test/common/mockDebug.ts | 7 +- .../contrib/debug/test/node/debugger.test.ts | 11 +- .../test/node/streamDebugAdapter.test.ts | 2 +- .../contrib/debug/test/node/terminals.test.ts | 2 +- .../browser/editSessions.contribution.ts | 33 +- .../browser/editSessionsStorageService.ts | 13 +- .../test/browser/editSessions.test.ts | 2 +- .../emmet/test/browser/emmetAction.test.ts | 2 +- .../encryption.contribution.ts | 4 +- .../abstractRuntimeExtensionsEditor.ts | 6 +- .../extensions/browser/extensionEditor.ts | 57 +- .../browser/extensionFeaturesTab.ts | 6 +- ...ensionRecommendationNotificationService.ts | 12 +- .../browser/extensions.contribution.ts | 254 +- .../extensions/browser/extensionsActions.ts | 454 +- .../extensions/browser/extensionsList.ts | 48 +- .../extensions/browser/extensionsViewlet.ts | 7 +- .../extensions/browser/extensionsViews.ts | 30 +- .../extensions/browser/extensionsWidgets.ts | 71 +- .../browser/extensionsWorkbenchService.ts | 731 +- .../extensions/browser/media/extension.css | 18 +- .../browser/media/extensionActions.css | 15 +- .../browser/media/extensionEditor.css | 1 + .../browser/media/extensionsViewlet.css | 8 +- .../browser/media/extensionsWidgets.css | 3 +- .../browser/workspaceRecommendations.ts | 2 + .../contrib/extensions/common/extensions.ts | 5 +- .../debugExtensionHostAction.ts | 73 +- .../extensions.contribution.ts | 16 +- .../test/common/extensionQuery.test.ts | 2 +- .../test/electron-sandbox/extension.test.ts | 2 +- .../extensionRecommendationsService.test.ts | 2 +- .../extensionsActions.test.ts | 170 +- .../electron-sandbox/extensionsViews.test.ts | 13 +- .../extensionsWorkbenchService.test.ts | 261 +- .../browser/externalTerminal.contribution.ts | 14 +- .../common/externalUriOpenerService.test.ts | 2 +- .../editors/textFileSaveErrorHandler.ts | 8 +- .../contrib/files/browser/explorerService.ts | 6 + .../files/browser/fileActions.contribution.ts | 17 +- .../contrib/files/browser/fileActions.ts | 24 +- .../contrib/files/browser/fileCommands.ts | 67 +- .../contrib/files/browser/fileConstants.ts | 1 + .../contrib/files/browser/fileImportExport.ts | 3 +- .../files/browser/files.contribution.ts | 27 +- .../workbench/contrib/files/browser/files.ts | 37 +- .../files/browser/views/explorerView.ts | 2 +- .../files/browser/views/explorerViewer.ts | 4 + .../files/browser/views/openEditorsView.ts | 82 +- .../workbench/contrib/files/common/files.ts | 3 + .../fileActions.contribution.ts | 2 +- .../files/test/browser/editorAutoSave.test.ts | 2 +- .../browser/explorerFileNestingTrie.test.ts | 2 +- .../files/test/browser/explorerModel.test.ts | 2 +- .../files/test/browser/explorerView.test.ts | 2 +- .../files/test/browser/fileActions.test.ts | 2 +- .../test/browser/fileEditorInput.test.ts | 2 +- .../test/browser/fileOnDiskProvider.test.ts | 2 +- .../browser/textFileEditorTracker.test.ts | 2 +- .../browser/inlineChat.contribution.ts | 69 +- .../browser/inlineChatAccessibilityHelp.ts | 6 +- .../browser/inlineChatAccessibleView.ts | 23 +- .../inlineChat/browser/inlineChatActions.ts | 276 +- .../browser/inlineChatContentWidget.ts | 95 +- .../browser/inlineChatController.ts | 636 +- .../browser/inlineChatCurrentLine.ts | 166 + .../browser/inlineChatFileCreationWidget.ts | 256 - .../inlineChat/browser/inlineChatNotebook.ts | 9 + .../inlineChat/browser/inlineChatSession.ts | 272 +- .../browser/inlineChatSessionService.ts | 24 +- .../browser/inlineChatSessionServiceImpl.ts | 174 +- .../browser/inlineChatStrategies.ts | 350 +- .../inlineChat/browser/inlineChatWidget.ts | 286 +- .../browser/inlineChatZoneWidget.ts | 237 +- .../inlineChat/browser/media/inlineChat.css | 253 +- .../browser/media/inlineChatContentWidget.css | 20 +- .../contrib/inlineChat/common/inlineChat.ts | 186 +- ..._should_be_easier_to_undo_esc__7537.1.snap | 13 + ..._should_be_easier_to_undo_esc__7537.2.snap | 6 + .../test/browser/inlineChatController.test.ts | 332 +- .../test/browser/inlineChatSession.test.ts | 101 +- .../test/browser/inlineChatStrategies.test.ts | 2 +- .../test/browser/testWorkerService.ts | 10 +- .../browser/interactive.contribution.ts | 114 +- .../interactive/browser/interactiveCommon.ts | 4 +- .../interactive/browser/interactiveEditor.ts | 98 +- .../browser/interactiveHistoryService.ts | 30 +- .../browser/replInputHintContentWidget.ts | 156 + .../{issue.ts => baseIssueReporterService.ts} | 261 +- .../issue/browser/issue.contribution.ts | 15 +- .../contrib/issue/browser/issueFormService.ts | 277 +- .../contrib/issue/browser/issueQuickAccess.ts | 15 +- .../issue/browser/issueReporterModel.ts | 5 +- .../issue/browser/issueReporterPage.ts | 4 +- .../issue/browser/issueReporterService.ts | 202 +- .../contrib/issue/browser/issueService.ts | 22 +- .../browser/test/testReporterModel.test.ts | 340 + .../issue/common/issue.contribution.ts | 3 +- .../workbench/contrib/issue/common/issue.ts | 141 +- .../contrib/issue/common/issueReporterUtil.ts | 26 + .../electron-sandbox/issue.contribution.ts | 109 +- .../electron-sandbox/issueMainService.ts | 4 +- .../issueReporter-dev.esm.html | 46 + .../electron-sandbox/issueReporter-dev.html | 3 +- .../electron-sandbox/issueReporter.esm.html | 45 + .../issue/electron-sandbox/issueReporter.html | 2 +- .../issue/electron-sandbox/issueReporter.js | 11 +- .../electron-sandbox/issueReporterMain.ts | 14 +- .../electron-sandbox/issueReporterService.ts | 72 +- .../electron-sandbox/issueReporterService2.ts | 311 +- .../issue/electron-sandbox/issueService.ts | 130 +- .../electron-sandbox/media/issueReporter.css | 5 + .../media/newIssueReporter.css | 470 + .../nativeIssueFormService.ts | 60 + .../electron-sandbox/process.contribution.ts | 96 + .../issue/electron-sandbox/processService.ts | 62 + .../browser}/testReporterModel.test.ts | 6 +- .../browser/languageStatus.contribution.ts | 33 +- .../browser/media/languageStatus.css | 1 + .../contrib/list/browser/list.contribution.ts | 4 + .../list/browser/listResizeColumnAction.ts | 34 + .../browser/tableColumnResizeQuickPick.ts | 56 + .../browser/localHistoryCommands.ts | 106 +- .../common/localizationsActions.ts | 4 +- .../localization.contribution.ts | 2 +- .../contrib/logs/common/logs.contribution.ts | 4 +- .../contrib/logs/common/logsActions.ts | 4 +- .../browser/markdownDocumentRenderer.ts | 150 +- .../browser/markdownSettingRenderer.ts | 43 +- .../browser/markdownSettingRenderer.test.ts | 20 +- .../markers/browser/markersFileDecorations.ts | 2 +- .../markers/browser/markersTreeViewer.ts | 6 +- .../markers/test/browser/markersModel.test.ts | 2 +- .../browser/mergeEditorInputModel.ts | 4 +- .../mergeEditor/browser/view/colors.ts | 18 +- .../browser/view/conflictActions.ts | 4 +- .../mergeEditor/browser/view/editorGutter.ts | 7 +- .../browser/view/editors/codeEditorView.ts | 6 +- .../browser/view/fixedZoneWidget.ts | 1 + .../mergeEditor/browser/view/viewModel.ts | 1 + .../mergeEditor/test/browser/mapping.test.ts | 2 +- .../mergeEditor/test/browser/model.test.ts | 2 +- .../multiDiffEditor/browser/actions.ts | 57 +- .../browser/multiDiffEditor.ts | 10 +- .../browser/multiDiffEditorInput.ts | 136 +- .../browser/multiDiffSourceResolverService.ts | 10 +- .../browser/scmMultiDiffSourceResolver.ts | 6 +- .../executionStatusBarItemController.ts | 16 +- .../contrib/editorHint/emptyCellEditorHint.ts | 7 +- .../editorStatusBar/editorStatusBar.ts | 41 +- .../browser/contrib/find/findFilters.ts | 43 +- .../contrib/find/findMatchDecorationModel.ts | 6 +- .../browser/contrib/find/findModel.ts | 24 +- .../contrib/find/media/notebookFind.css | 4 + .../browser/contrib/find/notebookFind.ts | 13 +- .../contrib/find/notebookFindReplaceWidget.ts | 170 +- .../contrib/find/notebookFindWidget.ts | 9 +- .../multicursor/notebookMulticursor.ts | 754 + .../browser/contrib/navigation/arrow.ts | 18 +- .../notebookVariablesTree.ts | 5 +- .../notebookVariablesView.ts | 5 +- .../contrib/outline/notebookOutline.ts | 412 +- .../saveParticipants/saveParticipants.ts | 2 +- .../browser/contrib/troubleshoot/layout.ts | 4 +- .../notebook/browser/controller/apiActions.ts | 2 +- .../browser/controller/cellOperations.ts | 16 +- .../browser/controller/cellOutputActions.ts | 78 +- .../controller/chat/cellChatActions.ts | 21 +- .../chat/notebook.chat.contribution.ts | 41 +- .../controller/chat/notebookChatContext.ts | 2 + .../controller/chat/notebookChatController.ts | 40 +- .../browser/controller/coreActions.ts | 11 +- .../browser/controller/editActions.ts | 69 +- .../browser/controller/layoutActions.ts | 10 +- .../browser/diff/diffCellEditorOptions.ts | 11 +- .../notebook/browser/diff/diffComponents.ts | 578 +- .../browser/diff/diffElementOutputs.ts | 19 +- .../browser/diff/diffElementViewModel.ts | 167 +- .../notebook/browser/diff/notebookDiff.css | 83 +- .../browser/diff/notebookDiffActions.ts | 302 +- .../browser/diff/notebookDiffEditor.ts | 360 +- .../browser/diff/notebookDiffEditorBrowser.ts | 69 +- .../notebook/browser/diff/notebookDiffList.ts | 98 +- .../browser/diff/notebookDiffOverviewRuler.ts | 12 +- .../browser/diff/notebookDiffViewModel.ts | 545 + .../browser/diff/notebookMultiDiffEditor.ts | 283 + .../diff/notebookMultiDiffEditorInput.ts | 57 + .../notebook/browser/media/notebook.css | 9 + .../browser/media/notebookCellChat.css | 9 - .../notebook/browser/notebook.contribution.ts | 106 +- .../browser/notebookAccessibilityHelp.ts | 49 +- .../browser/notebookAccessibilityProvider.ts | 2 +- .../browser/notebookAccessibleView.ts | 21 +- .../notebook/browser/notebookBrowser.ts | 71 +- .../notebook/browser/notebookEditor.ts | 2 +- .../notebook/browser/notebookEditorWidget.ts | 221 +- .../contrib/notebook/browser/notebookIcons.ts | 1 + .../notebook/browser/notebookOptions.ts | 8 +- .../browser/services/notebookEditorService.ts | 3 +- .../services/notebookEditorServiceImpl.ts | 113 +- .../services/notebookExecutionServiceImpl.ts | 11 +- .../notebookKernelHistoryServiceImpl.ts | 2 +- .../services/notebookKernelServiceImpl.ts | 14 +- .../services/notebookLoggingServiceImpl.ts | 10 +- .../browser/services/notebookServiceImpl.ts | 47 +- .../services/notebookWorkerServiceImpl.ts | 80 +- .../contrib/notebook/browser/view/cellPart.ts | 2 +- .../browser/view/cellParts/cellActionView.ts | 6 +- .../browser/view/cellParts/cellComments.ts | 112 +- .../browser/view/cellParts/cellDnd.ts | 2 +- .../browser/view/cellParts/cellExecution.ts | 44 +- .../browser/view/cellParts/cellOutput.ts | 78 +- .../browser/view/cellParts/cellStatusPart.ts | 23 +- .../browser/view/cellParts/cellToolbars.ts | 34 +- .../browser/view/cellParts/codeCell.ts | 70 +- .../view/cellParts/codeCellRunToolbar.ts | 15 +- .../browser/view/cellParts/markupCell.ts | 2 +- .../browser/view/notebookCellEditorPool.ts | 136 + .../notebook/browser/view/notebookCellList.ts | 3 - .../browser/view/notebookCellListView.ts | 2 +- .../view/renderers/backLayerWebView.ts | 12 +- .../browser/view/renderers/cellRenderer.ts | 16 +- .../browser/view/renderers/webviewPreloads.ts | 42 +- .../browser/viewModel/baseCellViewModel.ts | 75 +- .../browser/viewModel/cellOutputViewModel.ts | 17 + .../browser/viewModel/codeCellViewModel.ts | 59 +- .../browser/viewModel/markupCellViewModel.ts | 103 +- .../viewModel/notebookOutlineDataSource.ts | 222 + .../notebookOutlineDataSourceFactory.ts | 38 + .../viewModel/notebookOutlineEntryFactory.ts | 3 +- .../viewModel/notebookOutlineProvider.ts | 316 - .../notebookOutlineProviderFactory.ts | 39 - .../viewModel/notebookViewModelImpl.ts | 35 +- .../viewParts/notebookEditorStickyScroll.ts | 42 +- .../viewParts/notebookEditorToolbar.ts | 2 +- .../viewParts/notebookHorizontalTracker.ts | 64 + .../notebookKernelQuickPickStrategy.ts | 44 +- .../viewParts/notebookTopCellToolbar.ts | 8 +- .../common/model/notebookCellTextModel.ts | 5 + .../common/model/notebookTextModel.ts | 47 +- .../contrib/notebook/common/notebookCommon.ts | 55 +- .../notebook/common/notebookContextKeys.ts | 8 +- .../notebook/common/notebookEditorInput.ts | 88 +- .../notebook/common/notebookEditorModel.ts | 44 +- .../notebookEditorModelResolverService.ts | 10 +- .../notebookEditorModelResolverServiceImpl.ts | 110 +- .../notebook/common/notebookKernelService.ts | 2 +- .../notebook/common/notebookLoggingService.ts | 2 + .../notebook/common/notebookProvider.ts | 3 - .../notebook/common/notebookService.ts | 5 +- .../services/notebookSimpleWorker.esm.ts | 9 + .../common/services/notebookSimpleWorker.ts | 19 +- .../NotebookEditorWidgetService.test.ts | 161 + .../test/browser/cellDecorations.test.ts | 2 +- .../notebook/test/browser/cellDnd.test.ts | 2 +- .../test/browser/cellOperations.test.ts | 6 +- .../notebook/test/browser/cellOutput.test.ts | 130 + ...contributedStatusBarItemController.test.ts | 2 +- .../contrib/executionStatusBarItem.test.ts | 2 +- .../test/browser/contrib/find.test.ts | 2 +- .../browser/contrib/layoutActions.test.ts | 2 +- .../contrib/notebookCellDiagnostics.test.ts | 5 +- .../browser/contrib/notebookClipboard.test.ts | 2 +- .../browser/contrib/notebookOutline.test.ts | 7 +- .../notebookOutlineViewProviders.test.ts | 63 +- .../browser/contrib/notebookSymbols.test.ts | 13 +- .../browser/contrib/notebookUndoRedo.test.ts | 2 +- .../browser/contrib/outputCopyTests.test.ts | 2 +- .../test/browser/notebookBrowser.test.ts | 2 +- .../test/browser/notebookCellAnchor.test.ts | 2 +- .../test/browser/notebookCellList.test.ts | 2 +- .../test/browser/notebookCommon.test.ts | 2 +- .../test/browser/notebookDiff.test.ts | 526 +- .../test/browser/notebookEditor.test.ts | 2 +- .../test/browser/notebookEditorModel.test.ts | 48 +- .../browser/notebookExecutionService.test.ts | 37 +- .../notebookExecutionStateService.test.ts | 63 +- .../test/browser/notebookFolding.test.ts | 2 +- .../browser/notebookKernelHistory.test.ts | 26 +- .../browser/notebookKernelService.test.ts | 26 +- .../notebookRendererMessagingService.test.ts | 2 +- .../test/browser/notebookSelection.test.ts | 2 +- .../test/browser/notebookServiceImpl.test.ts | 4 +- .../test/browser/notebookStickyScroll.test.ts | 13 +- .../test/browser/notebookTextModel.test.ts | 110 +- .../notebookVariablesDataSource.test.ts | 2 +- .../test/browser/notebookViewModel.test.ts | 4 +- .../test/browser/notebookViewZones.test.ts | 2 +- .../browser/notebookWorkbenchToolbar.test.ts | 2 +- .../test/browser/testNotebookEditor.ts | 33 +- .../outline/browser/outline.contribution.ts | 6 +- .../output/browser/outputLinkProvider.ts | 63 +- .../contrib/output/browser/outputServices.ts | 9 +- .../contrib/output/browser/outputView.ts | 4 +- .../output/common/outputLinkComputer.esm.ts | 9 + .../output/common/outputLinkComputer.ts | 41 +- .../test/browser/outputLinkProvider.test.ts | 2 +- .../performance/browser/perfviewEditor.ts | 8 +- .../electron-sandbox/startupTimings.ts | 10 +- .../preferences/browser/keybindingsEditor.ts | 22 +- .../browser/media/settingsEditor2.css | 16 +- .../browser/preferences.contribution.ts | 1 + .../browser/preferencesRenderers.ts | 1 + .../preferences/browser/preferencesSearch.ts | 17 +- .../preferences/browser/preferencesWidgets.ts | 10 +- .../preferences/browser/settingsEditor2.ts | 256 +- .../settingsEditorSettingIndicators.ts | 50 +- .../preferences/browser/settingsLayout.ts | 5 + .../preferences/browser/settingsTree.ts | 443 +- .../preferences/browser/settingsTreeModels.ts | 29 +- .../preferences/browser/settingsWidgets.ts | 236 +- .../contrib/preferences/browser/tocTree.ts | 2 +- .../common/preferencesContribution.ts | 70 +- .../common/settingsEditorColorRegistry.ts | 39 +- .../common/settingsFilesystemProvider.ts | 98 + .../keybindingsEditorContribution.test.ts | 2 +- .../test/browser/settingsTreeModels.test.ts | 2 +- .../test/common/smartSnippetInserter.test.ts | 2 +- .../browser/commandsQuickAccess.ts | 7 +- .../browser/relauncher.contribution.ts | 13 +- .../remote/browser/media/tunnelView.css | 5 + .../contrib/remote/browser/remote.ts | 10 +- .../contrib/remote/browser/remoteIndicator.ts | 20 +- .../contrib/remote/browser/tunnelView.ts | 16 +- .../remoteTunnel.contribution.ts | 15 +- .../browser/interactiveEditor.css | 21 + .../browser/media/interactive.css | 36 + .../replNotebook/browser/repl.contribution.ts | 339 + .../replNotebook/browser/replEditor.ts | 710 + .../replNotebook/browser/replEditorInput.ts | 150 + .../workbench/contrib/scm/browser/activity.ts | 407 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 46 +- .../contrib/scm/browser/media/scm.css | 144 +- .../vs/workbench/contrib/scm/browser/menus.ts | 22 +- .../contrib/scm/browser/scm.contribution.ts | 103 +- .../contrib/scm/browser/scmHistory.ts | 310 + .../contrib/scm/browser/scmHistoryViewPane.ts | 1037 + .../scm/browser/scmRepositoriesViewPane.ts | 5 +- .../scm/browser/scmRepositoryRenderer.ts | 43 +- .../contrib/scm/browser/scmViewPane.ts | 1022 +- .../scm/browser/scmViewPaneContainer.ts | 7 +- .../vs/workbench/contrib/scm/browser/util.ts | 14 +- .../contrib/scm/browser/workingSet.ts | 90 +- .../workbench/contrib/scm/common/history.ts | 59 +- .../vs/workbench/contrib/scm/common/scm.ts | 15 +- .../contrib/scm/common/scmService.ts | 51 +- .../scm/test/browser/scmHistory.test.ts | 592 + .../search/browser/anythingQuickAccess.ts | 13 +- .../notebookSearch/notebookSearchService.ts | 15 +- .../notebookSearch/searchNotebookHelpers.ts | 2 +- .../quickTextSearch/textSearchQuickAccess.ts | 14 +- .../search/browser/search.contribution.ts | 18 +- .../search/browser/searchActionsFind.ts | 12 +- .../contrib/search/browser/searchModel.ts | 40 +- .../search/browser/searchResultsView.ts | 10 +- .../contrib/search/browser/searchView.ts | 54 +- .../contrib/search/browser/searchWidget.ts | 33 +- .../search/test/browser/searchActions.test.ts | 2 +- .../search/test/browser/searchModel.test.ts | 3 +- .../browser/searchNotebookHelpers.test.ts | 36 +- .../search/test/browser/searchResult.test.ts | 2 +- .../search/test/browser/searchViewlet.test.ts | 33 +- .../search/test/common/cacheState.test.ts | 2 +- .../search/test/common/extractRange.test.ts | 3 +- .../searchEditor/browser/searchEditor.ts | 27 +- .../browser/searchEditorActions.ts | 2 +- .../searchEditor/browser/searchEditorInput.ts | 2 +- .../browser/commands/configureSnippets.ts | 10 +- .../contrib/snippets/browser/snippetPicker.ts | 10 +- .../snippets/test/browser/snippetFile.test.ts | 2 +- .../test/browser/snippetsRegistry.test.ts | 2 +- .../test/browser/snippetsRewrite.test.ts | 2 +- .../test/browser/snippetsService.test.ts | 2 +- .../speech/test/common/speechService.test.ts | 2 +- .../contrib/splash/browser/partsSplash.ts | 4 +- .../surveys/browser/ces.contribution.ts | 161 - .../contrib/tags/common/javaWorkspaceTags.ts | 19 + .../electron-sandbox/workspaceTagsService.ts | 206 +- .../tags/test/node/workspaceTags.test.ts | 2 +- .../tasks/browser/abstractTaskService.ts | 361 +- .../tasks/browser/task.contribution.ts | 31 +- .../contrib/tasks/browser/taskQuickPick.ts | 43 +- .../tasks/browser/terminalTaskSystem.ts | 8 +- .../contrib/tasks/common/problemMatcher.ts | 416 +- .../contrib/tasks/common/taskConfiguration.ts | 1 + .../contrib/tasks/common/taskService.ts | 7 + .../tasks/test/common/problemMatcher.test.ts | 2 +- .../test/common/taskConfiguration.test.ts | 2 +- .../browser/telemetry.contribution.ts | 15 +- .../browser/environmentVariableInfo.ts | 4 +- .../browser/media/CodeTabExpansion.psm1 | 62 + .../browser/media/GitTabExpansion.psm1 | 666 + .../terminal/browser/media/cgmanifest.json | 48 + .../fish/vendor_conf.d/shellIntegration.fish | 2 +- .../terminal/browser/media/scrollbar.css | 45 - .../browser/media/shellIntegration-bash.sh | 46 +- .../media/shellIntegration-profile.zsh | 14 +- .../browser/media/shellIntegration-rc.zsh | 2 +- .../browser/media/shellIntegration.ps1 | 293 +- .../terminal/browser/media/terminal.css | 55 +- .../contrib/terminal/browser/media/xterm.css | 96 +- .../terminal/browser/terminal.contribution.ts | 17 +- .../contrib/terminal/browser/terminal.ts | 29 +- .../terminal/browser/terminalActions.ts | 4 +- .../browser/terminalConfigurationService.ts | 8 +- .../terminal/browser/terminalEditor.ts | 57 +- .../terminal/browser/terminalEditorService.ts | 4 + .../contrib/terminal/browser/terminalGroup.ts | 45 +- .../terminal/browser/terminalGroupService.ts | 94 +- .../contrib/terminal/browser/terminalIcons.ts | 2 +- .../terminal/browser/terminalInstance.ts | 245 +- .../browser/terminalProcessManager.ts | 6 +- .../browser/terminalProfileResolverService.ts | 11 +- .../browser/terminalProfileService.ts | 2 +- .../browser/terminalResizeDebouncer.ts | 84 + .../browser/terminalRunRecentQuickPick.ts | 48 +- .../terminal/browser/terminalService.ts | 80 +- .../terminal/browser/terminalTabbedView.ts | 62 +- .../terminal/browser/terminalTabsList.ts | 35 +- .../contrib/terminal/browser/terminalView.ts | 20 +- .../terminal/browser/widgets/widgetManager.ts | 4 +- .../terminal/browser/xterm/decorationAddon.ts | 6 +- .../browser/xterm/lineDataEventAddon.ts | 5 +- .../browser/xterm/markNavigationAddon.ts | 2 +- .../terminal/browser/xterm/xtermTerminal.ts | 54 +- .../contrib/terminal/common/history.ts | 16 +- .../contrib/terminal/common/terminal.ts | 7 +- .../terminal/common/terminalColorRegistry.ts | 50 +- .../terminal/common/terminalConfiguration.ts | 12 +- .../terminal/common/terminalContextKey.ts | 4 +- .../contrib/terminal/terminal.all.ts | 2 +- .../terminalConfigurationService.test.ts | 7 +- .../test/browser/terminalInstance.test.ts | 2 +- .../test/browser/xterm/xtermTerminal.test.ts | 9 + .../terminal/test/common/history.test.ts | 13 +- .../test/common/terminalColorRegistry.test.ts | 2 +- .../test/common/terminalDataBuffering.test.ts | 2 +- .../test/common/terminalEnvironment.test.ts | 14 +- .../terminal.accessibility.contribution.ts | 9 +- .../browser/terminalAccessibilityHelp.ts | 35 +- .../terminalAccessibleBufferProvider.ts | 10 +- .../test/browser/bufferContentTracker.test.ts | 2 +- .../browser/media/terminalInitialHint.css | 3 +- .../browser/terminal.chat.contribution.ts | 4 + .../terminal.initialHint.contribution.ts | 67 +- .../chat/browser/terminalChat.ts | 14 +- .../browser/terminalChatAccessibilityHelp.ts | 19 +- .../browser/terminalChatAccessibleView.ts | 17 +- .../chat/browser/terminalChatActions.ts | 62 +- .../chat/browser/terminalChatController.ts | 340 +- .../chat/browser/terminalChatEnabler.ts | 35 + .../chat/browser/terminalChatWidget.ts | 80 +- .../terminalInitialHintConfiguration.ts | 3 +- .../test/browser/terminalInitialHint.test.ts | 12 +- .../terminal.commandGuide.contribution.ts | 101 + .../terminalCommandGuideConfiguration.ts | 27 + .../developer/browser/media/developer.css | 4 +- .../terminal.developer.contribution.ts | 2 +- .../terminal.highlight.contribution.ts | 59 - .../browser/terminalLinkDetectorAdapter.ts | 3 +- .../links/browser/terminalLinkOpeners.ts | 8 +- .../links/browser/terminalLinkQuickpick.ts | 5 +- .../test/browser/terminalLinkHelpers.test.ts | 2 +- .../test/browser/quickFixAddon.test.ts | 36 +- .../browser/media/stickyScroll.css | 5 +- .../terminalStickyScrollColorRegistry.ts | 19 +- .../browser/terminalStickyScrollOverlay.ts | 70 +- .../browser/terminal.suggest.contribution.ts | 147 +- .../suggest/browser/terminalSuggestAddon.ts | 485 +- .../suggest/common/terminal.suggest.ts | 4 + .../common/terminalSuggestConfiguration.ts | 49 +- .../windows11_pwsh_getcontent_file.ts | 2 +- .../windows11_pwsh_input_ls_complete_ls.ts | 4 - .../windows11_pwsh_namespace_completion.ts | 18 +- .../terminalSuggestAddon.integrationTest.ts | 69 +- .../test/browser/testRawPwshCompletions.ts | 51571 ++++++++++++++++ .../test/browser/terminalTypeAhead.test.ts | 2 +- .../browser/codeCoverageDecorations.ts | 145 +- .../explorerProjections/treeProjection.ts | 6 +- .../contrib/testing/browser/icons.ts | 6 +- .../contrib/testing/browser/media/testing.css | 17 +- .../testing/browser/testCoverageBars.ts | 15 +- .../testing/browser/testCoverageView.ts | 8 +- .../testing/browser/testExplorerActions.ts | 384 +- .../testResultsView/testMessageStack.ts | 49 + .../testResultsView/testResultsOutput.ts | 573 + .../testResultsView/testResultsSubject.ts | 130 + .../testResultsView/testResultsTree.ts | 900 + .../testResultsViewContent.css} | 0 .../testResultsView/testResultsViewContent.ts | 547 + .../testing/browser/testing.contribution.ts | 3 +- .../testing/browser/testingConfigurationUi.ts | 29 +- .../testing/browser/testingDecorations.ts | 179 +- .../testing/browser/testingExplorerFilter.ts | 3 +- .../testing/browser/testingExplorerView.ts | 116 +- .../testing/browser/testingOutputPeek.ts | 1959 +- .../contrib/testing/browser/theme.ts | 158 +- .../contrib/testing/common/configuration.ts | 2 +- .../contrib/testing/common/constants.ts | 4 + .../contrib/testing/common/testCoverage.ts | 130 +- .../testing/common/testCoverageService.ts | 15 +- .../contrib/testing/common/testExclusions.ts | 4 +- .../testing/common/testExplorerFilterState.ts | 2 + .../contrib/testing/common/testId.ts | 2 +- .../testing/common/testProfileService.ts | 32 +- .../contrib/testing/common/testResult.ts | 7 +- .../testing/common/testResultService.ts | 4 +- .../contrib/testing/common/testService.ts | 94 +- .../contrib/testing/common/testServiceImpl.ts | 140 +- .../contrib/testing/common/testTypes.ts | 56 +- .../testing/common/testingContextKeys.ts | 9 + .../common/testingContinuousRunService.ts | 2 +- .../testing/common/testingDecorations.ts | 5 + .../nameProjection.test.ts | 3 +- .../treeProjection.test.ts | 51 +- .../testing/test/common/testCoverage.test.ts | 58 +- .../common/testExplorerFilterState.test.ts | 2 +- .../test/common/testProfileService.test.ts | 2 +- .../test/common/testResultService.test.ts | 8 +- .../test/common/testResultStorage.test.ts | 7 +- .../testing/test/common/testService.test.ts | 108 + .../contrib/testing/test/common/testStubs.ts | 20 + .../testing/test/common/testingUri.test.ts | 2 +- .../themes/browser/themes.contribution.ts | 58 +- .../test/node/colorRegistry.releaseTest.ts | 7 +- .../contrib/timeline/browser/timelinePane.ts | 23 +- .../update/browser/releaseNotesEditor.ts | 10 +- .../update/browser/update.contribution.ts | 1 - .../url/test/browser/trustedDomains.test.ts | 2 +- .../browser/media/userDataProfilesEditor.css | 277 +- .../browser/userDataProfile.contribution.ts | 7 +- .../browser/userDataProfile.ts | 477 +- .../browser/userDataProfileActions.ts | 121 +- .../browser/userDataProfilePreview.ts | 28 - .../browser/userDataProfilesEditor.ts | 1641 +- .../browser/userDataProfilesEditorModel.ts | 838 +- .../userDataProfile/common/userDataProfile.ts | 5 +- .../userDataSync/browser/userDataSync.ts | 2 +- .../webview/browser/pre/index-no-csp.html | 12 +- .../contrib/webview/browser/pre/index.html | 54 +- .../webview/browser/pre/service-worker.js | 3 +- .../contrib/webview/browser/themeing.ts | 4 +- .../contrib/webview/browser/webviewElement.ts | 19 +- .../webview/browser/webviewMessages.d.ts | 5 + .../browser/webviewWindowDragMonitor.ts | 26 +- .../browser/webviewIconManager.ts | 19 +- .../browser/gettingStarted.contribution.ts | 10 +- .../browser/gettingStarted.ts | 126 +- .../browser/gettingStartedColors.ts | 6 +- .../browser/gettingStartedDetailsRenderer.ts | 21 +- .../browser/gettingStartedList.ts | 2 +- .../browser/gettingStartedService.ts | 8 +- .../browser/media/gettingStarted.css | 11 +- .../common/gettingStartedContent.ts | 131 +- .../common/media/empty.ts} | 2 +- .../gettingStartedMarkdownRenderer.test.ts | 2 +- .../common/newFile.contribution.ts | 2 +- .../browser/editor/editorWalkThrough.ts | 5 +- .../browser/media/walkThroughPart.css | 2 +- .../browser/walkThroughInput.ts | 10 +- .../browser/walkThroughPart.ts | 4 +- .../common/walkThroughContentProvider.ts | 64 +- .../electron-sandbox/actions/windowActions.ts | 36 +- .../electron-sandbox/desktop.contribution.ts | 18 +- .../electron-sandbox/desktop.main.ts | 2 +- .../parts/titlebar/titlebarPart.ts | 10 +- .../vs/workbench/electron-sandbox/window.ts | 113 +- .../actions/common/menusExtensionPoint.ts | 43 +- .../aiRelatedInformationService.test.ts | 2 +- .../assignment/common/assignmentService.ts | 2 +- .../test/common/nullAssignmentService.ts | 18 + .../authenticationExtensionsService.ts | 92 +- .../browser/authenticationService.ts | 22 +- .../authentication/common/authentication.ts | 28 +- .../browser/authenticationService.test.ts | 2 +- .../browser/auxiliaryWindowService.ts | 40 +- .../clipboard/browser/clipboardService.ts | 16 +- .../test/common/commandService.test.ts | 2 +- .../browser/configurationService.ts | 18 +- .../common/configurationEditing.ts | 4 +- .../test/browser/configuration.test.ts | 5 +- .../test/browser/configurationEditing.test.ts | 2 +- .../test/browser/configurationService.test.ts | 2 +- .../test/common/configurationModels.test.ts | 2 +- .../baseConfigurationResolverService.ts | 45 +- .../browser/configurationResolverService.ts | 4 +- .../common/configurationResolver.ts | 3 +- .../configurationResolverService.ts | 4 +- .../configurationResolverService.test.ts | 22 +- .../decorations/browser/decorationsService.ts | 10 +- .../test/browser/decorationsService.test.ts | 2 +- .../dialogs/browser/simpleFileDialog.ts | 2 +- .../fileDialogService.test.ts | 8 +- .../editor/browser/editorResolverService.ts | 18 +- .../editor/common/customEditorLabelService.ts | 80 +- .../editor/common/editorGroupsService.ts | 21 +- .../browser/customEditorLabelService.test.ts | 234 + .../test/browser/editorGroupsService.test.ts | 2 +- .../browser/editorResolverService.test.ts | 2 +- .../editor/test/browser/editorService.test.ts | 2 +- .../test/browser/editorsObserver.test.ts | 2 +- .../environment/browser/environmentService.ts | 13 +- .../browser/extensionEnablementService.ts | 8 +- .../browser/webExtensionsScannerService.ts | 15 +- .../common/extensionManagement.ts | 1 + .../extensionManagementChannelClient.ts | 24 +- .../common/extensionManagementService.ts | 134 +- .../remoteExtensionManagementService.ts | 9 +- .../common/webExtensionManagementService.ts | 9 +- .../nativeExtensionManagementService.ts | 4 +- .../remoteExtensionManagementService.ts | 7 +- .../extensionEnablementService.test.ts | 8 +- .../extensions/browser/extensionService.ts | 17 +- .../browser/webWorkerExtensionHost.ts | 23 +- .../common/abstractExtensionService.ts | 206 +- .../extensions/common/extensionHostManager.ts | 13 +- .../common/extensionHostManagers.ts | 1 + .../services/extensions/common/extensions.ts | 20 +- .../common/extensionsApiProposals.ts | 129 - .../common/extensionsProposedApi.ts | 50 +- .../extensions/common/extensionsRegistry.ts | 18 +- .../extensions/common/extensionsUtil.ts | 7 +- .../common/lazyCreateExtensionHostManager.ts | 3 + .../extensions/common/remoteExtensionHost.ts | 17 +- .../services/extensions/common/rpcProtocol.ts | 2 +- .../cachedExtensionScanner.ts | 9 +- .../localProcessExtensionHost.ts | 2 +- .../nativeExtensionService.ts | 14 +- .../test/browser/extensionService.test.ts | 13 +- .../browser/extensionStorageMigration.test.ts | 2 +- .../extensionDescriptionRegistry.test.ts | 5 +- ...extensionManifestPropertiesService.test.ts | 2 +- .../test/common/rpcProtocol.test.ts | 2 +- .../webWorkerExtensionHostIframe.esm.html | 164 + .../worker/webWorkerExtensionHostIframe.html | 57 +- .../common/filesConfigurationService.ts | 14 +- .../test/browser/historyService.test.ts | 2 +- .../host/browser/browserHostService.ts | 21 +- .../workbench/services/host/browser/host.ts | 8 +- .../electron-sandbox/nativeHostService.ts | 12 +- .../browser/keyboardLayoutService.ts | 7 +- .../browser/browserKeyboardMapper.test.ts | 2 +- .../test/browser/keybindingEditing.test.ts | 2 +- .../test/browser/keybindingIO.test.ts | 2 +- .../test/node/keyboardMapperTestUtils.ts | 7 +- .../test/node/macLinuxKeyboardMapper.test.ts | 2 +- .../services/label/test/browser/label.test.ts | 2 +- .../language/common/languageService.ts | 11 +- .../languageDetectionSimpleWorker.esm.ts | 9 + .../browser/languageDetectionSimpleWorker.ts | 49 +- .../languageDetectionWorker.protocol.ts | 27 + .../languageDetectionWorkerServiceImpl.ts | 142 +- .../services/layout/browser/layoutService.ts | 13 +- .../services/lifecycle/common/lifecycle.ts | 42 +- .../electron-sandbox/lifecycleService.ts | 31 +- .../electron-sandbox/lifecycleService.test.ts | 35 +- .../localization/browser/localeService.ts | 78 +- .../electron-sandbox/localeService.ts | 4 +- .../services/model/common/modelService.ts | 8 +- .../common/notebookDocumentService.ts | 15 + .../services/output/common/output.ts | 5 - .../preferences/browser/preferencesService.ts | 154 +- .../preferences/common/preferences.ts | 17 +- .../preferences/common/preferencesModels.ts | 46 +- .../browser/keybindingsEditorModel.test.ts | 2 +- .../test/browser/preferencesService.test.ts | 6 +- .../test/common/preferencesValidation.test.ts | 2 +- .../progress/browser/progressService.ts | 15 +- .../test/browser/progressIndicator.test.ts | 2 +- .../common/abstractRemoteAgentService.ts | 13 + .../remote/common/remoteAgentService.ts | 6 + .../remote/common/remoteExtensionsScanner.ts | 24 +- .../services/remote/common/tunnelModel.ts | 2 + .../electron-sandbox/requestService.ts | 10 +- .../services/search/browser/searchService.ts | 32 +- .../search/common/fileSearchManager.ts | 127 +- .../services/search/common/getFileResults.ts | 26 +- .../common/localFileSearchWorkerTypes.ts | 19 +- .../services/search/common/queryBuilder.ts | 85 +- .../services/search/common/search.ts | 162 +- .../search/common/searchExtConversionTypes.ts | 614 + .../services/search/common/searchExtTypes.ts | 453 +- .../search/common/searchExtTypesInternal.ts | 21 + .../services/search/common/searchHelpers.ts | 14 +- .../services/search/common/searchService.ts | 7 +- .../search/common/textSearchManager.ts | 194 +- .../services/search/node/fileSearch.ts | 24 +- .../services/search/node/rawSearchService.ts | 32 +- .../services/search/node/ripgrepFileSearch.ts | 10 +- .../search/node/ripgrepSearchProvider.ts | 49 +- .../search/node/ripgrepSearchUtils.ts | 26 +- .../search/node/ripgrepTextSearchEngine.ts | 113 +- .../services/search/node/textSearchAdapter.ts | 10 +- .../services/search/node/textSearchManager.ts | 4 +- .../search/test/browser/queryBuilder.test.ts | 61 +- .../search/test/common/ignoreFile.test.ts | 2 +- .../search/test/common/queryBuilder.test.ts | 2 +- .../search/test/common/replace.test.ts | 2 +- .../search/test/common/search.test.ts | 46 +- .../search/test/common/searchHelpers.test.ts | 118 +- .../test/node/fileSearch.integrationTest.ts | 6 +- .../node/rawSearchService.integrationTest.ts | 2 +- .../test/node/ripgrepFileSearch.test.ts | 2 +- .../node/ripgrepTextSearchEngineUtils.test.ts | 227 +- .../test/node/search.integrationTest.ts | 20 +- .../test/node/textSearch.integrationTest.ts | 21 +- .../test/node/textSearchManager.test.ts | 10 +- .../search/worker/localFileSearch.esm.ts | 9 + .../services/search/worker/localFileSearch.ts | 46 +- .../services/statusbar/browser/statusbar.ts | 4 +- .../suggest/browser/media/suggest.css | 3 +- .../suggest/browser/simpleCompletionItem.ts | 27 +- .../suggest/browser/simpleCompletionModel.ts | 76 +- .../suggest/browser/simpleSuggestWidget.ts | 14 +- .../browser/simpleSuggestWidgetRenderer.ts | 22 +- .../test/browser/commonProperties.test.ts | 2 +- .../test/node/commonProperties.test.ts | 2 +- .../textMateWorkerTokenizerController.ts | 15 +- .../threadedBackgroundTokenizerFactory.ts | 59 +- .../textMateTokenizationWorker.worker.esm.ts | 9 + .../textMateTokenizationWorker.worker.ts | 66 +- .../worker/textMateWorkerHost.ts | 22 + .../worker/textMateWorkerTokenizer.ts | 2 + .../textMateTokenizationFeatureImpl.ts | 21 +- .../tokenizationSupportWithLineLimit.ts | 4 +- .../services/textMate/common/TMGrammars.ts | 2 +- .../test/browser/arrayOperation.test.ts | 2 +- .../textfile/browser/textFileService.ts | 7 +- .../services/textfile/common/encoding.ts | 116 +- .../textfile/common/textFileEditorModel.ts | 33 +- .../common/textFileEditorModelManager.ts | 5 +- .../common/textFileSaveParticipant.ts | 50 +- .../services/textfile/common/textfiles.ts | 7 +- .../test/browser/textEditorService.test.ts | 2 +- .../textFileEditorModel.integrationTest.ts | 2 +- .../test/browser/textFileEditorModel.test.ts | 24 +- .../textFileEditorModelManager.test.ts | 2 +- .../test/browser/textFileService.test.ts | 2 +- .../textfile/test/common/fixtures/files.ts | 4 +- .../test/common/textFileService.io.test.ts | 17 +- .../nativeTextFileService.test.ts | 2 +- .../node/encoding/encoding.integrationTest.ts | 2 +- .../test/node/encoding/encoding.test.ts | 30 +- .../encoding/fixtures/some.shiftjis.1.txt | 2 + .../browser/textModelResolverService.test.ts | 2 +- .../themes/browser/fileIconThemeData.ts | 2 +- .../themes/browser/workbenchThemeService.ts | 2 + .../services/themes/common/colorThemeData.ts | 45 +- .../test/node/tokenStyleResolving.test.ts | 2 +- .../services/timer/browser/timerService.ts | 3 +- ...eSitterTokenizationFeature.contribution.ts | 27 + .../browser/treeSitterTokenizationFeature.ts | 241 + .../tunnel/electron-sandbox/tunnelService.ts | 4 +- .../common/untitledTextEditorModel.ts | 2 +- .../untitledTextEditor.integrationTest.ts | 2 +- .../test/browser/untitledTextEditor.test.ts | 2 +- .../common/userActivityService.ts | 22 +- .../test/browser/domActivityTracker.test.ts | 2 +- .../test/common/userActivityService.test.ts | 74 + .../browser/extensionsResource.ts | 16 +- .../userDataProfile/browser/tasksResource.ts | 2 +- .../userDataProfileImportExportService.ts | 1079 +- .../browser/userDataProfileManagement.ts | 9 +- .../browser/userDataProfileStorageService.ts | 2 +- .../userDataProfile/common/userDataProfile.ts | 36 +- .../common/userDataProfileIcons.ts | 2 +- .../browser/userDataSyncWorkbenchService.ts | 2 +- .../userDataSync/common/userDataSync.ts | 4 +- .../userDataSync/common/userDataSyncUtil.ts | 4 +- .../services/views/browser/viewsService.ts | 20 +- .../test/browser/viewContainerModel.test.ts | 2 +- .../browser/viewDescriptorService.test.ts | 2 +- .../common/fileWorkingCopyManager.ts | 6 +- .../common/storedFileWorkingCopy.ts | 37 +- .../common/storedFileWorkingCopyManager.ts | 6 +- .../storedFileWorkingCopySaveParticipant.ts | 51 +- .../common/workingCopyFileService.ts | 6 +- .../browser/fileWorkingCopyManager.test.ts | 4 +- .../test/browser/resourceWorkingCopy.test.ts | 2 +- .../browser/storedFileWorkingCopy.test.ts | 26 +- .../storedFileWorkingCopyManager.test.ts | 5 +- .../browser/untitledFileWorkingCopy.test.ts | 2 +- .../untitledFileWorkingCopyManager.test.ts | 8 +- .../untitledScratchpadWorkingCopy.test.ts | 2 +- .../browser/workingCopyBackupTracker.test.ts | 2 +- .../browser/workingCopyEditorService.test.ts | 2 +- .../browser/workingCopyFileService.test.ts | 2 +- .../test/common/workingCopyService.test.ts | 2 +- .../workingCopyBackupService.test.ts | 6 +- .../workingCopyBackupTracker.test.ts | 2 +- .../workingCopyHistoryService.test.ts | 2 +- .../workingCopyHistoryTracker.test.ts | 2 +- .../abstractWorkspaceEditingService.ts | 7 +- .../workspaceEditingService.ts | 4 +- .../test/browser/workspaces.test.ts | 2 +- .../test/common/workspaceTrust.test.ts | 2 +- .../workbench/test/browser/codeeditor.test.ts | 2 +- .../test/browser/contributions.test.ts | 2 +- .../vs/workbench/test/browser/part.test.ts | 4 +- .../parts/editor/breadcrumbModel.test.ts | 2 +- .../parts/editor/diffEditorInput.test.ts | 2 +- .../test/browser/parts/editor/editor.test.ts | 2 +- .../editor/editorCommandsContext.test.ts | 188 + .../parts/editor/editorDiffModel.test.ts | 2 +- .../parts/editor/editorGroupModel.test.ts | 2 +- .../browser/parts/editor/editorInput.test.ts | 2 +- .../browser/parts/editor/editorModel.test.ts | 2 +- .../browser/parts/editor/editorPane.test.ts | 2 +- .../editor/filteredEditorGroupModel.test.ts | 2 +- .../parts/editor/resourceEditorInput.test.ts | 2 +- .../editor/sideBySideEditorInput.test.ts | 2 +- .../parts/editor/textEditorPane.test.ts | 2 +- .../editor/textResourceEditorInput.test.ts | 2 +- .../parts/statusbar/statusbarModel.test.ts | 2 +- .../test/browser/quickAccess.test.ts | 10 +- .../vs/workbench/test/browser/viewlet.test.ts | 2 +- .../vs/workbench/test/browser/webview.test.ts | 2 +- .../vs/workbench/test/browser/window.test.ts | 2 +- .../test/browser/workbenchTestServices.ts | 71 +- .../vs/workbench/test/common/memento.test.ts | 2 +- .../test/common/notifications.test.ts | 2 +- .../workbench/test/common/resources.test.ts | 2 +- .../src/vs/workbench/test/common/utils.ts | 2 +- .../test/common/workbenchTestServices.ts | 3 +- .../electron-sandbox/resolveExternal.test.ts | 2 +- .../electron-sandbox/workbenchTestServices.ts | 8 +- .../src/vs/workbench/workbench.common.main.ts | 9 +- .../vs/workbench/workbench.desktop.main.ts | 3 + .../vs/workbench/workbench.web.main.nls.js | 6 - patched-vscode/src/vscode-dts/README.md | 2 +- patched-vscode/src/vscode-dts/vscode.d.ts | 483 +- .../vscode.proposed.aiTextSearchProvider.d.ts | 196 - ...code.proposed.aiTextSearchProviderNew.d.ts | 35 + .../vscode.proposed.attributableCoverage.d.ts | 38 +- .../vscode.proposed.authGetSessions.d.ts | 62 - ...ode.proposed.chatParticipantAdditions.d.ts | 107 +- ...scode.proposed.chatParticipantPrivate.d.ts | 31 + .../vscode.proposed.chatProvider.d.ts | 25 +- .../vscode.proposed.chatVariableResolver.d.ts | 26 - .../vscode.proposed.commentReveal.d.ts | 45 + ...e.proposed.commentThreadApplicability.d.ts | 8 + ...osed.contribChatParticipantDetection.d.ts} | 4 +- ...osed.contribDebugCreateConfiguration.d.ts} | 3 +- ...ibSourceControlHistoryItemChangesMenu.d.ts | 7 + ...contribSourceControlHistoryTitleMenu.d.ts} | 4 +- ...de.proposed.contribViewContainerTitle.d.ts | 8 + ...code.proposed.createFileSystemWatcher.d.ts | 2 +- ...scode.proposed.defaultChatParticipant.d.ts | 9 +- .../vscode.proposed.extensionsAny.d.ts | 1 + .../vscode.proposed.fileComments.d.ts | 65 +- .../vscode.proposed.fileSearchProvider.d.ts | 6 + ...vscode.proposed.fileSearchProviderNew.d.ts | 101 + .../vscode.proposed.findFiles2New.d.ts | 105 + .../vscode.proposed.findTextInFilesNew.d.ts | 164 + ...e.proposed.inlineCompletionsAdditions.d.ts | 6 + .../vscode.proposed.interactive.d.ts | 2 - .../vscode-dts/vscode.proposed.lmTools.d.ts | 195 + .../vscode.proposed.mappedEditsProvider.d.ts | 2 +- ...vscode.proposed.notebookReplDocument.d.ts} | 18 +- ...ode.proposed.quickInputButtonLocation.d.ts | 29 + .../vscode.proposed.scmHistoryProvider.d.ts | 19 +- ...ode.proposed.terminalShellIntegration.d.ts | 313 - .../vscode.proposed.testObserver.d.ts | 6 + .../vscode.proposed.testRelatedCode.d.ts | 36 + ...vscode.proposed.textSearchCompleteNew.d.ts | 38 + ...vscode.proposed.textSearchProviderNew.d.ts | 275 + .../test/automation/src/application.ts | 6 + .../test/automation/src/notebook.ts | 1 - .../test/automation/src/playwrightBrowser.ts | 3 +- .../test/automation/src/playwrightDriver.ts | 79 +- .../test/automation/src/profiler.ts | 208 + patched-vscode/test/automation/src/search.ts | 15 + .../test/automation/src/terminal.ts | 1 + patched-vscode/test/automation/tsconfig.json | 1 + .../test/integration/browser/src/index.ts | 8 +- .../test/integration/browser/tsconfig.json | 1 + patched-vscode/test/monaco/monaco.test.ts | 9 +- patched-vscode/test/monaco/tsconfig.json | 1 + patched-vscode/test/package.json | 3 + .../smoke/src/areas/notebook/notebook.test.ts | 24 +- .../smoke/src/areas/search/search.test.ts | 11 + .../src/areas/task/task-quick-pick.test.ts | 8 +- .../test/smoke/src/areas/task/task.test.ts | 6 +- .../areas/terminal/terminal-editors.test.ts | 4 +- .../src/areas/terminal/terminal-input.test.ts | 4 +- .../terminal/terminal-persistence.test.ts | 9 +- .../areas/terminal/terminal-profiles.test.ts | 4 +- .../terminal-shellIntegration.test.ts | 4 +- .../areas/terminal/terminal-splitCwd.test.ts | 4 +- .../terminal/terminal-stickyScroll.test.ts | 4 +- .../src/areas/terminal/terminal-tabs.test.ts | 7 +- .../smoke/src/areas/terminal/terminal.test.ts | 23 +- patched-vscode/test/smoke/src/main.ts | 2 +- patched-vscode/test/smoke/tsconfig.json | 1 + patched-vscode/test/unit/assert-esm.js | 498 + patched-vscode/test/unit/browser/index.esm.js | 421 + patched-vscode/test/unit/browser/index.js | 11 + .../test/unit/browser/renderer.esm.html | 247 + .../test/unit/browser/renderer.html | 1 + .../test/unit/electron/index.esm.js | 358 + patched-vscode/test/unit/electron/index.js | 2 +- patched-vscode/test/unit/electron/preload.js | 92 + .../test/unit/electron/renderer.esm.html | 136 + .../test/unit/electron/renderer.esm.js | 366 + patched-vscode/test/unit/electron/renderer.js | 27 +- patched-vscode/test/unit/node/index.js | 44 +- patched-vscode/test/unit/node/index.mjs | 249 + patched-vscode/yarn.lock | 719 +- 2582 files changed, 139218 insertions(+), 45125 deletions(-) delete mode 100644 patched-vscode/build/azure-pipelines/sdl-scan.yml delete mode 100644 patched-vscode/build/azure-pipelines/upload-configuration.js create mode 100644 patched-vscode/build/azure-pipelines/win32/sdl-scan-win32.yml create mode 100644 patched-vscode/build/buildfile.js create mode 100644 patched-vscode/build/lib/date.js create mode 100644 patched-vscode/build/lib/date.ts create mode 100644 patched-vscode/build/lib/esm.js create mode 100644 patched-vscode/build/lib/esm.ts create mode 100644 patched-vscode/build/lib/inlineMeta.js create mode 100644 patched-vscode/build/lib/inlineMeta.ts create mode 100644 patched-vscode/extensions/css-language-features/client/src/dropOrPaste/dropOrPasteResource.ts create mode 100644 patched-vscode/extensions/css-language-features/client/src/dropOrPaste/shared.ts create mode 100644 patched-vscode/extensions/css-language-features/client/src/dropOrPaste/uriList.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/.npmignore delete mode 100644 patched-vscode/extensions/markdown-language-features/server/.vscode/launch.json delete mode 100644 patched-vscode/extensions/markdown-language-features/server/.vscode/settings.json delete mode 100644 patched-vscode/extensions/markdown-language-features/server/.vscode/tasks.json delete mode 100644 patched-vscode/extensions/markdown-language-features/server/CHANGELOG.md delete mode 100644 patched-vscode/extensions/markdown-language-features/server/README.md delete mode 100644 patched-vscode/extensions/markdown-language-features/server/build/pipeline.yml delete mode 100644 patched-vscode/extensions/markdown-language-features/server/extension-browser.webpack.config.js delete mode 100644 patched-vscode/extensions/markdown-language-features/server/package.json delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/browser/main.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/browser/workerMain.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/config.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/configuration.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/logging.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/node/main.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/node/workerMain.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/protocol.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/server.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/util/dispose.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/util/file.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/util/limiter.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/util/resourceMap.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/src/workspace.ts delete mode 100644 patched-vscode/extensions/markdown-language-features/server/tsconfig.json delete mode 100644 patched-vscode/extensions/markdown-language-features/server/yarn.lock create mode 100644 patched-vscode/extensions/markdown-math/syntaxes/md-math-fence.tmLanguage.json create mode 100644 patched-vscode/extensions/microsoft-authentication/src/browser/authProvider.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/cachePlugin.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/experimentation.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/loggerOptions.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/publicClientCache.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/scopeData.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/telemetryReporter.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/common/test/scopeData.test.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/extensionV1.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/extensionV2.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/node/authProvider.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/node/loopbackTemplate.ts create mode 100644 patched-vscode/extensions/microsoft-authentication/src/node/publicClientCache.ts delete mode 100644 patched-vscode/extensions/sagemaker-extension/.vscodeignore delete mode 100644 patched-vscode/extensions/sagemaker-extension/README.md delete mode 100644 patched-vscode/extensions/sagemaker-extension/extension-browser.webpack.config.js delete mode 100644 patched-vscode/extensions/sagemaker-extension/extension.webpack.config.js delete mode 100644 patched-vscode/extensions/sagemaker-extension/package.json delete mode 100644 patched-vscode/extensions/sagemaker-extension/src/constant.ts delete mode 100644 patched-vscode/extensions/sagemaker-extension/src/extension.ts delete mode 100644 patched-vscode/extensions/sagemaker-extension/src/sessionWarning.ts delete mode 100644 patched-vscode/extensions/sagemaker-extension/tsconfig.json delete mode 100644 patched-vscode/extensions/sagemaker-extension/yarn.lock delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/.vscodeignore delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/CONTRIBUTING.md delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/README.md delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/extension-browser.webpack.config.js delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/extension.webpack.config.js delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/package.json delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/tsconfig.json delete mode 100644 patched-vscode/extensions/sagemaker-idle-extension/yarn.lock delete mode 100644 patched-vscode/extensions/sagemaker-terminal-crash-mitigation/.vscodeignore delete mode 100644 patched-vscode/extensions/sagemaker-terminal-crash-mitigation/README.md delete mode 100644 patched-vscode/extensions/sagemaker-terminal-crash-mitigation/extension-browser.webpack.config.js delete mode 100644 patched-vscode/extensions/sagemaker-terminal-crash-mitigation/extension.webpack.config.js delete mode 100644 patched-vscode/extensions/sagemaker-terminal-crash-mitigation/package.json delete mode 100644 patched-vscode/extensions/sagemaker-terminal-crash-mitigation/src/extension.ts delete mode 100644 patched-vscode/extensions/sagemaker-terminal-crash-mitigation/tsconfig.json delete mode 100644 patched-vscode/extensions/sagemaker-terminal-crash-mitigation/yarn.lock create mode 100644 patched-vscode/extensions/typescript-language-features/src/filesystems/ata.ts create mode 100644 patched-vscode/extensions/typescript-language-features/src/utils/hash.ts create mode 100644 patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts create mode 100644 patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts create mode 100644 patched-vscode/migrate.mjs create mode 100644 patched-vscode/resources/linux/debian/templates.template create mode 100644 patched-vscode/scripts/test-esm.bat create mode 100755 patched-vscode/scripts/test-esm.sh create mode 100644 patched-vscode/scripts/test-integration-esm.bat create mode 100755 patched-vscode/scripts/test-integration-esm.sh rename patched-vscode/{extensions/markdown-language-features/server/extension.webpack.config.js => src/bootstrap-cli.js} (55%) create mode 100644 patched-vscode/src/bootstrap-import.js create mode 100644 patched-vscode/src/bootstrap-meta.js rename patched-vscode/src/{vs/workbench/workbench.desktop.main.nls.js => bootstrap-server.js} (75%) delete mode 100644 patched-vscode/src/bootstrap.js delete mode 100644 patched-vscode/src/buildfile.js create mode 100644 patched-vscode/src/typings/vscode-globals-nls.d.ts rename patched-vscode/src/{vs/workbench/contrib/notebook/common/services/notebookWorkerHost.ts => typings/vscode-globals-ttp.d.ts} (59%) create mode 100644 patched-vscode/src/vs/base/browser/domObservable.ts create mode 100644 patched-vscode/src/vs/base/browser/ui/radio/radio.css create mode 100644 patched-vscode/src/vs/base/browser/ui/radio/radio.ts create mode 100644 patched-vscode/src/vs/base/common/hotReloadHelpers.ts rename patched-vscode/src/vs/base/common/{stripComments.d.ts => jsonc.d.ts} (74%) rename patched-vscode/src/vs/base/common/{stripComments.js => jsonc.js} (69%) create mode 100644 patched-vscode/src/vs/base/common/observableInternal/api.ts create mode 100644 patched-vscode/src/vs/base/common/observableInternal/lazyObservableValue.ts create mode 100644 patched-vscode/src/vs/base/common/worker/simpleWorkerBootstrap.ts delete mode 100644 patched-vscode/src/vs/base/node/languagePacks.d.ts delete mode 100644 patched-vscode/src/vs/base/node/languagePacks.js create mode 100644 patched-vscode/src/vs/base/node/nls.d.ts create mode 100644 patched-vscode/src/vs/base/node/nls.js create mode 100644 patched-vscode/src/vs/base/parts/request/test/browser/request.test.ts rename patched-vscode/src/vs/base/test/common/{stripComments.test.ts => jsonParse.test.ts} (57%) create mode 100644 patched-vscode/src/vs/base/test/common/jsonSchema.test.ts create mode 100644 patched-vscode/src/vs/base/test/common/numbers.test.ts create mode 100644 patched-vscode/src/vs/code/browser/workbench/workbench-dev.esm.html create mode 100644 patched-vscode/src/vs/code/browser/workbench/workbench.esm.html create mode 100644 patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.esm.html create mode 100644 patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer.esm.html create mode 100644 patched-vscode/src/vs/code/electron-sandbox/workbench/workbench-dev.esm.html create mode 100644 patched-vscode/src/vs/code/electron-sandbox/workbench/workbench.esm.html create mode 100644 patched-vscode/src/vs/editor/browser/observableCodeEditor.ts delete mode 100644 patched-vscode/src/vs/editor/browser/observableUtilities.ts create mode 100644 patched-vscode/src/vs/editor/browser/services/treeSitter/treeSitterParserService.ts create mode 100644 patched-vscode/src/vs/editor/common/cursor/cursorTypeEditOperations.ts create mode 100644 patched-vscode/src/vs/editor/common/languages/highlights/typescript.scm create mode 100644 patched-vscode/src/vs/editor/common/model/tokens.ts create mode 100644 patched-vscode/src/vs/editor/common/model/treeSitterTokens.ts create mode 100644 patched-vscode/src/vs/editor/common/services/editorSimpleWorker.esm.ts create mode 100644 patched-vscode/src/vs/editor/common/services/editorWorkerBootstrap.ts create mode 100644 patched-vscode/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts create mode 100644 patched-vscode/src/vs/editor/common/services/textModelSync/textModelSync.protocol.ts create mode 100644 patched-vscode/src/vs/editor/common/services/treeSitterParserService.ts delete mode 100644 patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverController.ts rename patched-vscode/src/vs/editor/contrib/hover/browser/{hoverController.ts => contentHoverController2.ts} (75%) create mode 100644 patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts create mode 100644 patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts create mode 100644 patched-vscode/src/vs/editor/contrib/hover/browser/hoverUtils.ts create mode 100644 patched-vscode/src/vs/editor/contrib/hover/browser/marginHoverController.ts rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => controller}/commandIds.ts (100%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => controller}/commands.ts (97%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => controller}/inlineCompletionContextKeys.ts (98%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => controller}/inlineCompletionsController.ts (54%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => hintsWidget}/hoverParticipant.ts (81%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => hintsWidget}/inlineCompletionsHintsWidget.css (100%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => hintsWidget}/inlineCompletionsHintsWidget.ts (98%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => model}/ghostText.ts (100%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => model}/inlineCompletionsModel.ts (92%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => model}/inlineCompletionsSource.ts (95%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => model}/provideInlineCompletions.ts (95%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ => model}/singleTextEdit.ts (99%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{suggestWidgetInlineCompletionProvider.ts => model/suggestWidgetAdaptor.ts} (91%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ghostText.css => view/ghostTextView.css} (100%) rename patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/{ghostTextWidget.ts => view/ghostTextView.ts} (97%) create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget.css create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget.ts create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdits/browser/commands.ts create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdits/browser/consts.ts create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEdits.contribution.ts create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsController.ts create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsModel.ts create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsWidget.css create mode 100644 patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsWidget.ts create mode 100644 patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderText.contribution.ts create mode 100644 patched-vscode/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts rename patched-vscode/src/vs/editor/{browser/services/webWorker.ts => standalone/browser/standaloneWebWorker.ts} (81%) create mode 100644 patched-vscode/src/vs/editor/test/browser/services/treeSitterParserService.test.ts create mode 100644 patched-vscode/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts create mode 100644 patched-vscode/src/vs/editor/test/common/services/testTreeSitterService.ts create mode 100644 patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/1.txt create mode 100644 patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/2.txt create mode 100644 patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/advanced.expected.diff.json create mode 100644 patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/legacy.expected.diff.json create mode 100644 patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/1.tst create mode 100644 patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/2.tst create mode 100644 patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/advanced.expected.diff.json create mode 100644 patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/legacy.expected.diff.json delete mode 100644 patched-vscode/src/vs/nls.build.ts create mode 100644 patched-vscode/src/vs/nls.messages.ts delete mode 100644 patched-vscode/src/vs/nls.mock.ts create mode 100644 patched-vscode/src/vs/platform/cssDev/node/cssDevService.ts create mode 100644 patched-vscode/src/vs/platform/extensions/common/extensionsApiProposals.ts create mode 100644 patched-vscode/src/vs/platform/extensions/test/common/extensions.test.ts create mode 100644 patched-vscode/src/vs/platform/issue/electron-main/processMainService.ts rename patched-vscode/src/vs/{code => platform/native}/electron-main/auth.ts (54%) create mode 100644 patched-vscode/src/vs/platform/observable/common/wrapInReloadableClass.ts create mode 100644 patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.esm.ts create mode 100644 patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts create mode 100644 patched-vscode/src/vs/workbench/api/common/extHostLanguageModelTools.ts create mode 100644 patched-vscode/src/vs/workbench/api/worker/extensionHostWorker.esm.ts delete mode 100644 patched-vscode/src/vs/workbench/browser/client.ts create mode 100644 patched-vscode/src/vs/workbench/browser/parts/editor/editorCommandsContext.ts delete mode 100644 patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/reloadableWorkbenchContribution.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCodeCitationContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollections.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCommandContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts rename patched-vscode/src/vs/workbench/contrib/chat/browser/{ => chatContentParts}/chatConfirmationWidget.ts (100%) create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatWarningContentPart.ts rename patched-vscode/src/vs/workbench/contrib/chat/browser/{ => chatContentParts}/media/chatConfirmationWidget.css (95%) create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorHover.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/editorHoverWrapper.ts rename patched-vscode/{extensions/microsoft-authentication/src/browser/crypto.ts => src/vs/workbench/contrib/chat/browser/contrib/media/editorHoverWrapper.css} (85%) create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/common/chatServiceTelemetry.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.0.snap create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.1.snap create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/codeEditor/test/node/language-configuration.json create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/browser/callStackWidget.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/browser/debugSettingMigration.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/browser/replAccessibilityHelp.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/browser/replAccessibleView.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/browser/runAndDebugAccessibilityHelp.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/test/browser/variablesView.test.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/debug/test/browser/watchExpressionView.test.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts delete mode 100644 patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.1.snap create mode 100644 patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.2.snap create mode 100644 patched-vscode/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts rename patched-vscode/src/vs/workbench/contrib/issue/browser/{issue.ts => baseIssueReporterService.ts} (82%) create mode 100644 patched-vscode/src/vs/workbench/contrib/issue/browser/test/testReporterModel.test.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/issue/common/issueReporterUtil.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.esm.html create mode 100644 patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.esm.html create mode 100644 patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/media/newIssueReporter.css create mode 100644 patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/processService.ts rename patched-vscode/src/vs/workbench/contrib/issue/{issue => test/browser}/testReporterModel.test.ts (97%) create mode 100644 patched-vscode/src/vs/workbench/contrib/list/browser/listResizeColumnAction.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/list/browser/tableColumnResizeQuickPick.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditorInput.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory.ts delete mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts delete mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.esm.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellOutput.test.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/output/common/outputLinkComputer.esm.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/preferences/common/settingsFilesystemProvider.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/replNotebook/browser/interactiveEditor.css create mode 100644 patched-vscode/src/vs/workbench/contrib/replNotebook/browser/media/interactive.css create mode 100644 patched-vscode/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/scm/browser/scmHistory.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/scm/test/browser/scmHistory.test.ts delete mode 100644 patched-vscode/src/vs/workbench/contrib/surveys/browser/ces.contribution.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/terminal/browser/media/CodeTabExpansion.psm1 create mode 100644 patched-vscode/src/vs/workbench/contrib/terminal/browser/media/GitTabExpansion.psm1 create mode 100644 patched-vscode/src/vs/workbench/contrib/terminal/browser/media/cgmanifest.json delete mode 100644 patched-vscode/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css create mode 100644 patched-vscode/src/vs/workbench/contrib/terminal/browser/terminalResizeDebouncer.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatEnabler.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/terminalContrib/commandGuide/browser/terminal.commandGuide.contribution.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/terminalContrib/commandGuide/common/terminalCommandGuideConfiguration.ts delete mode 100644 patched-vscode/src/vs/workbench/contrib/terminalContrib/highlight/browser/terminal.highlight.contribution.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/testRawPwshCompletions.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsOutput.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts rename patched-vscode/src/vs/workbench/contrib/testing/browser/{testingOutputPeek.css => testResultsView/testResultsViewContent.css} (100%) create mode 100644 patched-vscode/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts create mode 100644 patched-vscode/src/vs/workbench/contrib/testing/test/common/testService.test.ts delete mode 100644 patched-vscode/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts rename patched-vscode/{extensions/microsoft-authentication/src/browser/fetch.ts => src/vs/workbench/contrib/welcomeGettingStarted/common/media/empty.ts} (91%) create mode 100644 patched-vscode/src/vs/workbench/services/assignment/test/common/nullAssignmentService.ts create mode 100644 patched-vscode/src/vs/workbench/services/editor/test/browser/customEditorLabelService.test.ts delete mode 100644 patched-vscode/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts create mode 100644 patched-vscode/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html create mode 100644 patched-vscode/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.esm.ts create mode 100644 patched-vscode/src/vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol.ts create mode 100644 patched-vscode/src/vs/workbench/services/search/common/searchExtConversionTypes.ts create mode 100644 patched-vscode/src/vs/workbench/services/search/common/searchExtTypesInternal.ts create mode 100644 patched-vscode/src/vs/workbench/services/search/worker/localFileSearch.esm.ts create mode 100644 patched-vscode/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.esm.ts create mode 100644 patched-vscode/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerHost.ts create mode 100644 patched-vscode/src/vs/workbench/services/textfile/test/node/encoding/fixtures/some.shiftjis.1.txt create mode 100644 patched-vscode/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.contribution.ts create mode 100644 patched-vscode/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.ts create mode 100644 patched-vscode/src/vs/workbench/services/userActivity/test/common/userActivityService.test.ts create mode 100644 patched-vscode/src/vs/workbench/test/browser/parts/editor/editorCommandsContext.test.ts delete mode 100644 patched-vscode/src/vs/workbench/workbench.web.main.nls.js create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.aiTextSearchProviderNew.d.ts delete mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.authGetSessions.d.ts create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.commentReveal.d.ts rename patched-vscode/{extensions/markdown-language-features/server/src/util/schemes.ts => src/vscode-dts/vscode.proposed.contribChatParticipantDetection.d.ts} (80%) rename patched-vscode/{extensions/microsoft-authentication/src/node/fetch.ts => src/vscode-dts/vscode.proposed.contribDebugCreateConfiguration.d.ts} (83%) create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemChangesMenu.d.ts rename patched-vscode/{extensions/microsoft-authentication/src/node/crypto.ts => src/vscode-dts/vscode.proposed.contribSourceControlHistoryTitleMenu.d.ts} (71%) create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.contribViewContainerTitle.d.ts create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.findFiles2New.d.ts create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.findTextInFilesNew.d.ts create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.lmTools.d.ts rename patched-vscode/{build/azure-pipelines/common/installPlaywright.ts => src/vscode-dts/vscode.proposed.notebookReplDocument.d.ts} (51%) create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts delete mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.terminalShellIntegration.d.ts create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.testRelatedCode.d.ts create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.textSearchCompleteNew.d.ts create mode 100644 patched-vscode/src/vscode-dts/vscode.proposed.textSearchProviderNew.d.ts create mode 100644 patched-vscode/test/automation/src/profiler.ts create mode 100644 patched-vscode/test/package.json create mode 100644 patched-vscode/test/unit/assert-esm.js create mode 100644 patched-vscode/test/unit/browser/index.esm.js create mode 100644 patched-vscode/test/unit/browser/renderer.esm.html create mode 100644 patched-vscode/test/unit/electron/index.esm.js create mode 100644 patched-vscode/test/unit/electron/preload.js create mode 100644 patched-vscode/test/unit/electron/renderer.esm.html create mode 100644 patched-vscode/test/unit/electron/renderer.esm.js create mode 100644 patched-vscode/test/unit/node/index.mjs diff --git a/patched-vscode/ThirdPartyNotices.txt b/patched-vscode/ThirdPartyNotices.txt index 2e3f2610..664f2b00 100644 --- a/patched-vscode/ThirdPartyNotices.txt +++ b/patched-vscode/ThirdPartyNotices.txt @@ -263,6 +263,34 @@ suitability for any purpose. --------------------------------------------------------- +cacheable-request 7.0.4 - MIT + + +Copyright (c) cacheable-request authors + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + Colorsublime-Themes 0.1.0 https://github.com/Colorsublime/Colorsublime-Themes @@ -517,7 +545,7 @@ to the base-name name of the original file, and an extension of txt, html, or si --------------------------------------------------------- -go-syntax 0.6.6 - MIT +go-syntax 0.7.5 - MIT https://github.com/worlpaker/go-syntax MIT License @@ -833,7 +861,7 @@ SOFTWARE. --------------------------------------------------------- -jlelong/vscode-latex-basics 1.7.0 - MIT +jlelong/vscode-latex-basics 1.9.0 - MIT https://github.com/jlelong/vscode-latex-basics Copyright (c) vscode-latex-basics authors @@ -890,7 +918,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -JuliaEditorSupport/atom-language-julia 0.22.1 - MIT +JuliaEditorSupport/atom-language-julia 0.23.0 - MIT https://github.com/JuliaEditorSupport/atom-language-julia The atom-language-julia package is licensed under the MIT "Expat" License: @@ -1241,7 +1269,7 @@ THE SOFTWARE. --------------------------------------------------------- -marked 4.1.0 - MIT +marked 14.0.0 - MIT https://github.com/markedjs/marked information @@ -1506,6 +1534,22 @@ SOFTWARE. --------------------------------------------------------- +RedCMD/YAML-Syntax-Highlighter 1.1.1 - MIT +https://github.com/RedCMD/YAML-Syntax-Highlighter + +MIT License + +Copyright 2024 RedCMD + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + redhat-developer/vscode-java 1.26.0 - MIT https://github.com/redhat-developer/vscode-java @@ -1928,31 +1972,6 @@ to the base-name name of the original file, and an extension of txt, html, or si --------------------------------------------------------- -textmate/yaml.tmbundle 0.0.0 - TextMate Bundle License -https://github.com/textmate/yaml.tmbundle - -Copyright (c) 2015 FichteFoll - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------- - ---------------------------------------------------------- - trond-snekvik/vscode-rst 1.5.3 - MIT https://github.com/trond-snekvik/vscode-rst diff --git a/patched-vscode/build/.cachesalt b/patched-vscode/build/.cachesalt index 4a19f329..58a1efc1 100644 --- a/patched-vscode/build/.cachesalt +++ b/patched-vscode/build/.cachesalt @@ -1 +1 @@ -2024-05-25T03:29:59.419Z +2024-08-14T18:12:43.548Z diff --git a/patched-vscode/build/.moduleignore b/patched-vscode/build/.moduleignore index 32fb3bd2..3f573e06 100644 --- a/patched-vscode/build/.moduleignore +++ b/patched-vscode/build/.moduleignore @@ -96,10 +96,13 @@ node-pty/lib/*.test.js node-pty/tools/** node-pty/deps/** node-pty/scripts/** +node-pty/third_party/** !node-pty/build/Release/spawn-helper !node-pty/build/Release/*.exe !node-pty/build/Release/*.dll !node-pty/build/Release/*.node +!node-pty/build/Release/conpty/conpty.dll +!node-pty/build/Release/conpty/OpenConsole.exe @parcel/watcher/binding.gyp @parcel/watcher/build/** @@ -160,7 +163,7 @@ typescript/lib/tsserverlibrary.js jschardet/index.js jschardet/src/** -jschardet/dist/jschardet.js +# TODO@esm uncomment when we can use jschardet.min.js again jschardet/dist/jschardet.js es6-promise/lib/** diff --git a/patched-vscode/build/.webignore b/patched-vscode/build/.webignore index 88fe96f5..837366b6 100644 --- a/patched-vscode/build/.webignore +++ b/patched-vscode/build/.webignore @@ -14,12 +14,15 @@ jschardet/index.js jschardet/src/** -jschardet/dist/jschardet.js +# TODO@esm uncomment when we can use jschardet.min.js again jschardet/dist/jschardet.js vscode-textmate/webpack.config.js @xterm/xterm/src/** +@xterm/addon-clipboard/src/** +@xterm/addon-clipboard/out/** + @xterm/addon-image/src/** @xterm/addon-image/out/** @@ -35,6 +38,7 @@ vscode-textmate/webpack.config.js # This makes sure the model is included in the package !@vscode/vscode-languagedetection/model/** +!@vscode/tree-sitter-wasm/wasm/** # Ensure only the required telemetry pieces are loaded in web to reduce bundle size @microsoft/1ds-core-js/** @@ -42,7 +46,9 @@ vscode-textmate/webpack.config.js @microsoft/applicationinsights-core-js/** @microsoft/applicationinsights-shims/** !@microsoft/1ds-core-js/dist/ms.core.min.js +!@microsoft/1ds-core-js/bundle/ms.core.min.js !@microsoft/1ds-post-js/dist/ms.post.min.js +!@microsoft/1ds-post-js/bundle/ms.post.min.js !@microsoft/applicationinsights-core-js/browser/applicationinsights-core-js.min.js !@microsoft/applicationinsights-shims/dist/umd/applicationinsights-shims.min.js diff --git a/patched-vscode/build/azure-pipelines/cli/cli-compile.yml b/patched-vscode/build/azure-pipelines/cli/cli-compile.yml index 267682f7..e77ba78a 100644 --- a/patched-vscode/build/azure-pipelines/cli/cli-compile.yml +++ b/patched-vscode/build/azure-pipelines/cli/cli-compile.yml @@ -49,16 +49,22 @@ steps: export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER="$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc" export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=--sysroot=$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot" export CC_aarch64_unknown_linux_gnu="$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc --sysroot=$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot" + export PKG_CONFIG_LIBDIR_aarch64_unknown_linux_gnu="$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot/usr/lib/aarch64-linux-gnu/pkgconfig:$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot/usr/share/pkgconfig" + export PKG_CONFIG_SYSROOT_DIR_aarch64_unknown_linux_gnu="$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot" export OBJDUMP="$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/bin/objdump" elif [ "$SYSROOT_ARCH" == "amd64" ]; then export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/bin/x86_64-linux-gnu-gcc" export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=--sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot -C link-arg=-L$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/usr/lib/x86_64-linux-gnu" export CC_x86_64_unknown_linux_gnu="$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/bin/x86_64-linux-gnu-gcc --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" + export PKG_CONFIG_LIBDIR_x86_64_unknown_linux_gnu="$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/usr/lib/x86_64-linux-gnu/pkgconfig:$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot/usr/share/pkgconfig" + export PKG_CONFIG_SYSROOT_DIR_x86_64_unknown_linux_gnu="$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" export OBJDUMP="$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/bin/objdump" elif [ "$SYSROOT_ARCH" == "armhf" ]; then export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER="$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-gcc" export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS="-C link-arg=--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" export CC_armv7_unknown_linux_gnueabihf="$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-gcc --sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" + export PKG_CONFIG_LIBDIR_armv7_unknown_linux_gnueabihf="$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/usr/lib/arm-rpi-linux-gnueabihf/pkgconfig:$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/usr/share/pkgconfig" + export PKG_CONFIG_SYSROOT_DIR_armv7_unknown_linux_gnueabihf="$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" export OBJDUMP="$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/bin/objdump" fi fi @@ -78,7 +84,7 @@ steps: fi done < <("$OBJDUMP" -T "$PWD/target/${{ parameters.VSCODE_CLI_TARGET }}/release/code") if [[ "$glibc_version" != "2.17" ]]; then - echo "Error: binary has dependency on GLIBC > 2.17" + echo "Error: binary has dependency on GLIBC > 2.17, found $glibc_version" exit 1 fi fi diff --git a/patched-vscode/build/azure-pipelines/common/publish.js b/patched-vscode/build/azure-pipelines/common/publish.js index c990e3a7..aa185ed8 100644 --- a/patched-vscode/build/azure-pipelines/common/publish.js +++ b/patched-vscode/build/azure-pipelines/common/publish.js @@ -389,14 +389,8 @@ function getPlatform(product, os, arch, type, isLegacy) { } } case 'server': - if (arch === 'arm64') { - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } return `server-win32-${arch}`; case 'web': - if (arch === 'arm64') { - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } return `server-win32-${arch}-web`; case 'cli': return `cli-win32-${arch}`; diff --git a/patched-vscode/build/azure-pipelines/common/publish.ts b/patched-vscode/build/azure-pipelines/common/publish.ts index 75065ffa..652cd168 100644 --- a/patched-vscode/build/azure-pipelines/common/publish.ts +++ b/patched-vscode/build/azure-pipelines/common/publish.ts @@ -550,14 +550,8 @@ function getPlatform(product: string, os: string, arch: string, type: string, is } } case 'server': - if (arch === 'arm64') { - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } return `server-win32-${arch}`; case 'web': - if (arch === 'arm64') { - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } return `server-win32-${arch}-web`; case 'cli': return `cli-win32-${arch}`; diff --git a/patched-vscode/build/azure-pipelines/darwin/helper-plugin-entitlements.plist b/patched-vscode/build/azure-pipelines/darwin/helper-plugin-entitlements.plist index 1cc1a152..48f7bf5c 100644 --- a/patched-vscode/build/azure-pipelines/darwin/helper-plugin-entitlements.plist +++ b/patched-vscode/build/azure-pipelines/darwin/helper-plugin-entitlements.plist @@ -6,8 +6,6 @@ com.apple.security.cs.allow-unsigned-executable-memory - com.apple.security.cs.allow-dyld-environment-variables - com.apple.security.cs.disable-library-validation diff --git a/patched-vscode/build/azure-pipelines/darwin/product-build-darwin-test.yml b/patched-vscode/build/azure-pipelines/darwin/product-build-darwin-test.yml index ed6d0236..c5f9dfb0 100644 --- a/patched-vscode/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/patched-vscode/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -7,6 +7,9 @@ parameters: type: boolean - name: VSCODE_RUN_SMOKE_TESTS type: boolean + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - script: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" @@ -17,34 +20,56 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test.sh --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - script: yarn test-node - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - script: yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: Run unit tests (Browser, Chromium & Webkit) - timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-esm.sh --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - script: yarn test-browser-esm-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium & Webkit) [ESM] + timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test.sh --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - script: yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - script: yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - script: yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - script: yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: Run unit tests (Browser, Chromium & Webkit) - timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-esm.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm --build + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - script: yarn test-browser-esm-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium & Webkit) [ESM] + timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - script: yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - script: yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - script: | @@ -59,7 +84,6 @@ steps: compile-extension:ipynb \ compile-extension:notebook-renderers \ compile-extension:json-language-features-server \ - compile-extension:markdown-language-features-server \ compile-extension:markdown-language-features \ compile-extension-media \ compile-extension:microsoft-authentication \ @@ -70,24 +94,44 @@ steps: displayName: Build integration tests - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test-integration.sh --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-integration-esm.sh --tfs "Integration Tests" + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test-integration --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + ./scripts/test-integration-esm.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - script: ./scripts/test-web-integration.sh --browser webkit env: diff --git a/patched-vscode/build/azure-pipelines/darwin/product-build-darwin.yml b/patched-vscode/build/azure-pipelines/darwin/product-build-darwin.yml index 8b4bda1c..ccb6b5b3 100644 --- a/patched-vscode/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/patched-vscode/build/azure-pipelines/darwin/product-build-darwin.yml @@ -9,6 +9,9 @@ parameters: type: boolean - name: VSCODE_RUN_SMOKE_TESTS type: boolean + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -78,6 +81,14 @@ steps: - script: | set -e + # Refs https://github.com/microsoft/vscode/issues/219893#issuecomment-2209313109 + sudo xcode-select --switch /Applications/Xcode_15.2.app + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Switch to Xcode >= 15.1 + + - script: | + set -e + c++ --version python3 -m pip install setuptools for i in {1..5}; do # try 5 times @@ -93,6 +104,11 @@ steps: ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" + # Avoid using dlopen to load Kerberos on macOS which can cause missing libraries + # https://github.com/mongodb-js/kerberos/commit/04044d2814ad1d01e77f1ce87f26b03d86692cf2 + # flipped the default to support legacy linux distros which shouldn't happen + # on macOS. + GYP_DEFINES: "kerberos_use_rtld=false" displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) @@ -161,6 +177,7 @@ steps: VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} + VSCODE_BUILD_ESM: ${{ parameters.VSCODE_BUILD_ESM }} - ${{ elseif and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - task: DownloadPipelineArtifact@2 diff --git a/patched-vscode/build/azure-pipelines/linux/product-build-linux-legacy-server.yml b/patched-vscode/build/azure-pipelines/linux/product-build-linux-legacy-server.yml index dc8424f2..b3b505c1 100644 --- a/patched-vscode/build/azure-pipelines/linux/product-build-linux-legacy-server.yml +++ b/patched-vscode/build/azure-pipelines/linux/product-build-linux-legacy-server.yml @@ -5,6 +5,9 @@ parameters: type: boolean - name: VSCODE_ARCH type: string + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - task: NodeTool@0 @@ -84,16 +87,6 @@ steps: imageName: vscode-linux-build-agent:centos7-devtoolset8-$(VSCODE_ARCH) containerCommand: uname - - ${{ if eq(parameters.VSCODE_ARCH, 'armhf') }}: - - task: Docker@1 - displayName: "Pull Docker image" - inputs: - azureSubscriptionEndpoint: "vscode-builds-subscription" - azureContainerRegistry: vscodehub.azurecr.io - command: "Run an image" - imageName: vscode-linux-build-agent:bionic-arm32v7 - containerCommand: uname - - script: | set -e # To workaround the issue of yarn not respecting the registry value from .npmrc @@ -129,18 +122,8 @@ steps: VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-$(VSCODE_ARCH) - ${{ if eq(parameters.VSCODE_ARCH, 'armhf') }}: - VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-arm32v7 displayName: Install dependencies - - ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: - - script: | - set -e - EXPECTED_GLIBC_VERSION="2.17" \ - EXPECTED_GLIBCXX_VERSION="3.4.19" \ - ./build/azure-pipelines/linux/verify-glibc-requirements.sh - displayName: Check GLIBC and GLIBCXX dependencies in remote/node_modules - - script: node build/azure-pipelines/distro/mixin-npm displayName: Mixin distro node modules @@ -172,9 +155,11 @@ steps: yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci mv ../vscode-reh-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH) # TODO@joaomoreno ARCHIVE_PATH=".build/linux/server/vscode-server-linux-legacy-$(VSCODE_ARCH).tar.gz" + UNARCHIVE_PATH="`pwd`/../vscode-server-linux-$(VSCODE_ARCH)" mkdir -p $(dirname $ARCHIVE_PATH) tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH) echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" + echo "##vso[task.setvariable variable=SERVER_UNARCHIVE_PATH]$UNARCHIVE_PATH" env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build server @@ -192,6 +177,26 @@ steps: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build server (web) + - ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: + - script: | + set -e + EXPECTED_GLIBC_VERSION="2.17" \ + EXPECTED_GLIBCXX_VERSION="3.4.19" \ + ./build/azure-pipelines/linux/verify-glibc-requirements.sh + env: + SEARCH_PATH: $(SERVER_UNARCHIVE_PATH) + displayName: Check GLIBC and GLIBCXX dependencies in server archive + + - ${{ else }}: + - script: | + set -e + EXPECTED_GLIBC_VERSION="2.17" \ + EXPECTED_GLIBCXX_VERSION="3.4.22" \ + ./build/azure-pipelines/linux/verify-glibc-requirements.sh + env: + SEARCH_PATH: $(SERVER_UNARCHIVE_PATH) + displayName: Check GLIBC and GLIBCXX dependencies in server archive + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - template: product-build-linux-test.yml parameters: @@ -199,6 +204,7 @@ steps: VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: false + VSCODE_BUILD_ESM: ${{ parameters.VSCODE_BUILD_ESM }} ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 diff --git a/patched-vscode/build/azure-pipelines/linux/product-build-linux-test.yml b/patched-vscode/build/azure-pipelines/linux/product-build-linux-test.yml index f5c00aa0..0b358a81 100644 --- a/patched-vscode/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/patched-vscode/build/azure-pipelines/linux/product-build-linux-test.yml @@ -10,6 +10,9 @@ parameters: - name: PUBLISH_TASK_NAME type: string default: PublishPipelineArtifact@0 + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - script: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" @@ -33,36 +36,61 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test.sh --tfs "Unit Tests" - env: - DISPLAY: ":10" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-esm.sh --tfs "Unit Tests" + env: + DISPLAY: ":10" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - script: yarn test-browser-esm-no-install --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium) [ESM] + timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test.sh --tfs "Unit Tests" + env: + DISPLAY: ":10" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - script: yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - script: yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 - - script: yarn test-node - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - script: yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - script: yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - script: yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-esm.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm --build + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - script: yarn test-browser-esm-no-install --build --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium) [ESM] + timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - script: yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - script: yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - script: | @@ -77,7 +105,6 @@ steps: compile-extension:ipynb \ compile-extension:notebook-renderers \ compile-extension:json-language-features-server \ - compile-extension:markdown-language-features-server \ compile-extension:markdown-language-features \ compile-extension-media \ compile-extension:microsoft-authentication \ @@ -89,11 +116,18 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: ./scripts/test-integration.sh --tfs "Integration Tests" - env: - DISPLAY: ":10" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: ./scripts/test-integration-esm.sh --tfs "Integration Tests" + env: + DISPLAY: ":10" + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: ./scripts/test-integration.sh --tfs "Integration Tests" + env: + DISPLAY: ":10" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - script: ./scripts/test-web-integration.sh --browser chromium displayName: Run integration tests (Browser, Chromium) @@ -104,20 +138,36 @@ steps: timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + ./scripts/test-integration-esm.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - script: ./scripts/test-web-integration.sh --browser chromium env: @@ -151,7 +201,7 @@ steps: - script: yarn --cwd test/smoke compile displayName: Compile smoke tests - - script: yarn gulp compile-extension:markdown-language-features compile-extension-media compile-extension:vscode-test-resolver + - script: yarn gulp compile-extension:markdown-language-features compile-extension:ipynb compile-extension-media compile-extension:vscode-test-resolver displayName: Build extensions for smoke tests - script: yarn gulp node diff --git a/patched-vscode/build/azure-pipelines/linux/product-build-linux.yml b/patched-vscode/build/azure-pipelines/linux/product-build-linux.yml index 352b3136..2aa304e4 100644 --- a/patched-vscode/build/azure-pipelines/linux/product-build-linux.yml +++ b/patched-vscode/build/azure-pipelines/linux/product-build-linux.yml @@ -11,6 +11,9 @@ parameters: type: boolean - name: VSCODE_ARCH type: string + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -131,16 +134,6 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e - - EXPECTED_GLIBC_VERSION="2.28" \ - EXPECTED_GLIBCXX_VERSION="3.4.25" \ - ./build/azure-pipelines/linux/verify-glibc-requirements.sh - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Check GLIBC and GLIBCXX dependencies in remote/node_modules - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: node build/azure-pipelines/distro/mixin-npm condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) @@ -213,9 +206,11 @@ steps: yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci mv ../vscode-reh-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH) # TODO@joaomoreno ARCHIVE_PATH=".build/linux/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz" + UNARCHIVE_PATH="`pwd`/../vscode-server-linux-$(VSCODE_ARCH)" mkdir -p $(dirname $ARCHIVE_PATH) tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH) echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" + echo "##vso[task.setvariable variable=SERVER_UNARCHIVE_PATH]$UNARCHIVE_PATH" env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build server @@ -232,6 +227,36 @@ steps: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build server (web) + - ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: + - script: | + set -e + + source ./build/azure-pipelines/linux/setup-env.sh + + EXPECTED_GLIBC_VERSION="2.28" \ + EXPECTED_GLIBCXX_VERSION="3.4.25" \ + ./build/azure-pipelines/linux/verify-glibc-requirements.sh + env: + SEARCH_PATH: $(SERVER_UNARCHIVE_PATH) + npm_config_arch: $(NPM_ARCH) + VSCODE_ARCH: $(VSCODE_ARCH) + displayName: Check GLIBC and GLIBCXX dependencies in server archive + + - ${{ else }}: + - script: | + set -e + + source ./build/azure-pipelines/linux/setup-env.sh + + EXPECTED_GLIBC_VERSION="2.28" \ + EXPECTED_GLIBCXX_VERSION="3.4.26" \ + ./build/azure-pipelines/linux/verify-glibc-requirements.sh + env: + SEARCH_PATH: $(SERVER_UNARCHIVE_PATH) + npm_config_arch: $(NPM_ARCH) + VSCODE_ARCH: $(VSCODE_ARCH) + displayName: Check GLIBC and GLIBCXX dependencies in server archive + - ${{ else }}: - script: yarn gulp "transpile-client-swc" "transpile-extensions" env: @@ -245,6 +270,7 @@ steps: VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} + VSCODE_BUILD_ESM: ${{ parameters.VSCODE_BUILD_ESM }} ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 diff --git a/patched-vscode/build/azure-pipelines/linux/setup-env.sh b/patched-vscode/build/azure-pipelines/linux/setup-env.sh index 9bfbf9ab..949b5f37 100755 --- a/patched-vscode/build/azure-pipelines/linux/setup-env.sh +++ b/patched-vscode/build/azure-pipelines/linux/setup-env.sh @@ -13,7 +13,7 @@ SYSROOT_ARCH="$SYSROOT_ARCH" node -e '(async () => { const { getVSCodeSysroot } if [ "$npm_config_arch" == "x64" ]; then if [ "$(echo "$@" | grep -c -- "--only-remote")" -eq 0 ]; then # Download clang based on chromium revision used by vscode - curl -s https://raw.githubusercontent.com/chromium/chromium/122.0.6261.156/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux + curl -s https://raw.githubusercontent.com/chromium/chromium/124.0.6367.243/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux # Download libcxx headers and objects from upstream electron releases DEBUG=libcxx-fetcher \ @@ -25,9 +25,9 @@ if [ "$npm_config_arch" == "x64" ]; then # Set compiler toolchain # Flags for the client build are based on - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/122.0.6261.156:build/config/arm.gni - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/122.0.6261.156:build/config/compiler/BUILD.gn - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/122.0.6261.156:build/config/c++/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/124.0.6367.243:build/config/arm.gni + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/124.0.6367.243:build/config/compiler/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/124.0.6367.243:build/config/c++/BUILD.gn export CC="$PWD/.build/CR_Clang/bin/clang --gcc-toolchain=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu" export CXX="$PWD/.build/CR_Clang/bin/clang++ --gcc-toolchain=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu" export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" @@ -54,17 +54,15 @@ elif [ "$npm_config_arch" == "arm64" ]; then export VSCODE_REMOTE_LDFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot -L$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot/usr/lib/aarch64-linux-gnu -L$VSCODE_SYSROOT_DIR/aarch64-linux-gnu/aarch64-linux-gnu/sysroot/lib/aarch64-linux-gnu" fi elif [ "$npm_config_arch" == "arm" ]; then - if [ "$(echo "$@" | grep -c -- "--only-remote")" -eq 0 ]; then - # Set compiler toolchain for client native modules - export CC=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-gcc - export CXX=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-g++ - export CXXFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" - export LDFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot -L$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/usr/lib/arm-linux-gnueabihf -L$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/lib/arm-linux-gnueabihf" + # Set compiler toolchain for client native modules + export CC=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-gcc + export CXX=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-g++ + export CXXFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" + export LDFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot -L$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/usr/lib/arm-linux-gnueabihf -L$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/lib/arm-linux-gnueabihf" - # Set compiler toolchain for remote server - export VSCODE_REMOTE_CC=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-gcc - export VSCODE_REMOTE_CXX=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-g++ - export VSCODE_REMOTE_CXXFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" - export VSCODE_REMOTE_LDFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot -L$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/usr/lib/arm-linux-gnueabihf -L$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/lib/arm-linux-gnueabihf" - fi + # Set compiler toolchain for remote server + export VSCODE_REMOTE_CC=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-gcc + export VSCODE_REMOTE_CXX=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/bin/arm-rpi-linux-gnueabihf-g++ + export VSCODE_REMOTE_CXXFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot" + export VSCODE_REMOTE_LDFLAGS="--sysroot=$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot -L$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/usr/lib/arm-linux-gnueabihf -L$VSCODE_SYSROOT_DIR/arm-rpi-linux-gnueabihf/arm-rpi-linux-gnueabihf/sysroot/lib/arm-linux-gnueabihf" fi diff --git a/patched-vscode/build/azure-pipelines/linux/verify-glibc-requirements.sh b/patched-vscode/build/azure-pipelines/linux/verify-glibc-requirements.sh index f07c0ba7..19482c24 100755 --- a/patched-vscode/build/azure-pipelines/linux/verify-glibc-requirements.sh +++ b/patched-vscode/build/azure-pipelines/linux/verify-glibc-requirements.sh @@ -9,8 +9,8 @@ elif [ "$VSCODE_ARCH" == "armhf" ]; then TRIPLE="arm-rpi-linux-gnueabihf" fi -# Get all files with .node extension from remote/node_modules folder -files=$(find remote/node_modules -name "*.node" -not -path "*prebuilds*") +# Get all files with .node extension from server folder +files=$(find $SEARCH_PATH -name "*.node" -not -path "*prebuilds*" -o -type f -executable -name "node") echo "Verifying requirements for files: $files" @@ -19,13 +19,13 @@ for file in $files; do glibcxx_version="$EXPECTED_GLIBCXX_VERSION" while IFS= read -r line; do if [[ $line == *"GLIBC_"* ]]; then - version=$(echo "$line" | awk '{print $5}' | tr -d '()') + version=$(echo "$line" | awk '{if ($5 ~ /^[0-9a-fA-F]+$/) print $6; else print $5}' | tr -d '()') version=${version#*_} if [[ $(printf "%s\n%s" "$version" "$glibc_version" | sort -V | tail -n1) == "$version" ]]; then glibc_version=$version fi elif [[ $line == *"GLIBCXX_"* ]]; then - version=$(echo "$line" | awk '{print $5}' | tr -d '()') + version=$(echo "$line" | awk '{if ($5 ~ /^[0-9a-fA-F]+$/) print $6; else print $5}' | tr -d '()') version=${version#*_} if [[ $(printf "%s\n%s" "$version" "$glibcxx_version" | sort -V | tail -n1) == "$version" ]]; then glibcxx_version=$version @@ -34,11 +34,11 @@ for file in $files; do done < <("$PWD/.build/sysroots/$TRIPLE/$TRIPLE/bin/objdump" -T "$file") if [[ "$glibc_version" != "$EXPECTED_GLIBC_VERSION" ]]; then - echo "Error: File $file has dependency on GLIBC > $EXPECTED_GLIBC_VERSION" + echo "Error: File $file has dependency on GLIBC > $EXPECTED_GLIBC_VERSION, found $glibc_version" exit 1 fi if [[ "$glibcxx_version" != "$EXPECTED_GLIBCXX_VERSION" ]]; then - echo "Error: File $file has dependency on GLIBCXX > $EXPECTED_GLIBCXX_VERSION" + echo "Error: File $file has dependency on GLIBCXX > $EXPECTED_GLIBCXX_VERSION, found $glibcxx_version" exit 1 fi done diff --git a/patched-vscode/build/azure-pipelines/product-build.yml b/patched-vscode/build/azure-pipelines/product-build.yml index 0508059f..b0e495b7 100644 --- a/patched-vscode/build/azure-pipelines/product-build.yml +++ b/patched-vscode/build/azure-pipelines/product-build.yml @@ -100,6 +100,10 @@ parameters: displayName: "Skip tests" type: boolean default: false + - name: VSCODE_BUILD_ESM # TODO@bpasero TODO@esm remove me once ESM is shipped + displayName: "ï¸â— Build as ESM (!FOR TESTING ONLY!) ï¸â—" + type: boolean + default: false variables: - name: VSCODE_PRIVATE_BUILD @@ -110,6 +114,8 @@ variables: value: ${{ parameters.CARGO_REGISTRY }} - name: VSCODE_QUALITY value: ${{ parameters.VSCODE_QUALITY }} + - name: VSCODE_BUILD_ESM + value: ${{ parameters.VSCODE_BUILD_ESM }} - name: VSCODE_BUILD_STAGE_WINDOWS value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_LINUX @@ -148,6 +154,8 @@ variables: value: microsoft/vscode-distro - name: skipComponentGovernanceDetection value: true + - name: ComponentDetection.Timeout + value: 600 - name: Codeql.SkipTaskAutoInjection value: true - name: ARTIFACT_PREFIX @@ -182,9 +190,10 @@ extends: validateToolOutput: None allTools: true codeql: - compiled: - enabled: true runSourceLanguagesInSourceAnalysis: true + compiled: + enabled: false + justificationForDisabling: "CodeQL breaks ESRP CodeSign on macOS (ICM #520035761, githubcustomers/microsoft-codeql-support#198)" credscan: suppressionsFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/CredScanSuppressions.json eslint: @@ -215,111 +224,130 @@ extends: - template: build/azure-pipelines/product-compile.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} - - stage: CompileCLI - dependsOn: [] - jobs: - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - job: CLILinuxX64 - pool: - name: 1es-ubuntu-20.04-x64 - os: linux - steps: - - template: build/azure-pipelines/linux/cli-build-linux.yml@self - parameters: - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_LINUX: ${{ parameters.VSCODE_BUILD_LINUX }} + - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - stage: CompileCLI + dependsOn: [] + jobs: + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - job: CLILinuxX64 + pool: + name: 1es-ubuntu-20.04-x64 + os: linux + steps: + - template: build/azure-pipelines/linux/cli-build-linux.yml@self + parameters: + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_LINUX: ${{ parameters.VSCODE_BUILD_LINUX }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true))) }}: - - job: CLILinuxGnuARM - pool: - name: 1es-ubuntu-20.04-x64 - os: linux - steps: - - template: build/azure-pipelines/linux/cli-build-linux.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_LINUX_ARMHF: ${{ parameters.VSCODE_BUILD_LINUX_ARMHF }} - VSCODE_BUILD_LINUX_ARM64: ${{ parameters.VSCODE_BUILD_LINUX_ARM64 }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE, true)) }}: - - job: CLIAlpineX64 - pool: - name: 1es-ubuntu-20.04-x64 - os: linux - steps: - - template: build/azure-pipelines/alpine/cli-build-alpine.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_ALPINE: ${{ parameters.VSCODE_BUILD_ALPINE }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }}: - - job: CLIAlpineARM64 - pool: - name: 1es-mariner-2.0-arm64 - os: linux - hostArchitecture: arm64 - container: ubuntu-2004-arm64 - steps: - - template: build/azure-pipelines/alpine/cli-build-alpine.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_ALPINE_ARM64: ${{ parameters.VSCODE_BUILD_ALPINE_ARM64 }} - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - job: CLIMacOSX64 - pool: - name: Azure Pipelines - image: macOS-11 - os: macOS - steps: - - template: build/azure-pipelines/darwin/cli-build-darwin.yml@self - parameters: - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - - job: CLIMacOSARM64 - pool: - name: Azure Pipelines - image: macOS-11 - os: macOS - steps: - - template: build/azure-pipelines/darwin/cli-build-darwin.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true))) }}: + - job: CLILinuxGnuARM + pool: + name: 1es-ubuntu-20.04-x64 + os: linux + steps: + - template: build/azure-pipelines/linux/cli-build-linux.yml@self + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_LINUX_ARMHF: ${{ parameters.VSCODE_BUILD_LINUX_ARMHF }} + VSCODE_BUILD_LINUX_ARM64: ${{ parameters.VSCODE_BUILD_LINUX_ARM64 }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE, true)) }}: + - job: CLIAlpineX64 + pool: + name: 1es-ubuntu-20.04-x64 + os: linux + steps: + - template: build/azure-pipelines/alpine/cli-build-alpine.yml@self + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ALPINE: ${{ parameters.VSCODE_BUILD_ALPINE }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }}: + - job: CLIAlpineARM64 + pool: + name: 1es-mariner-2.0-arm64 + os: linux + hostArchitecture: arm64 + container: ubuntu-2004-arm64 + steps: + - template: build/azure-pipelines/alpine/cli-build-alpine.yml@self + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ALPINE_ARM64: ${{ parameters.VSCODE_BUILD_ALPINE_ARM64 }} + + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - job: CLIMacOSX64 + pool: + name: Azure Pipelines + image: macOS-13 + os: macOS + steps: + - template: build/azure-pipelines/darwin/cli-build-darwin.yml@self + parameters: + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - job: CLIWindowsX64 - pool: - name: 1es-windows-2019-x64 - os: windows - steps: - - template: build/azure-pipelines/win32/cli-build-win32.yml@self - parameters: - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: + - job: CLIMacOSARM64 + pool: + name: Azure Pipelines + image: macOS-13 + os: macOS + steps: + - template: build/azure-pipelines/darwin/cli-build-darwin.yml@self + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - job: CLIWindowsARM64 - pool: - name: 1es-windows-2019-x64 - os: windows + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - job: CLIWindowsX64 + pool: + name: 1es-windows-2019-x64 + os: windows + steps: + - template: build/azure-pipelines/win32/cli-build-win32.yml@self + parameters: + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - job: CLIWindowsARM64 + pool: + name: 1es-windows-2019-x64 + os: windows + steps: + - template: build/azure-pipelines/win32/cli-build-win32.yml@self + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false)) }}: + - stage: CustomSDL + dependsOn: [] + pool: + name: 1es-windows-2019-x64 + os: windows + jobs: + - job: WindowsSDL + variables: + - group: 'API Scan' steps: - - template: build/azure-pipelines/win32/cli-build-win32.yml@self + - template: build/azure-pipelines/win32/sdl-scan-win32.yml@self parameters: + VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true)) }}: - stage: Windows dependsOn: - Compile - - CompileCLI + - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - CompileCLI pool: name: 1es-windows-2019-x64 os: windows @@ -334,6 +362,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: true @@ -348,6 +377,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false @@ -362,6 +392,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false @@ -377,6 +408,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} @@ -400,6 +432,7 @@ extends: - template: build/azure-pipelines/win32/product-build-win32.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_ARCH: arm64 VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false @@ -410,7 +443,8 @@ extends: - stage: Linux dependsOn: - Compile - - CompileCLI + - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - CompileCLI pool: name: 1es-ubuntu-20.04-x64 os: linux @@ -427,6 +461,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: true VSCODE_RUN_INTEGRATION_TESTS: false @@ -442,6 +477,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: true @@ -457,6 +493,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -474,6 +511,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} @@ -499,6 +537,7 @@ extends: parameters: VSCODE_ARCH: armhf VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -514,6 +553,7 @@ extends: parameters: VSCODE_ARCH: arm64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -538,6 +578,7 @@ extends: parameters: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF_LEGACY_SERVER, true) }}: @@ -550,6 +591,7 @@ extends: parameters: VSCODE_ARCH: armhf VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_RUN_INTEGRATION_TESTS: false - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64_LEGACY_SERVER, true) }}: @@ -562,13 +604,15 @@ extends: parameters: VSCODE_ARCH: arm64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_RUN_INTEGRATION_TESTS: false - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_ALPINE'], true)) }}: - stage: Alpine dependsOn: - Compile - - CompileCLI + - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - CompileCLI pool: name: 1es-ubuntu-20.04-x64 os: linux @@ -594,10 +638,11 @@ extends: - stage: macOS dependsOn: - Compile - - CompileCLI + - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - CompileCLI pool: name: Azure Pipelines - image: macOS-11 + image: macOS-13 os: macOS variables: BUILDSECMON_OPT_IN: true @@ -612,6 +657,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: true VSCODE_RUN_INTEGRATION_TESTS: false @@ -625,6 +671,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: true @@ -638,6 +685,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -652,6 +700,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false @@ -666,6 +715,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} @@ -697,6 +747,7 @@ extends: - template: build/azure-pipelines/darwin/product-build-darwin.yml@self parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false diff --git a/patched-vscode/build/azure-pipelines/product-compile.yml b/patched-vscode/build/azure-pipelines/product-compile.yml index 41c33f3f..46b5dde2 100644 --- a/patched-vscode/build/azure-pipelines/product-compile.yml +++ b/patched-vscode/build/azure-pipelines/product-compile.yml @@ -1,6 +1,9 @@ parameters: - name: VSCODE_QUALITY type: string + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - task: NodeTool@0 @@ -98,6 +101,10 @@ steps: - script: node build/azure-pipelines/distro/mixin-quality displayName: Mixin distro quality + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - script: node migrate.mjs --disable-watch + displayName: Migrate to ESM + - template: common/install-builtin-extensions.yml@self - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -135,13 +142,22 @@ steps: - script: | set -e - AZURE_STORAGE_ACCOUNT="ticino" \ + AZURE_STORAGE_ACCOUNT="vscodeweb" \ AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ node build/azure-pipelines/upload-sourcemaps displayName: Upload sourcemaps to Azure + - script: | + set -e + AZURE_STORAGE_ACCOUNT="ticino" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ + node build/azure-pipelines/upload-sourcemaps + displayName: Upload sourcemaps to Azure (Deprecated) + - script: ./build/azure-pipelines/common/extract-telemetry.sh displayName: Generate lists of telemetry events diff --git a/patched-vscode/build/azure-pipelines/sdl-scan.yml b/patched-vscode/build/azure-pipelines/sdl-scan.yml deleted file mode 100644 index 927cd5e0..00000000 --- a/patched-vscode/build/azure-pipelines/sdl-scan.yml +++ /dev/null @@ -1,296 +0,0 @@ -trigger: none -pr: none - -parameters: - - name: NPM_REGISTRY - displayName: "Custom NPM Registry" - type: string - default: "https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/" - - name: SCAN_WINDOWS - displayName: "Scan Windows" - type: boolean - default: true - - name: SCAN_LINUX - displayName: "Scan Linux" - type: boolean - default: false - -variables: - - name: NPM_REGISTRY - value: ${{ parameters.NPM_REGISTRY }} - - name: SCAN_WINDOWS - value: ${{ eq(parameters.SCAN_WINDOWS, true) }} - - name: SCAN_LINUX - value: ${{ eq(parameters.SCAN_LINUX, true) }} - - name: VSCODE_MIXIN_REPO - value: microsoft/vscode-distro - - name: skipComponentGovernanceDetection - value: true - - name: NPM_ARCH - value: x64 - - name: VSCODE_ARCH - value: x64 - - name: Codeql.enabled - value: true - - name: Codeql.TSAEnabled - value: true - - name: Codeql.TSAOptionsPath - value: '$(Build.SourcesDirectory)\build\azure-pipelines\config\tsaoptions.json' - -stages: - - stage: Windows - condition: eq(variables.SCAN_WINDOWS, 'true') - pool: 1es-windows-2019-x64 - jobs: - - job: WindowsJob - timeoutInMinutes: 0 - steps: - - task: CredScan@3 - continueOnError: true - inputs: - scanFolder: "$(Build.SourcesDirectory)" - outputFormat: "pre" - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - template: ./distro/download-distro.yml - - - task: AzureKeyVault@1 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm config set registry "$env:NPM_REGISTRY" --location=project } - # npm >v7 deprecated the `always-auth` config option, refs npm/cli@72a7eeb - # following is a workaround for yarn to send authorization header - # for GET requests to the registry. - exec { Add-Content -Path .npmrc -Value "always-auth=true" } - exec { yarn config set registry "$env:NPM_REGISTRY" } - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM & Yarn - - - task: npmAuthenticate@0 - inputs: - workingFile: .npmrc - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build/setup-npm-registry.js $env:NPM_REGISTRY } - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - task: CodeQL3000Init@0 - displayName: CodeQL Initialize - condition: eq(variables['Codeql.enabled'], 'True') - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - . build/azure-pipelines/win32/retry.ps1 - $ErrorActionPreference = "Stop" - # TODO: remove custom node-gyp when updating to Node v20, - # refs https://github.com/npm/cli/releases/tag/v10.2.3 which is available with Node >= 20.10.0 - $nodeGypDir = "$(Agent.TempDirectory)/custom-packages" - mkdir "$nodeGypDir" - npm install node-gyp@10.0.1 -g --prefix "$nodeGypDir" - $env:npm_config_node_gyp = "${nodeGypDir}/node_modules/node-gyp/bin/node-gyp.js" - $env:npm_config_arch = "$(NPM_ARCH)" - retry { exec { yarn --frozen-lockfile --check-files } } - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - CHILD_CONCURRENCY: 1 - displayName: Install dependencies - - - script: node build/azure-pipelines/distro/mixin-npm - displayName: Mixin distro node modules - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - env: - VSCODE_QUALITY: stable - - - powershell: yarn compile - displayName: Compile - - - task: CodeQL3000Finalize@0 - displayName: CodeQL Finalize - condition: eq(variables['Codeql.enabled'], 'True') - - - powershell: yarn gulp "vscode-symbols-win32-$(VSCODE_ARCH)" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Download Symbols - - - task: PSScriptAnalyzer@1 - inputs: - Path: '$(Build.SourcesDirectory)' - Settings: required - Recurse: true - - - task: BinSkim@4 - inputs: - InputType: "Basic" - Function: "analyze" - TargetPattern: "guardianGlob" - AnalyzeIgnorePdbLoadError: true - AnalyzeTargetGlob: '$(agent.builddirectory)\scanbin\**.dll;$(agent.builddirectory)\scanbin\**.exe;$(agent.builddirectory)\scanbin\**.node' - AnalyzeLocalSymbolDirectories: '$(agent.builddirectory)\scanbin\VSCode-win32-$(VSCODE_ARCH)\pdb' - - - task: AntiMalware@4 - inputs: - InputType: Basic - ScanType: CustomScan - FileDirPath: '$(Build.SourcesDirectory)' - EnableServices: true - SupportLogOnError: false - TreatSignatureUpdateFailureAs: 'Warning' - SignatureFreshness: 'OneDay' - TreatStaleSignatureAs: 'Error' - - - task: PublishSecurityAnalysisLogs@3 - inputs: - ArtifactName: CodeAnalysisLogs - ArtifactType: Container - PublishProcessedResults: false - AllTools: true - - - task: TSAUpload@2 - inputs: - GdnPublishTsaOnboard: true - GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\build\azure-pipelines\config\tsaoptions.json' - - - stage: Linux - dependsOn: [] - condition: eq(variables.SCAN_LINUX, 'true') - pool: - vmImage: "Ubuntu-18.04" - jobs: - - job: LinuxJob - steps: - - task: CredScan@2 - inputs: - toolMajorVersion: "V2" - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - template: ./distro/download-distro.yml - - - task: AzureKeyVault@1 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - script: | - set -e - npm config set registry "$NPM_REGISTRY" --location=project - # npm >v7 deprecated the `always-auth` config option, refs npm/cli@72a7eeb - # following is a workaround for yarn to send authorization header - # for GET requests to the registry. - echo "always-auth=true" >> .npmrc - yarn config set registry "$NPM_REGISTRY" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM & Yarn - - - task: npmAuthenticate@0 - inputs: - workingFile: .npmrc - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: | - set -e - for i in {1..5}; do # try 5 times - yarn --cwd build --frozen-lockfile --check-files && break - if [ $i -eq 3 ]; then - echo "Yarn failed too many times" >&2 - exit 1 - fi - echo "Yarn failed $i, trying again..." - done - displayName: Install build dependencies - - - script: | - set -e - export npm_config_arch=$(NPM_ARCH) - - if [ -z "$CC" ] || [ -z "$CXX" ]; then - # Download clang based on chromium revision used by vscode - curl -s https://raw.githubusercontent.com/chromium/chromium/96.0.4664.110/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux - # Download libcxx headers and objects from upstream electron releases - DEBUG=libcxx-fetcher \ - VSCODE_LIBCXX_OBJECTS_DIR=$PWD/.build/libcxx-objects \ - VSCODE_LIBCXX_HEADERS_DIR=$PWD/.build/libcxx_headers \ - VSCODE_LIBCXXABI_HEADERS_DIR=$PWD/.build/libcxxabi_headers \ - VSCODE_ARCH="$(NPM_ARCH)" \ - node build/linux/libcxx-fetcher.js - # Set compiler toolchain - export CC=$PWD/.build/CR_Clang/bin/clang - export CXX=$PWD/.build/CR_Clang/bin/clang++ - export CXXFLAGS="-std=c++17 -nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr" - export LDFLAGS="-stdlib=libc++ -fuse-ld=lld -flto=thin -fsplit-lto-unit -L$PWD/.build/libcxx-objects -lc++abi" - export VSCODE_REMOTE_CC=$(which gcc) - export VSCODE_REMOTE_CXX=$(which g++) - fi - - for i in {1..5}; do # try 5 times - yarn --frozen-lockfile --check-files && break - if [ $i -eq 3 ]; then - echo "Yarn failed too many times" >&2 - exit 1 - fi - echo "Yarn failed $i, trying again..." - done - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - - - script: yarn --frozen-lockfile --check-files - workingDirectory: .build/distro/npm - env: - npm_config_arch: $(NPM_ARCH) - displayName: Install distro node modules - - - script: node build/azure-pipelines/distro/mixin-npm - displayName: Mixin distro node modules - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - env: - VSCODE_QUALITY: stable - - - script: yarn gulp vscode-symbols-linux-$(VSCODE_ARCH) - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build - - - task: BinSkim@3 - inputs: - toolVersion: Latest - InputType: CommandLine - arguments: analyze $(agent.builddirectory)\scanbin\exe\*.* --recurse --local-symbol-directories $(agent.builddirectory)\scanbin\VSCode-linux-$(VSCODE_ARCH)\pdb - - - task: TSAUpload@2 - inputs: - GdnPublishTsaConfigFile: '$(Build.SourceDirectory)\build\azure-pipelines\config\tsaoptions.json' diff --git a/patched-vscode/build/azure-pipelines/upload-configuration.js b/patched-vscode/build/azure-pipelines/upload-configuration.js deleted file mode 100644 index 39a44dc5..00000000 --- a/patched-vscode/build/azure-pipelines/upload-configuration.js +++ /dev/null @@ -1,112 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getSettingsSearchBuildId = exports.shouldSetupSettingsSearch = void 0; -const path = require("path"); -const os = require("os"); -const cp = require("child_process"); -const vfs = require("vinyl-fs"); -const util = require("../lib/util"); -const identity_1 = require("@azure/identity"); -const azure = require('gulp-azure-storage'); -const packageJson = require("../../package.json"); -const commit = process.env['BUILD_SOURCEVERSION']; -function generateVSCodeConfigurationTask() { - return new Promise((resolve, reject) => { - const buildDir = process.env['AGENT_BUILDDIRECTORY']; - if (!buildDir) { - return reject(new Error('$AGENT_BUILDDIRECTORY not set')); - } - if (!shouldSetupSettingsSearch()) { - console.log(`Only runs on main and release branches, not ${process.env.BUILD_SOURCEBRANCH}`); - return resolve(undefined); - } - if (process.env.VSCODE_QUALITY !== 'insider' && process.env.VSCODE_QUALITY !== 'stable') { - console.log(`Only runs on insider and stable qualities, not ${process.env.VSCODE_QUALITY}`); - return resolve(undefined); - } - const result = path.join(os.tmpdir(), 'configuration.json'); - const userDataDir = path.join(os.tmpdir(), 'tmpuserdata'); - const extensionsDir = path.join(os.tmpdir(), 'tmpextdir'); - const arch = process.env['VSCODE_ARCH']; - const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`); - const appName = process.env.VSCODE_QUALITY === 'insider' ? 'Visual\\ Studio\\ Code\\ -\\ Insiders.app' : 'Visual\\ Studio\\ Code.app'; - const appPath = path.join(appRoot, appName, 'Contents', 'Resources', 'app', 'bin', 'code'); - const codeProc = cp.exec(`${appPath} --export-default-configuration='${result}' --wait --user-data-dir='${userDataDir}' --extensions-dir='${extensionsDir}'`, (err, stdout, stderr) => { - clearTimeout(timer); - if (err) { - console.log(`err: ${err} ${err.message} ${err.toString()}`); - reject(err); - } - if (stdout) { - console.log(`stdout: ${stdout}`); - } - if (stderr) { - console.log(`stderr: ${stderr}`); - } - resolve(result); - }); - const timer = setTimeout(() => { - codeProc.kill(); - reject(new Error('export-default-configuration process timed out')); - }, 60 * 1000); - codeProc.on('error', err => { - clearTimeout(timer); - reject(err); - }); - }); -} -function shouldSetupSettingsSearch() { - const branch = process.env.BUILD_SOURCEBRANCH; - return !!(branch && (/\/main$/.test(branch) || branch.indexOf('/release/') >= 0)); -} -exports.shouldSetupSettingsSearch = shouldSetupSettingsSearch; -function getSettingsSearchBuildId(packageJson) { - try { - const branch = process.env.BUILD_SOURCEBRANCH; - const branchId = branch.indexOf('/release/') >= 0 ? 0 : - /\/main$/.test(branch) ? 1 : - 2; // Some unexpected branch - const out = cp.execSync(`git rev-list HEAD --count`); - const count = parseInt(out.toString()); - // - // 1.25.1, 1,234,567 commits, main = 1250112345671 - return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId; - } - catch (e) { - throw new Error('Could not determine build number: ' + e.toString()); - } -} -exports.getSettingsSearchBuildId = getSettingsSearchBuildId; -async function main() { - const configPath = await generateVSCodeConfigurationTask(); - if (!configPath) { - return; - } - const settingsSearchBuildId = getSettingsSearchBuildId(packageJson); - if (!settingsSearchBuildId) { - throw new Error('Failed to compute build number'); - } - const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); - return new Promise((c, e) => { - vfs.src(configPath) - .pipe(azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: 'configuration', - prefix: `${settingsSearchBuildId}/${commit}/` - })) - .on('end', () => c()) - .on('error', (err) => e(err)); - }); -} -if (require.main === module) { - main().catch(err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBsb2FkLWNvbmZpZ3VyYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ1cGxvYWQtY29uZmlndXJhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyw2QkFBNkI7QUFDN0IseUJBQXlCO0FBQ3pCLG9DQUFvQztBQUNwQyxnQ0FBZ0M7QUFDaEMsb0NBQW9DO0FBQ3BDLDhDQUF5RDtBQUN6RCxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsb0JBQW9CLENBQUMsQ0FBQztBQUM1QyxrREFBa0Q7QUFFbEQsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0FBRWxELFNBQVMsK0JBQStCO0lBQ3ZDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDdEMsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ3JELElBQUksQ0FBQyxRQUFRLEVBQUU7WUFDZCxPQUFPLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQywrQkFBK0IsQ0FBQyxDQUFDLENBQUM7U0FDMUQ7UUFFRCxJQUFJLENBQUMseUJBQXlCLEVBQUUsRUFBRTtZQUNqQyxPQUFPLENBQUMsR0FBRyxDQUFDLCtDQUErQyxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixFQUFFLENBQUMsQ0FBQztZQUM3RixPQUFPLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztTQUMxQjtRQUVELElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEtBQUssU0FBUyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxLQUFLLFFBQVEsRUFBRTtZQUN4RixPQUFPLENBQUMsR0FBRyxDQUFDLGtEQUFrRCxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxDQUFDLENBQUM7WUFDNUYsT0FBTyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7U0FDMUI7UUFFRCxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsRUFBRSxvQkFBb0IsQ0FBQyxDQUFDO1FBQzVELE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBQzFELE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxFQUFFLFdBQVcsQ0FBQyxDQUFDO1FBQzFELE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDeEMsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsaUJBQWlCLElBQUksRUFBRSxDQUFDLENBQUM7UUFDN0QsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQywyQ0FBMkMsQ0FBQyxDQUFDLENBQUMsNEJBQTRCLENBQUM7UUFDdEksTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQztRQUMzRixNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUN2QixHQUFHLE9BQU8sb0NBQW9DLE1BQU0sNkJBQTZCLFdBQVcsdUJBQXVCLGFBQWEsR0FBRyxFQUNuSSxDQUFDLEdBQUcsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDdkIsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ3BCLElBQUksR0FBRyxFQUFFO2dCQUNSLE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sSUFBSSxHQUFHLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFDO2dCQUM1RCxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7YUFDWjtZQUVELElBQUksTUFBTSxFQUFFO2dCQUNYLE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVyxNQUFNLEVBQUUsQ0FBQyxDQUFDO2FBQ2pDO1lBRUQsSUFBSSxNQUFNLEVBQUU7Z0JBQ1gsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLE1BQU0sRUFBRSxDQUFDLENBQUM7YUFDakM7WUFFRCxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDakIsQ0FBQyxDQUNELENBQUM7UUFDRixNQUFNLEtBQUssR0FBRyxVQUFVLENBQUMsR0FBRyxFQUFFO1lBQzdCLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNoQixNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsZ0RBQWdELENBQUMsQ0FBQyxDQUFDO1FBQ3JFLENBQUMsRUFBRSxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7UUFFZCxRQUFRLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRTtZQUMxQixZQUFZLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDcEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2IsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFnQix5QkFBeUI7SUFDeEMsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsQ0FBQztJQUM5QyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ25GLENBQUM7QUFIRCw4REFHQztBQUVELFNBQWdCLHdCQUF3QixDQUFDLFdBQWdDO0lBQ3hFLElBQUk7UUFDSCxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFtQixDQUFDO1FBQy9DLE1BQU0sUUFBUSxHQUFHLE1BQU0sQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN0RCxTQUFTLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDM0IsQ0FBQyxDQUFDLENBQUMseUJBQXlCO1FBRTlCLE1BQU0sR0FBRyxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsMkJBQTJCLENBQUMsQ0FBQztRQUNyRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFFdkMsc0VBQXNFO1FBQ3RFLGtEQUFrRDtRQUNsRCxPQUFPLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFDLEdBQUcsR0FBRyxHQUFHLEtBQUssR0FBRyxFQUFFLEdBQUcsUUFBUSxDQUFDO0tBQ3JGO0lBQUMsT0FBTyxDQUFDLEVBQUU7UUFDWCxNQUFNLElBQUksS0FBSyxDQUFDLG9DQUFvQyxHQUFHLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO0tBQ3JFO0FBQ0YsQ0FBQztBQWhCRCw0REFnQkM7QUFFRCxLQUFLLFVBQVUsSUFBSTtJQUNsQixNQUFNLFVBQVUsR0FBRyxNQUFNLCtCQUErQixFQUFFLENBQUM7SUFFM0QsSUFBSSxDQUFDLFVBQVUsRUFBRTtRQUNoQixPQUFPO0tBQ1A7SUFFRCxNQUFNLHFCQUFxQixHQUFHLHdCQUF3QixDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBRXBFLElBQUksQ0FBQyxxQkFBcUIsRUFBRTtRQUMzQixNQUFNLElBQUksS0FBSyxDQUFDLGdDQUFnQyxDQUFDLENBQUM7S0FDbEQ7SUFFRCxNQUFNLFVBQVUsR0FBRyxJQUFJLGlDQUFzQixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBRSxDQUFDLENBQUM7SUFFckosT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRTtRQUMzQixHQUFHLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQzthQUNqQixJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQztZQUNsQixPQUFPLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUI7WUFDMUMsVUFBVTtZQUNWLFNBQVMsRUFBRSxlQUFlO1lBQzFCLE1BQU0sRUFBRSxHQUFHLHFCQUFxQixJQUFJLE1BQU0sR0FBRztTQUM3QyxDQUFDLENBQUM7YUFDRixFQUFFLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDO2FBQ3BCLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFRLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3JDLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQUVELElBQUksT0FBTyxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUU7SUFDNUIsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1FBQ2xCLE9BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDbkIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNqQixDQUFDLENBQUMsQ0FBQztDQUNIIn0= \ No newline at end of file diff --git a/patched-vscode/build/azure-pipelines/upload-nlsmetadata.js b/patched-vscode/build/azure-pipelines/upload-nlsmetadata.js index 34c2005a..5b6cd3ed 100644 --- a/patched-vscode/build/azure-pipelines/upload-nlsmetadata.js +++ b/patched-vscode/build/azure-pipelines/upload-nlsmetadata.js @@ -16,13 +16,33 @@ const commit = process.env['BUILD_SOURCEVERSION']; const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); function main() { return new Promise((c, e) => { - es.merge(vfs.src('out-vscode-web-min/nls.metadata.json', { base: 'out-vscode-web-min' }), vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' })) + const combinedMetadataJson = es.merge( + // vscode: we are not using `out-build/nls.metadata.json` here because + // it includes metadata for translators for `keys`. but for our purpose + // we want only the `keys` and `messages` as `string`. + es.merge(vfs.src('out-build/nls.keys.json', { base: 'out-build' }), vfs.src('out-build/nls.messages.json', { base: 'out-build' })) .pipe(merge({ + fileName: 'vscode.json', + jsonSpace: '', + concatArrays: true, + edit: (parsedJson, file) => { + if (file.base === 'out-build') { + if (file.basename === 'nls.keys.json') { + return { keys: parsedJson }; + } + else { + return { messages: parsedJson }; + } + } + } + })), + // extensions + vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' })).pipe(merge({ fileName: 'combined.nls.metadata.json', jsonSpace: '', concatArrays: true, edit: (parsedJson, file) => { - if (file.base === 'out-vscode-web-min') { + if (file.basename === 'vscode.json') { return { vscode: parsedJson }; } // Handle extensions and follow the same structure as the Core nls file. @@ -72,13 +92,15 @@ function main() { const key = manifestJson.publisher + '.' + manifestJson.name; return { [key]: parsedJson }; }, - })) + })); + const nlsMessagesJs = vfs.src('out-build/nls.messages.js', { base: 'out-build' }); + es.merge(combinedMetadataJson, nlsMessagesJs) .pipe(gzip({ append: false })) .pipe(vfs.dest('./nlsMetadata')) .pipe(es.through(function (data) { console.log(`Uploading ${data.path}`); // trigger artifact upload - console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=combined.nls.metadata.json]${data.path}`); + console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=${data.basename}]${data.path}`); this.emit('data', data); })) .pipe(azure.upload({ diff --git a/patched-vscode/build/azure-pipelines/upload-nlsmetadata.ts b/patched-vscode/build/azure-pipelines/upload-nlsmetadata.ts index 416d0eec..030cc8f0 100644 --- a/patched-vscode/build/azure-pipelines/upload-nlsmetadata.ts +++ b/patched-vscode/build/azure-pipelines/upload-nlsmetadata.ts @@ -24,79 +24,103 @@ interface NlsMetadata { function main(): Promise { return new Promise((c, e) => { + const combinedMetadataJson = es.merge( + // vscode: we are not using `out-build/nls.metadata.json` here because + // it includes metadata for translators for `keys`. but for our purpose + // we want only the `keys` and `messages` as `string`. + es.merge( + vfs.src('out-build/nls.keys.json', { base: 'out-build' }), + vfs.src('out-build/nls.messages.json', { base: 'out-build' })) + .pipe(merge({ + fileName: 'vscode.json', + jsonSpace: '', + concatArrays: true, + edit: (parsedJson, file) => { + if (file.base === 'out-build') { + if (file.basename === 'nls.keys.json') { + return { keys: parsedJson }; + } else { + return { messages: parsedJson }; + } + } + } + })), - es.merge( - vfs.src('out-vscode-web-min/nls.metadata.json', { base: 'out-vscode-web-min' }), + // extensions vfs.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vfs.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), - vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' })) - .pipe(merge({ - fileName: 'combined.nls.metadata.json', - jsonSpace: '', - concatArrays: true, - edit: (parsedJson, file) => { - if (file.base === 'out-vscode-web-min') { - return { vscode: parsedJson }; - } + vfs.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' }) + ).pipe(merge({ + fileName: 'combined.nls.metadata.json', + jsonSpace: '', + concatArrays: true, + edit: (parsedJson, file) => { + if (file.basename === 'vscode.json') { + return { vscode: parsedJson }; + } - // Handle extensions and follow the same structure as the Core nls file. - switch (file.basename) { - case 'package.nls.json': - // put package.nls.json content in Core NlsMetadata format - // language packs use the key "package" to specify that - // translations are for the package.json file - parsedJson = { - messages: { - package: Object.values(parsedJson) - }, - keys: { - package: Object.keys(parsedJson) - }, - bundles: { - main: ['package'] - } - }; - break; + // Handle extensions and follow the same structure as the Core nls file. + switch (file.basename) { + case 'package.nls.json': + // put package.nls.json content in Core NlsMetadata format + // language packs use the key "package" to specify that + // translations are for the package.json file + parsedJson = { + messages: { + package: Object.values(parsedJson) + }, + keys: { + package: Object.keys(parsedJson) + }, + bundles: { + main: ['package'] + } + }; + break; - case 'nls.metadata.header.json': - parsedJson = { header: parsedJson }; - break; + case 'nls.metadata.header.json': + parsedJson = { header: parsedJson }; + break; - case 'nls.metadata.json': { - // put nls.metadata.json content in Core NlsMetadata format - const modules = Object.keys(parsedJson); + case 'nls.metadata.json': { + // put nls.metadata.json content in Core NlsMetadata format + const modules = Object.keys(parsedJson); - const json: NlsMetadata = { - keys: {}, - messages: {}, - bundles: { - main: [] - } - }; - for (const module of modules) { - json.messages[module] = parsedJson[module].messages; - json.keys[module] = parsedJson[module].keys; - json.bundles.main.push(module); + const json: NlsMetadata = { + keys: {}, + messages: {}, + bundles: { + main: [] } - parsedJson = json; - break; + }; + for (const module of modules) { + json.messages[module] = parsedJson[module].messages; + json.keys[module] = parsedJson[module].keys; + json.bundles.main.push(module); } + parsedJson = json; + break; } + } - // Get extension id and use that as the key - const folderPath = path.join(file.base, file.relative.split('/')[0]); - const manifest = readFileSync(path.join(folderPath, 'package.json'), 'utf-8'); - const manifestJson = JSON.parse(manifest); - const key = manifestJson.publisher + '.' + manifestJson.name; - return { [key]: parsedJson }; - }, - })) + // Get extension id and use that as the key + const folderPath = path.join(file.base, file.relative.split('/')[0]); + const manifest = readFileSync(path.join(folderPath, 'package.json'), 'utf-8'); + const manifestJson = JSON.parse(manifest); + const key = manifestJson.publisher + '.' + manifestJson.name; + return { [key]: parsedJson }; + }, + })); + + const nlsMessagesJs = vfs.src('out-build/nls.messages.js', { base: 'out-build' }); + + es.merge(combinedMetadataJson, nlsMessagesJs) .pipe(gzip({ append: false })) .pipe(vfs.dest('./nlsMetadata')) .pipe(es.through(function (data: Vinyl) { console.log(`Uploading ${data.path}`); // trigger artifact upload - console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=combined.nls.metadata.json]${data.path}`); + console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=${data.basename}]${data.path}`); this.emit('data', data); })) .pipe(azure.upload({ diff --git a/patched-vscode/build/azure-pipelines/web/product-build-web.yml b/patched-vscode/build/azure-pipelines/web/product-build-web.yml index bf43d921..a522e845 100644 --- a/patched-vscode/build/azure-pipelines/web/product-build-web.yml +++ b/patched-vscode/build/azure-pipelines/web/product-build-web.yml @@ -129,6 +129,15 @@ steps: node build/azure-pipelines/upload-cdn displayName: Upload to CDN + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ + node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.js.map + displayName: Upload sourcemaps (Web) + # upload only the workbench.web.main.js source maps because # we just compiled these bits in the previous step and the # general task to upload source maps has already been run @@ -139,11 +148,11 @@ steps: AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.js.map - displayName: Upload sourcemaps (Web) + displayName: Upload sourcemaps (Deprecated) - script: | set -e - AZURE_STORAGE_ACCOUNT="ticino" \ + AZURE_STORAGE_ACCOUNT="vscodeweb" \ AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ diff --git a/patched-vscode/build/azure-pipelines/win32/product-build-win32-test.yml b/patched-vscode/build/azure-pipelines/win32/product-build-win32-test.yml index a3b251b7..fb3f3f4d 100644 --- a/patched-vscode/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/patched-vscode/build/azure-pipelines/win32/product-build-win32-test.yml @@ -12,6 +12,9 @@ parameters: - name: PUBLISH_TASK_NAME type: string default: PublishPipelineArtifact@0 + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - powershell: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" @@ -22,30 +25,48 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: .\scripts\test.bat --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - powershell: yarn test-node - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - powershell: node test/unit/browser/index.js --sequential --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - powershell: .\scripts\test-esm.bat --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - powershell: yarn test-node-esm + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - powershell: node test/unit/browser/index.esm.js --sequential --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - powershell: .\scripts\test.bat --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - powershell: yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - powershell: node test/unit/browser/index.js --sequential --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: .\scripts\test.bat --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - - - powershell: yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - - - powershell: yarn test-browser-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - powershell: .\scripts\test-esm.bat --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) [ESM] + timeoutInMinutes: 15 + - script: yarn test-node-esm --build + displayName: Run unit tests (node.js) [ESM] + timeoutInMinutes: 15 + - powershell: yarn test-browser-esm-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - powershell: .\scripts\test.bat --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + - powershell: yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + - powershell: yarn test-browser-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - powershell: | @@ -61,7 +82,6 @@ steps: compile-extension:ipynb ` compile-extension:notebook-renderers ` compile-extension:json-language-features-server ` - compile-extension:markdown-language-features-server ` compile-extension:markdown-language-features ` compile-extension-media ` compile-extension:microsoft-authentication ` @@ -72,10 +92,20 @@ steps: } displayName: Build integration tests + - powershell: .\build\azure-pipelines\win32\listprocesses.bat + displayName: Diagnostics before integration test runs + continueOnError: true + condition: succeededOrFailed() + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: .\scripts\test-integration.bat --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - powershell: .\scripts\test-integration-esm.bat --tfs "Integration Tests" + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - powershell: .\scripts\test-integration.bat --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - powershell: .\scripts\test-web-integration.bat --browser firefox displayName: Run integration tests (Browser, Firefox) @@ -86,20 +116,36 @@ steps: timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)" - exec { .\scripts\test-integration.bat --build --tfs "Integration Tests" } - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}: + - powershell: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)" + exec { .\scripts\test-integration-esm.bat --build --tfs "Integration Tests" } + displayName: Run integration tests (Electron) [ESM] + timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}: + - powershell: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)" + exec { .\scripts\test-integration.bat --build --tfs "Integration Tests" } + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -121,6 +167,11 @@ steps: displayName: Run integration tests (Remote) timeoutInMinutes: 20 + - powershell: .\build\azure-pipelines\win32\listprocesses.bat + displayName: Diagnostics after integration test runs + continueOnError: true + condition: succeededOrFailed() + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - powershell: .\build\azure-pipelines\win32\listprocesses.bat displayName: Diagnostics before smoke test run diff --git a/patched-vscode/build/azure-pipelines/win32/product-build-win32.yml b/patched-vscode/build/azure-pipelines/win32/product-build-win32.yml index d3827b93..a67e0ccf 100644 --- a/patched-vscode/build/azure-pipelines/win32/product-build-win32.yml +++ b/patched-vscode/build/azure-pipelines/win32/product-build-win32.yml @@ -11,6 +11,9 @@ parameters: type: boolean - name: VSCODE_RUN_SMOKE_TESTS type: boolean + - name: VSCODE_BUILD_ESM + type: boolean + default: false steps: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -160,7 +163,6 @@ steps: env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build server - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -171,7 +173,6 @@ steps: env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build server (web) - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-win32-test.yml@self @@ -181,6 +182,7 @@ steps: VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} + VSCODE_BUILD_ESM: ${{ parameters.VSCODE_BUILD_ESM }} ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 @@ -312,7 +314,7 @@ steps: sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH) sbomPackageName: "VS Code Windows $(VSCODE_ARCH) Server" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], ''), ne(variables['VSCODE_ARCH'], 'arm64')) + condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) displayName: Publish server archive - task: 1ES.PublishPipelineArtifact@1 @@ -322,7 +324,7 @@ steps: sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH)-web sbomPackageName: "VS Code Windows $(VSCODE_ARCH) Web" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], ''), ne(variables['VSCODE_ARCH'], 'arm64')) + condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) displayName: Publish web server archive - task: 1ES.PublishPipelineArtifact@1 diff --git a/patched-vscode/build/azure-pipelines/win32/sdl-scan-win32.yml b/patched-vscode/build/azure-pipelines/win32/sdl-scan-win32.yml new file mode 100644 index 00000000..81c0258f --- /dev/null +++ b/patched-vscode/build/azure-pipelines/win32/sdl-scan-win32.yml @@ -0,0 +1,162 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_QUALITY + type: string + +steps: + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download + + - task: UsePythonVersion@0 + inputs: + versionSpec: "3.x" + addToPath: true + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - powershell: node build/setup-npm-registry.js $env:NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm config set registry "$env:NPM_REGISTRY" --location=project } + # npm >v7 deprecated the `always-auth` config option, refs npm/cli@72a7eeb + # following is a workaround for yarn to send authorization header + # for GET requests to the registry. + exec { Add-Content -Path .npmrc -Value "always-auth=true" } + exec { yarn config set registry "$env:NPM_REGISTRY" } + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - pwsh: | + $includes = @' + { + 'target_defaults': { + 'conditions': [ + ['OS=="win"', { + 'msvs_settings': { + 'VCCLCompilerTool': { + 'AdditionalOptions': [ + '/Zi', + '/FS' + ], + }, + 'VCLinkerTool': { + 'AdditionalOptions': [ + '/profile' + ] + } + } + }] + ] + } + } + '@ + + if (!(Test-Path "~/.gyp")) { + mkdir "~/.gyp" + } + echo $includes > "~/.gyp/include.gypi" + displayName: Create include.gypi + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + . build/azure-pipelines/win32/retry.ps1 + $ErrorActionPreference = "Stop" + retry { exec { yarn --frozen-lockfile --check-files } } + env: + npm_config_arch: ${{ parameters.VSCODE_ARCH }} + CHILD_CONCURRENCY: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + + - script: node build/azure-pipelines/distro/mixin-npm + displayName: Mixin distro node modules + + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + env: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + + - powershell: yarn compile + displayName: Compile + + - powershell: | + Get-ChildItem '$(Build.SourcesDirectory)' -Recurse -Filter "*.exe" + Get-ChildItem '$(Build.SourcesDirectory)' -Recurse -Filter "*.dll" + Get-ChildItem '$(Build.SourcesDirectory)' -Recurse -Filter "*.node" + Get-ChildItem '$(Build.SourcesDirectory)' -Recurse -Filter "*.pdb" + displayName: List files + + - powershell: yarn gulp "vscode-symbols-win32-${{ parameters.VSCODE_ARCH }}" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download Symbols + + - powershell: | + Get-ChildItem '$(Agent.BuildDirectory)\scanbin' -Recurse -Filter "*.exe" + Get-ChildItem '$(Agent.BuildDirectory)\scanbin' -Recurse -Filter "*.dll" + Get-ChildItem '$(Agent.BuildDirectory)\scanbin' -Recurse -Filter "*.node" + Get-ChildItem '$(Agent.BuildDirectory)\scanbin' -Recurse -Filter "*.pdb" + displayName: List files again + + - task: BinSkim@4 + inputs: + InputType: "Basic" + Function: "analyze" + TargetPattern: "guardianGlob" + AnalyzeIgnorePdbLoadError: true + AnalyzeTargetGlob: '$(Agent.BuildDirectory)\scanbin\**.dll;$(Agent.BuildDirectory)\scanbin\**.exe;$(Agent.BuildDirectory)\scanbin\**.node' + AnalyzeLocalSymbolDirectories: '$(Agent.BuildDirectory)\scanbin\VSCode-win32-${{ parameters.VSCODE_ARCH }}\pdb' + + - task: CopyFiles@2 + displayName: 'Collect Symbols for API Scan' + inputs: + SourceFolder: $(Agent.BuildDirectory) + Contents: 'scanbin\**\*.pdb' + TargetFolder: '$(Agent.BuildDirectory)\symbols' + flattenFolders: true + condition: succeeded() + + - task: APIScan@2 + inputs: + softwareFolder: $(Agent.BuildDirectory)\scanbin + softwareName: 'vscode-client' + softwareVersionNum: '1' + symbolsFolder: 'srv*https://symweb.azurefd.net;$(Agent.BuildDirectory)\symbols' + isLargeApp: false + toolVersion: 'Latest' + azureSubscription: 'vscode-apiscan' + displayName: Run ApiScan + condition: succeeded() + env: + AzureServicesAuthConnectionString: $(apiscan-connectionstring) + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + + - task: PublishSecurityAnalysisLogs@3 + inputs: + ArtifactName: CodeAnalysisLogs + ArtifactType: Container + PublishProcessedResults: false + AllTools: true diff --git a/patched-vscode/build/buildfile.js b/patched-vscode/build/buildfile.js new file mode 100644 index 00000000..74ac8436 --- /dev/null +++ b/patched-vscode/build/buildfile.js @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const { isESM } = require('./lib/esm'); + +/** + * @param {string} name + * @param {string[]=} exclude + * @returns {import('./lib/bundle').IEntryPoint} + */ +function createModuleDescription(name, exclude) { + + let excludes = ['vs/css']; + if (Array.isArray(exclude) && exclude.length > 0) { + excludes = excludes.concat(exclude); + } + + return { + name: name, + include: [], + exclude: excludes + }; +} + +/** + * @param {string} name + */ +function createEditorWorkerModuleDescription(name) { + const amdVariant = createModuleDescription(name, ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']); + amdVariant.target = 'amd'; + + const esmVariant = { ...amdVariant, dest: undefined }; + esmVariant.target = 'esm'; + esmVariant.name = `${esmVariant.name}.esm`; + + return [amdVariant, esmVariant]; +} + +// TODO@esm take the editor simple worker top level and rename away from "base" +exports.base = [ + { + name: 'vs/editor/common/services/editorSimpleWorker', + include: ['vs/base/common/worker/simpleWorker'], + exclude: [], + prepend: [ + { path: 'vs/loader.js' }, + { path: 'vs/base/worker/workerMain.js' } + ], + dest: 'vs/base/worker/workerMain.js', + target: 'amd' + }, + { + name: 'vs/editor/common/services/editorSimpleWorker.esm', + target: 'esm' + }, + { + name: 'vs/base/common/worker/simpleWorker', + exclude: [], + target: 'amd' + } +]; + +exports.workerExtensionHost = createEditorWorkerModuleDescription('vs/workbench/api/worker/extensionHostWorker'); +exports.workerNotebook = createEditorWorkerModuleDescription('vs/workbench/contrib/notebook/common/services/notebookSimpleWorker'); +exports.workerLanguageDetection = createEditorWorkerModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker'); +exports.workerLocalFileSearch = createEditorWorkerModuleDescription('vs/workbench/services/search/worker/localFileSearch'); +exports.workerProfileAnalysis = createEditorWorkerModuleDescription('vs/platform/profiling/electron-sandbox/profileAnalysisWorker'); +exports.workerOutputLinks = createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'); +exports.workerBackgroundTokenization = createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'); + +exports.workbenchDesktop = function () { + return isESM() ? [ + createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), + createModuleDescription('vs/platform/files/node/watcher/watcherMain'), + createModuleDescription('vs/platform/terminal/node/ptyHostMain'), + createModuleDescription('vs/workbench/api/node/extensionHostProcess'), + createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain'), + createModuleDescription('vs/workbench/workbench.desktop.main') + ] : [ + ...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), + ...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'), + createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), + createModuleDescription('vs/platform/files/node/watcher/watcherMain'), + createModuleDescription('vs/platform/terminal/node/ptyHostMain'), + createModuleDescription('vs/workbench/api/node/extensionHostProcess'), + createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain'), + ]; +}; + +exports.workbenchWeb = function () { + return isESM() ? [ + createModuleDescription('vs/workbench/workbench.web.main') + ] : [ + ...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), + ...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'), + createModuleDescription('vs/code/browser/workbench/workbench', ['vs/workbench/workbench.web.main']) + ]; +}; + +exports.keyboardMaps = [ + createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux'), + createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin'), + createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win') +]; + +exports.code = [ + createModuleDescription('vs/code/electron-main/main'), + createModuleDescription('vs/code/node/cli'), + createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), + createModuleDescription('vs/code/node/sharedProcess/sharedProcessMain'), + createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain') +]; + +exports.codeWeb = [ + createModuleDescription('vs/code/browser/workbench/workbench') +]; + +exports.entrypoint = createModuleDescription; diff --git a/patched-vscode/build/checksums/electron.txt b/patched-vscode/build/checksums/electron.txt index a80aa153..aecf657e 100644 --- a/patched-vscode/build/checksums/electron.txt +++ b/patched-vscode/build/checksums/electron.txt @@ -1,75 +1,75 @@ -3d3d8bb185d7b63b0db910661fdd69d6381afb8c97742bbd2526a9c932e1f8ca *chromedriver-v29.4.0-darwin-arm64.zip -c3d075943d87604ffa50382cc8d5798485349544ca391cab88c892f889d3b14c *chromedriver-v29.4.0-darwin-x64.zip -6d62d2dba55e4419fa003d45f93dad1324ec29a4d3eb84fd9fd5fd7a64339389 *chromedriver-v29.4.0-linux-arm64.zip -81bb3d362331c7296f700b1b0e8f07c4c7739b1151f698cd56af927bedda59e7 *chromedriver-v29.4.0-linux-armv7l.zip -ab593cc39aefac8c5abd259e31f6add4b2b70c52231724a6c08ac1872b4a0edf *chromedriver-v29.4.0-linux-x64.zip -705d42ccc05b2c48b0673b9dcf63eb78772bb79dba078a523d384ed2481bc9c0 *chromedriver-v29.4.0-mas-arm64.zip -956a7caa28eeeb0c02eb7638a53215ffd89b4f12880f0893ff10f497ca1a8117 *chromedriver-v29.4.0-mas-x64.zip -1f070176aa33e0139d61a3d758fd2f015f09bb275577293fe93564749b6310ba *chromedriver-v29.4.0-win32-arm64.zip -38a71526d243bcb73c28cb648bd4816d70b5e643df52f9f86a83416014589744 *chromedriver-v29.4.0-win32-ia32.zip -f90750d3589cb3c9f6f0ebc70d5e025cf81c382e8c23fa47a54570696a478ef0 *chromedriver-v29.4.0-win32-x64.zip -05dffc90dd1341cc7a6b50127985e4e217fef7f50a173c7d0ff34039dd2d81b6 *electron-api.json -7f63f7cf675ba6dec3a5e4173d729bd53c75f81e612f809641d9d0c4d9791649 *electron-v29.4.0-darwin-arm64-dsym-snapshot.zip -aa29530fcafa4db364978d4f414a6ec2005ea695f7fee70ffbe5e114e9e453f0 *electron-v29.4.0-darwin-arm64-dsym.zip -8d12fb6d9bcdf5bbfc93dbcd1cac348735dc6f98aa450ee03ec7837a01a8a938 *electron-v29.4.0-darwin-arm64-symbols.zip -c16d05f1231bb3c77da05ab236b454b3a2b6a642403be51e7c9b16cd2c421a19 *electron-v29.4.0-darwin-arm64.zip -2dfc1017831ab2f6e9ddb575d3b9cff5a0d56f16a335a3c0df508e964e2db963 *electron-v29.4.0-darwin-x64-dsym-snapshot.zip -025de6aa39d98762928e1b700f46177e74be20101b27457659b938e2c69db326 *electron-v29.4.0-darwin-x64-dsym.zip -ec4eb0a618207233985ceaab297be34b3d4f0813d88801d5637295b238dd661a *electron-v29.4.0-darwin-x64-symbols.zip -8ed7924f77a5c43c137a57097c5c47c2e8e9a78197e18af11a767c98035c123e *electron-v29.4.0-darwin-x64.zip -bde1772fa8ac4850e108012a9edd3bd93472bad8f68ddd55fca355dad81dde4f *electron-v29.4.0-linux-arm64-debug.zip -dfe7852a7423196efb2205c788d942db3ffc9de6ce52577e173bcf7ca6973d48 *electron-v29.4.0-linux-arm64-symbols.zip -c3764d6c3799950e3418e8e5a5a5b2c41abe421dd8bcdebf054c7c85798d9860 *electron-v29.4.0-linux-arm64.zip -bde1772fa8ac4850e108012a9edd3bd93472bad8f68ddd55fca355dad81dde4f *electron-v29.4.0-linux-armv7l-debug.zip -360668ba669cb2c01c2f960cdee76c29670e6ce907ccc0718e971a04af594ce9 *electron-v29.4.0-linux-armv7l-symbols.zip -c5e92943ad78b4e41a32ae53c679e148ea2ae09f95f914b1834dbdbae578ba91 *electron-v29.4.0-linux-armv7l.zip -375be885426bcbd272bd068bfcef41a83296c2f8e61e633233d2a9e9a69242fc *electron-v29.4.0-linux-x64-debug.zip -847e0f75624616c2918b33de2eefeec63419bd250685610d3f52fa115527d2b9 *electron-v29.4.0-linux-x64-symbols.zip -91e5eb374c2c85a07c2d4e99a89eb18515ff0169a49c3fa75289800e1225729e *electron-v29.4.0-linux-x64.zip -098f973537c3d9679a69409d0b84bcc1a6113bb2002ee60068e2c22f335a3855 *electron-v29.4.0-mas-arm64-dsym-snapshot.zip -2724aa32eb441eea21680d95fc1efdd75ac473fa19623c7acf3d546419e96154 *electron-v29.4.0-mas-arm64-dsym.zip -98dd81914752a57da4cbaad1f0aa94b16335f9b8f997be9aa049be90b96b2886 *electron-v29.4.0-mas-arm64-symbols.zip -fd2663f65c1f995304e3eb65870b7146adfefef07cf82bf44de75855fd4f36e8 *electron-v29.4.0-mas-arm64.zip -237983b2169e69bb73aa0987e871e3e486755904b71ebe36c3e902377f92754a *electron-v29.4.0-mas-x64-dsym-snapshot.zip -a5d59599827d32ef322b99eee8416e39235f4c7a0ada78342a885665e0b732dd *electron-v29.4.0-mas-x64-dsym.zip -5182e7697ac0591e0b95c33f70316af24093c9100f442be2cee0039660e959ac *electron-v29.4.0-mas-x64-symbols.zip -e0ee7057aff0240a70b9ed75ff44d55aeae9af67fbc8915f741711a8bb6fe744 *electron-v29.4.0-mas-x64.zip -2802872dfc6de0f0e2e8cef9d2f4f384e3d82b20ad36fc981c4e725dd2f2abcd *electron-v29.4.0-win32-arm64-pdb.zip -d49c954dc25ae9e4c75e61af80b9718014c52f016f43a29071913f0e7100c7bd *electron-v29.4.0-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v29.4.0-win32-arm64-toolchain-profile.zip -483d692efbe4fb1231ff63afb8a236b2e22b486fbe5ac6abbc8b208abf94a4d3 *electron-v29.4.0-win32-arm64.zip -98458f49ba67a08e473d475a68a2818d9df076a5246fbc9b45403e8796f9d35b *electron-v29.4.0-win32-ia32-pdb.zip -69d505d4ae59d9dddf83c4e530e45dd7c5bc64d6da90cf4f851e523be9e51014 *electron-v29.4.0-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v29.4.0-win32-ia32-toolchain-profile.zip -d5a21a17a64e9638f49f057356af23b51f56bd6a7fea3c2e0a28ff3186a7bc41 *electron-v29.4.0-win32-ia32.zip -521ee7b3398c4dc395b43dac86cd099e86a6123de2b43636ee805b7da014ed3f *electron-v29.4.0-win32-x64-pdb.zip -e33848ebd6c6e4ce431aa367bef887050947a136e883677cfc524ca5cabc1e98 *electron-v29.4.0-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v29.4.0-win32-x64-toolchain-profile.zip -e4ef85aa3608221f8a3e011c1b1c2d2d36093ad19bda12d16b3816929fb6c99b *electron-v29.4.0-win32-x64.zip -707ee08593289ee83514b4fc55123611309f995788f38a5ec03e285741aac1c8 *electron.d.ts -281b5f4a49de55fdb86b1662530f07f2ced1252c878eb7a941c88ede545339e0 *ffmpeg-v29.4.0-darwin-arm64.zip -0b735912df9b2ff3d03eb23942e03bc0116d82f1291d0a45cbde14177c2f3066 *ffmpeg-v29.4.0-darwin-x64.zip -4e2ba537d7c131abbd34168bce2c28cc9ef6262b217d5f4085afccfdf9635da6 *ffmpeg-v29.4.0-linux-arm64.zip -4aa56ad5d849f4e61af22678a179346b68aec9100282e1b8a43df25d95721677 *ffmpeg-v29.4.0-linux-armv7l.zip -0558e6e1f78229d303e16d4d8c290794baa9adc619fdd2ddccadb3ea241a1df4 *ffmpeg-v29.4.0-linux-x64.zip -224f15d8f96c75348cd7f1b85c4eab63468fae1e50ff4b1381e08011cf76e4f7 *ffmpeg-v29.4.0-mas-arm64.zip -175ec79f0dc4c5966d9a0ca6ec1674106340ecc64503585c12c2f854249af06f *ffmpeg-v29.4.0-mas-x64.zip -5fa13744b87fef1bfd24a37513677f446143e085504541f8ce97466803bd1893 *ffmpeg-v29.4.0-win32-arm64.zip -d7ba316bb7e13025c9db29e0acafebb540b7716c9f111e469733615d8521186a *ffmpeg-v29.4.0-win32-ia32.zip -35c70a28bcfd4f0b1f8c985d3d1348936bd60767d231ce28ba38f3daeeef64bb *ffmpeg-v29.4.0-win32-x64.zip -8c7228ea0ecab25a1f7fcd1ba9680684d19f9671a497113d71a851a53867b048 *hunspell_dictionaries.zip -7552547c8d585b9bc43518d239d7ce3ad7c5cad0346b07cdcfc1eab638b2b794 *libcxx-objects-v29.4.0-linux-arm64.zip -76054a779d4845ad752b625213ce8990f08dcc5b89aa20660dd4f2e817ba30a8 *libcxx-objects-v29.4.0-linux-armv7l.zip -761c317a9c874bd3d1118d0ecad33c4be23727f538cfbb42a08dd87c68da6039 *libcxx-objects-v29.4.0-linux-x64.zip -f98f9972cc30200b8e05815f5a9cd5cec04bdeee0e48ae2143cdaeff5db9d71d *libcxx_headers.zip -f0b0dd2be579baaf97901322ef489d03fae69a0b8524ea77b24fb3c896f73dd9 *libcxxabi_headers.zip -5da864ea23d70538298a40e0d037a5a461a6b74984e72fd4f0cd20904bccaed1 *mksnapshot-v29.4.0-darwin-arm64.zip -bde97bd7c69209ed6bf4cf1cdf7de622e3a9f50fe6b4dc4b5618eee868f47c62 *mksnapshot-v29.4.0-darwin-x64.zip -a3df9b9e6ef14efe5827d0256d8ecaebe6d8be130cfc3faac0dea76eb53b9b11 *mksnapshot-v29.4.0-linux-arm64-x64.zip -648b9dbca21194d663ddb706e6086a166e691263c764c80f836ae02c27e3657a *mksnapshot-v29.4.0-linux-armv7l-x64.zip -e7a4201cda3956380facc2b5b9d0b1020cc5e654fba44129fc7429a982411cc1 *mksnapshot-v29.4.0-linux-x64.zip -ffb44c45733675e0378f45fce25dafa95697d0c86179f8e46742ada16bc11aa1 *mksnapshot-v29.4.0-mas-arm64.zip -0242da3ca193206e56b88eb108502244bae35dcc587210bd0a32d9fa4cb71041 *mksnapshot-v29.4.0-mas-x64.zip -1445806dca6effbc60072bbde7997cefb62bdb7a9e295a090d26f27c3882685f *mksnapshot-v29.4.0-win32-arm64-x64.zip -09599adc3afb0a13ae87fc4b8ab97c729fe3689faa6a4f5f7a4a3cf0d9cc49d3 *mksnapshot-v29.4.0-win32-ia32.zip -84f80683d95665d29284386509bb104e840ff0b797bfbbd19da86b84d370aa49 *mksnapshot-v29.4.0-win32-x64.zip +3e432bd550279c2d58c6a02b0bda048fa2a9a89aef9752e22c39a518a83fe96f *chromedriver-v30.4.0-darwin-arm64.zip +4d38f40ec33955dd0aef5cc0053b2b07c4744abb9f2614a44780280c4678aca9 *chromedriver-v30.4.0-darwin-x64.zip +338f2a361448e78aa8c319cd582f2dd6d891d6c0c66b676c628e50511c27514f *chromedriver-v30.4.0-linux-arm64.zip +8cffae19618fe5f0eaff40378c40084e18f9747bbbe2d87390203a3012c98367 *chromedriver-v30.4.0-linux-armv7l.zip +7ff1b75defc522f327c0f1bf6f4d13938b041cf5de1d7417b9dc5d1e7026e93c *chromedriver-v30.4.0-linux-x64.zip +8e93b0892d7d01c38608fa7907f28da1da27de09bb382f261d81a676f8c2ba64 *chromedriver-v30.4.0-mas-arm64.zip +d306ef0bc651bef046b21ba2e4745e2768be9df1b6c2fc2e7b1a0368f7e10d76 *chromedriver-v30.4.0-mas-x64.zip +bb7c5db2a20f437e0b398846ecc8111e7e51e2021b78f60db021262daacf7d7c *chromedriver-v30.4.0-win32-arm64.zip +df4acaa6684e7db07468c6dc8102cb49746884e8db2b53648431b2b61e8debb2 *chromedriver-v30.4.0-win32-ia32.zip +7a207b29977e52ba052037f00f4ea6284177fdd93e0774dfc39e83ee5a86513d *chromedriver-v30.4.0-win32-x64.zip +8c876662fd46c37b2dd9487ea8e62fde3f20a729dc95e42f12f7db4986138edc *electron-api.json +a36786c7d4e30887a6aa1f2adbc23e721266a31ffe84191bf4caddb731cfb973 *electron-v30.4.0-darwin-arm64-dsym-snapshot.zip +e5edcad107307e97bdd6f69ccab0368df4c3270213f2f42f259f3723d24ca611 *electron-v30.4.0-darwin-arm64-dsym.zip +cc522e772a1d80f7ec73ce2077dae93de76cd4f6767c467b6a8e01e4a252f15f *electron-v30.4.0-darwin-arm64-symbols.zip +2238c45a85b2c78aed00aeaf15bbfa2f64b4d13e48cc6b9bc330f24c4d214595 *electron-v30.4.0-darwin-arm64.zip +48c3197fc36f5ef64eaa913a0a769d2b2d4b79c94e61991ca73e8be2b24bf997 *electron-v30.4.0-darwin-x64-dsym-snapshot.zip +9b669dc060695e5495573cce7b7916bdadd980d523772fbbf57fd1811bb4feca *electron-v30.4.0-darwin-x64-dsym.zip +0acd5b74cbb1719a89a310fb42f5ab4282711bb0ccf4e8b242d9c24c8ecc7136 *electron-v30.4.0-darwin-x64-symbols.zip +6e633bf87be9f8bf46dff9733cfd0d611e018ae5df75f30735747721f91fcf43 *electron-v30.4.0-darwin-x64.zip +5bc27ceae706116a14fa24da3cda32f00893dd3f51ae2c2d58c4c08f26ce2ac2 *electron-v30.4.0-linux-arm64-debug.zip +d13970ef6f4ad30dd8268563ed0b768f703d6e4ea1389ee36b8e7eb407b40523 *electron-v30.4.0-linux-arm64-symbols.zip +aa422122373b84f4eb8ce937937b1b6fe8fb3975c3edafb9df85f7fba449afd4 *electron-v30.4.0-linux-arm64.zip +5bc27ceae706116a14fa24da3cda32f00893dd3f51ae2c2d58c4c08f26ce2ac2 *electron-v30.4.0-linux-armv7l-debug.zip +645af6535a3956e263d8ff586bfb45c5d16ec4c07add406e7c2a767eec070e8e *electron-v30.4.0-linux-armv7l-symbols.zip +3643857e1eec3037ad6f07e755bffc64f033a7196307ff0386bf67c9cc3ec31e *electron-v30.4.0-linux-armv7l.zip +0f099b597830fd990bac82fbe976720d5610450133dff880b570cbe1ef793742 *electron-v30.4.0-linux-x64-debug.zip +084566b78797502ad164832e49690d8845c53a8becb28085f271ab1a9fe20942 *electron-v30.4.0-linux-x64-symbols.zip +b365aac23c61dc0b18002c60937c4842e814cbe6d8e6a34e4dc211774ebaec01 *electron-v30.4.0-linux-x64.zip +b050b4de0fda4c2fa4ceeb0ecaf452cc426a9b03a9e286686d2167c7ded252f4 *electron-v30.4.0-mas-arm64-dsym-snapshot.zip +1e9c3f820e809fc2e05434d54862c7309dd04b74406869e06edb1e07faa3e40f *electron-v30.4.0-mas-arm64-dsym.zip +9b4112c73606d1a0393320178a3d0f172671ec211fe69b2106ad9b04b55b3538 *electron-v30.4.0-mas-arm64-symbols.zip +10ac65b581c2941fd05b2804055be2eed8d69de4c82b90f7357a3989d8276c9f *electron-v30.4.0-mas-arm64.zip +9e4cba132474d82894541ab33d02b28894f0452f4c6808e2428878d0b6a55aac *electron-v30.4.0-mas-x64-dsym-snapshot.zip +33c9c13a8a33ab292db5f14b1d8af1fa7967601badf505a06972fd3cdc9cb6bb *electron-v30.4.0-mas-x64-dsym.zip +1e8871c34294912bc562861226742dcd2de073ae2dc3fef8065fe86550265bca *electron-v30.4.0-mas-x64-symbols.zip +055bea26dfd9fa86838e25b77e29fec3967ed21020ce1362ff683be07ac90c45 *electron-v30.4.0-mas-x64.zip +1fd654358ad895fce4806fcd39be848deeccd20f767e96006ebb5c88ca973d92 *electron-v30.4.0-win32-arm64-pdb.zip +5cf51db3ece15f155afc066230f844bf7825fe912f0f479b8cf9eb47e46a2278 *electron-v30.4.0-win32-arm64-symbols.zip +7351fa5cb892853a7d4b67d8858d0f9cc6506c554a2e42c9ad7e8d5e29ae2743 *electron-v30.4.0-win32-arm64-toolchain-profile.zip +51bc3a21faefe99ccc8e8670dccf2320249af5c185728a31d907a18092542d73 *electron-v30.4.0-win32-arm64.zip +b4a539c7dbad3db544d7709faaf357d55d7f3349bfd4d42c884b0608725b526d *electron-v30.4.0-win32-ia32-pdb.zip +ddb355e3e45978eee33c679c42781587533b934f3e98374057d2b0c212062915 *electron-v30.4.0-win32-ia32-symbols.zip +7351fa5cb892853a7d4b67d8858d0f9cc6506c554a2e42c9ad7e8d5e29ae2743 *electron-v30.4.0-win32-ia32-toolchain-profile.zip +7260c9f533d5fa0c543d34593bb1a8bc4904943ca872afc5246e15da6f00b43a *electron-v30.4.0-win32-ia32.zip +2d0d5631158971e0fac9bb7c427c5361177c7e967cca41940ad18183d95c3e23 *electron-v30.4.0-win32-x64-pdb.zip +0ea8c0b8734bd45cb04ae83390bd7efee407ed6bb30dbaa1030e1a1f03a0f025 *electron-v30.4.0-win32-x64-symbols.zip +7351fa5cb892853a7d4b67d8858d0f9cc6506c554a2e42c9ad7e8d5e29ae2743 *electron-v30.4.0-win32-x64-toolchain-profile.zip +9dcf4b5bbdb6c672ba1b3812d4b642999faf4b6f4e2a4e4af314ba61596a62dc *electron-v30.4.0-win32-x64.zip +c524777653f0b3ba58da6c1e97aa5a3a172caf68d9b24a7d33f733b36c281ce8 *electron.d.ts +c1586ee3f3960943dfa1c1a3da2ebc327afd95c3c7a160b0646730fbc78731bb *ffmpeg-v30.4.0-darwin-arm64.zip +7f76a792c8f9ee9611f966956b4b70017253f9357ec73806d35c568808eec470 *ffmpeg-v30.4.0-darwin-x64.zip +5160348bcae6cb4fce20905338a903475c2eb52511370b44bdfeafef3cbeab6c *ffmpeg-v30.4.0-linux-arm64.zip +4f7583513d48b48c44a2cbc4430cbc9a33d8f9728622166db688e3de61190821 *ffmpeg-v30.4.0-linux-armv7l.zip +4f62d246a2ccb904e9f03bdc65fd5806edd69b95099fa2dcba4f148d44ea1bce *ffmpeg-v30.4.0-linux-x64.zip +c1586ee3f3960943dfa1c1a3da2ebc327afd95c3c7a160b0646730fbc78731bb *ffmpeg-v30.4.0-mas-arm64.zip +7f76a792c8f9ee9611f966956b4b70017253f9357ec73806d35c568808eec470 *ffmpeg-v30.4.0-mas-x64.zip +26b82e5943d09eb0765804df27182cd45c45e63e82fe95d60a5a78611d85a926 *ffmpeg-v30.4.0-win32-arm64.zip +ea688b108cb545bd436621c8d1279696ac6cb0b041d1d3d322dba06f8dd0f295 *ffmpeg-v30.4.0-win32-ia32.zip +733a522d2095964ff3796f3301591b7aa9cf1ba8dcc545d1928745022ef99e6a *ffmpeg-v30.4.0-win32-x64.zip +0b82c8db956264c1be4006eff3908b51ae8e48a17176fe61fa86d8ca8c103bf5 *hunspell_dictionaries.zip +27d5d6d82bd5703342e6a3804ff3c032433cda833ad3881f263035b23193ace9 *libcxx-objects-v30.4.0-linux-arm64.zip +babe51c929cc77907fdb5af11781938bab4b9f31642a0220b4ce4a35cfb904ff *libcxx-objects-v30.4.0-linux-armv7l.zip +e6e13dd7c493841c3ab406b147d021b4e5a379e286767c1459d23bd9581c10c9 *libcxx-objects-v30.4.0-linux-x64.zip +c9ba1b8c0e971cf477f7c0120436cc45a02b72908a41c82cd809ec8014b81e32 *libcxx_headers.zip +2c4c140bfeb235584f8f7c699d0d95b16ddb959484923aaa59eeb3310dbc161a *libcxxabi_headers.zip +0cee8848525e0d633d3ccf599d03ac05be6e0bc014b5a77ddce99c3a50941feb *mksnapshot-v30.4.0-darwin-arm64.zip +0c491f602cfc6fbd81b8b771d3fa28ec1eb77c23c7636479a158265dd7d59977 *mksnapshot-v30.4.0-darwin-x64.zip +6ea645627b0a0cab88cda80a2fa4d399a491bc5f0d0ff1d007f6dd28728d4592 *mksnapshot-v30.4.0-linux-arm64-x64.zip +6b33e8bc53281710d9754e3ea88a8c025649f3d7c499e9b8693183bd2b76ea8d *mksnapshot-v30.4.0-linux-armv7l-x64.zip +092afde6a2ab8ac922524a02b30343cd11f96c01405814019797bf38fa8c634b *mksnapshot-v30.4.0-linux-x64.zip +dada39480526a46992f2ff0ad8ea72a26565384e9976497867a6c4776705e49f *mksnapshot-v30.4.0-mas-arm64.zip +4082f5b379b788d300b2a7fc7c84deee80ebc61a34b534c795c1b4594c3f0d1a *mksnapshot-v30.4.0-mas-x64.zip +01a5e83fe293dd2c52e1d5cc79450fbfc1dfa1e4988146f94bfbf9a9e24cb9d7 *mksnapshot-v30.4.0-win32-arm64-x64.zip +0bccaa2779f8ce98bd4a2a66be082bcd3c34484aad8000c08de0fed5acb8ad88 *mksnapshot-v30.4.0-win32-ia32.zip +439c7f5b9fdd0ea19ee95f724773cda42382b27a31494ae8f2aca5c2ec043ec7 *mksnapshot-v30.4.0-win32-x64.zip diff --git a/patched-vscode/build/checksums/nodejs.txt b/patched-vscode/build/checksums/nodejs.txt index bcc93404..23a56cdb 100644 --- a/patched-vscode/build/checksums/nodejs.txt +++ b/patched-vscode/build/checksums/nodejs.txt @@ -1,7 +1,7 @@ -e0065c61f340e85106a99c4b54746c5cee09d59b08c5712f67f99e92aa44995d node-v20.11.1-darwin-arm64.tar.gz -c52e7fb0709dbe63a4cbe08ac8af3479188692937a7bd8e776e0eedfa33bb848 node-v20.11.1-darwin-x64.tar.gz -e34ab2fc2726b4abd896bcbff0250e9b2da737cbd9d24267518a802ed0606f3b node-v20.11.1-linux-arm64.tar.gz -e42791f76ece283c7a4b97fbf716da72c5128c54a9779f10f03ae74a4bcfb8f6 node-v20.11.1-linux-armv7l.tar.gz -bf3a779bef19452da90fb88358ec2c57e0d2f882839b20dc6afc297b6aafc0d7 node-v20.11.1-linux-x64.tar.gz -a5a9d30a8f7d56e00ccb27c1a7d24c8d0bc96a2689ebba8eb7527698793496f1 win-arm64/node.exe -bc585910690318aaebe3c57669cb83ca9d1e5791efd63195e238f54686e6c2ec win-x64/node.exe +4743bc042f90ba5d9edf09403207290a9cdd2f6061bdccf7caaa0bbfd49f343e node-v20.15.1-darwin-arm64.tar.gz +f5379772ffae1404cfd1fcc8cf0c6c5971306b8fb2090d348019047306de39dc node-v20.15.1-darwin-x64.tar.gz +8554c91ccd32782351035d3a9b168ad01c6922480800a21870fc5d6d86c2bb70 node-v20.15.1-linux-arm64.tar.gz +2c16717da7d2d7b00f6af146cdf436a0297cbcee52c85b754e4c9ed7cee34b51 node-v20.15.1-linux-armv7l.tar.gz +a9db028c0a1c63e3aa0d97de24b0966bc507d8239b3aedc4e752eea6b0580665 node-v20.15.1-linux-x64.tar.gz +8e3f84e8ec7e41f98a048eb0c1365cfe54426a556ead98c4803df45d29e0335d win-arm64/node.exe +229fb64aeb10d3cc18eaaa2f5a4c3f1c81792dd3647c5c4350e142db528d0f89 win-x64/node.exe diff --git a/patched-vscode/build/darwin/create-universal-app.js b/patched-vscode/build/darwin/create-universal-app.js index 7da8e55c..fe825b13 100644 --- a/patched-vscode/build/darwin/create-universal-app.js +++ b/patched-vscode/build/darwin/create-universal-app.js @@ -6,8 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); const path = require("path"); const fs = require("fs"); +const minimatch = require("minimatch"); const vscode_universal_bundler_1 = require("vscode-universal-bundler"); const cross_spawn_promise_1 = require("@malept/cross-spawn-promise"); +const esm_1 = require("../lib/esm"); const root = path.dirname(path.dirname(__dirname)); async function main(buildDir) { const arch = process.env['VSCODE_ARCH']; @@ -18,26 +20,30 @@ async function main(buildDir) { const appName = product.nameLong + '.app'; const x64AppPath = path.join(buildDir, 'VSCode-darwin-x64', appName); const arm64AppPath = path.join(buildDir, 'VSCode-darwin-arm64', appName); - const x64AsarPath = path.join(x64AppPath, 'Contents', 'Resources', 'app', 'node_modules.asar'); - const arm64AsarPath = path.join(arm64AppPath, 'Contents', 'Resources', 'app', 'node_modules.asar'); + const asarRelativePath = path.join('Contents', 'Resources', 'app', 'node_modules.asar'); const outAppPath = path.join(buildDir, `VSCode-darwin-${arch}`, appName); const productJsonPath = path.resolve(outAppPath, 'Contents', 'Resources', 'app', 'product.json'); + const filesToSkip = [ + '**/CodeResources', + '**/Credits.rtf', + ]; + const canAsar = !(0, esm_1.isESM)('ASAR disabled in universal build'); // TODO@esm ASAR disabled in ESM await (0, vscode_universal_bundler_1.makeUniversalApp)({ x64AppPath, arm64AppPath, - x64AsarPath, - arm64AsarPath, - filesToSkip: [ - 'product.json', - 'Credits.rtf', - 'CodeResources', - 'fsevents.node', - 'Info.plist', // TODO@deepak1556: regressed with 11.4.2 internal builds - 'MainMenu.nib', // Generated sequence is not deterministic with Xcode 13 - '.npmrc' - ], + asarPath: canAsar ? asarRelativePath : undefined, outAppPath, - force: true + force: true, + mergeASARs: canAsar, + x64ArchFiles: '*/kerberos.node', + filesToSkipComparison: (file) => { + for (const expected of filesToSkip) { + if (minimatch(file, expected)) { + return true; + } + } + return false; + } }); const productJson = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); Object.assign(productJson, { diff --git a/patched-vscode/build/darwin/create-universal-app.ts b/patched-vscode/build/darwin/create-universal-app.ts index ffba8952..6ece5868 100644 --- a/patched-vscode/build/darwin/create-universal-app.ts +++ b/patched-vscode/build/darwin/create-universal-app.ts @@ -5,8 +5,10 @@ import * as path from 'path'; import * as fs from 'fs'; +import * as minimatch from 'minimatch'; import { makeUniversalApp } from 'vscode-universal-bundler'; import { spawn } from '@malept/cross-spawn-promise'; +import { isESM } from '../lib/esm'; const root = path.dirname(path.dirname(__dirname)); @@ -21,27 +23,33 @@ async function main(buildDir?: string) { const appName = product.nameLong + '.app'; const x64AppPath = path.join(buildDir, 'VSCode-darwin-x64', appName); const arm64AppPath = path.join(buildDir, 'VSCode-darwin-arm64', appName); - const x64AsarPath = path.join(x64AppPath, 'Contents', 'Resources', 'app', 'node_modules.asar'); - const arm64AsarPath = path.join(arm64AppPath, 'Contents', 'Resources', 'app', 'node_modules.asar'); + const asarRelativePath = path.join('Contents', 'Resources', 'app', 'node_modules.asar'); const outAppPath = path.join(buildDir, `VSCode-darwin-${arch}`, appName); const productJsonPath = path.resolve(outAppPath, 'Contents', 'Resources', 'app', 'product.json'); + const filesToSkip = [ + '**/CodeResources', + '**/Credits.rtf', + ]; + + const canAsar = !isESM('ASAR disabled in universal build'); // TODO@esm ASAR disabled in ESM + await makeUniversalApp({ x64AppPath, arm64AppPath, - x64AsarPath, - arm64AsarPath, - filesToSkip: [ - 'product.json', - 'Credits.rtf', - 'CodeResources', - 'fsevents.node', - 'Info.plist', // TODO@deepak1556: regressed with 11.4.2 internal builds - 'MainMenu.nib', // Generated sequence is not deterministic with Xcode 13 - '.npmrc' - ], + asarPath: canAsar ? asarRelativePath : undefined, outAppPath, - force: true + force: true, + mergeASARs: canAsar, + x64ArchFiles: '*/kerberos.node', + filesToSkipComparison: (file: string) => { + for (const expected of filesToSkip) { + if (minimatch(file, expected)) { + return true; + } + } + return false; + } }); const productJson = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); diff --git a/patched-vscode/build/filters.js b/patched-vscode/build/filters.js index c7be2d81..b0f23d3f 100644 --- a/patched-vscode/build/filters.js +++ b/patched-vscode/build/filters.js @@ -77,7 +77,9 @@ module.exports.indentationFilter = [ '!src/vs/base/common/semver/semver.js', '!src/vs/base/node/terminateProcess.sh', '!src/vs/base/node/cpuUsage.sh', + '!src/vs/editor/common/languages/highlights/*.scm', '!test/unit/assert.js', + '!test/unit/assert-esm.js', '!resources/linux/snap/electron-launch', '!build/ext.js', '!build/npm/gyp/patches/gyp_spectre_mitigation_support.patch', @@ -117,7 +119,7 @@ module.exports.indentationFilter = [ '!src/vs/*/**/*.d.ts', '!src/typings/**/*.d.ts', '!extensions/**/*.d.ts', - '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist,opus,admx,adml,wasm}', + '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,psm1,template,yaml,yml,d.ts.recipe,ico,icns,plist,opus,admx,adml,wasm}', '!build/{lib,download,linux,darwin}/**/*.js', '!build/**/*.sh', '!build/azure-pipelines/**/*.js', @@ -199,7 +201,7 @@ module.exports.eslintFilter = [ .toString().split(/\r\n|\n/) .filter(line => !line.startsWith('#')) .filter(line => !!line) - .map(line => `!${line}`) + .map(line => line.startsWith('!') ? line.slice(1) : `!${line}`) ]; module.exports.stylelintFilter = [ diff --git a/patched-vscode/build/gulpfile.cli.js b/patched-vscode/build/gulpfile.cli.js index 86646fdb..592fc745 100644 --- a/patched-vscode/build/gulpfile.cli.js +++ b/patched-vscode/build/gulpfile.cli.js @@ -5,8 +5,6 @@ 'use strict'; -//@ts-check - const es = require('event-stream'); const gulp = require('gulp'); const path = require('path'); @@ -24,7 +22,6 @@ const createReporter = require('./lib/reporter').createReporter; const root = 'cli'; const rootAbs = path.resolve(__dirname, '..', root); const src = `${root}/src`; -const targetCliPath = path.join(root, 'target', 'debug', process.platform === 'win32' ? 'code.exe' : 'code'); const platformOpensslDirName = process.platform === 'win32' ? ( diff --git a/patched-vscode/build/gulpfile.compile.js b/patched-vscode/build/gulpfile.compile.js index c4947e76..c6d7bbde 100644 --- a/patched-vscode/build/gulpfile.compile.js +++ b/patched-vscode/build/gulpfile.compile.js @@ -3,20 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +//@ts-check 'use strict'; const gulp = require('gulp'); const util = require('./lib/util'); +const date = require('./lib/date'); +const esm = require('./lib/esm'); const task = require('./lib/task'); const compilation = require('./lib/compilation'); const optimize = require('./lib/optimize'); +const isESMBuild = typeof process.env.VSCODE_BUILD_ESM === 'string' && process.env.VSCODE_BUILD_ESM.toLowerCase() === 'true'; + +/** + * @param {boolean} disableMangle + */ function makeCompileBuildTask(disableMangle) { return task.series( util.rimraf('out-build'), util.buildWebNodePaths('out-build'), + date.writeISODate('out-build'), + esm.setESM(isESMBuild), compilation.compileApiProposalNamesTask, - compilation.compileTask('src', 'out-build', true, { disableMangle }), + compilation.compileTask(isESMBuild ? 'src2' : 'src', 'out-build', true, { disableMangle }), optimize.optimizeLoaderTask('out-build', 'out-build', true) ); } diff --git a/patched-vscode/build/gulpfile.editor.js b/patched-vscode/build/gulpfile.editor.js index 22b70a95..088748b8 100644 --- a/patched-vscode/build/gulpfile.editor.js +++ b/patched-vscode/build/gulpfile.editor.js @@ -29,19 +29,17 @@ const editorEntryPoints = [ { name: 'vs/editor/editor.main', include: [], - exclude: ['vs/css', 'vs/nls'], + exclude: ['vs/css'], prepend: [ - { path: 'out-editor-build/vs/css.js', amdModuleId: 'vs/css' }, - { path: 'out-editor-build/vs/nls.js', amdModuleId: 'vs/nls' } + { path: 'out-editor-build/vs/css.js', amdModuleId: 'vs/css' } ], }, { name: 'vs/base/common/worker/simpleWorker', include: ['vs/editor/common/services/editorSimpleWorker'], - exclude: ['vs/nls'], + exclude: [], prepend: [ { path: 'vs/loader.js' }, - { path: 'vs/nls.js', amdModuleId: 'vs/nls' }, { path: 'vs/base/worker/workerMain.js' } ], dest: 'vs/base/worker/workerMain.js' @@ -81,12 +79,15 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers importIgnorePattern: /(^vs\/css!)/, destRoot: path.join(root, 'out-editor-src'), - redirects: [] + redirects: { + '@vscode/tree-sitter-wasm': '../node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-web', + } }); }); // Disable mangling for the editor, as it complicates debugging & quite a few users rely on private/protected fields. -const compileEditorAMDTask = task.define('compile-editor-amd', compilation.compileTask('out-editor-src', 'out-editor-build', true, { disableMangle: true })); +// Disable NLS task to remove english strings to preserve backwards compatibility when we removed the `vs/nls!` AMD plugin. +const compileEditorAMDTask = task.define('compile-editor-amd', compilation.compileTask('out-editor-src', 'out-editor-build', true, { disableMangle: true, preserveEnglish: true })); const optimizeEditorAMDTask = task.define('optimize-editor-amd', optimize.optimizeTask( { @@ -99,7 +100,6 @@ const optimizeEditorAMDTask = task.define('optimize-editor-amd', optimize.optimi paths: { 'vs': 'out-editor-build/vs', 'vs/css': 'out-editor-build/vs/css.build', - 'vs/nls': 'out-editor-build/vs/nls.build', 'vscode': 'empty:' } }, @@ -124,7 +124,6 @@ const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => 'vs/base/worker/workerMain.ts', ], renames: { - 'vs/nls.mock.ts': 'vs/nls.ts' } }); }); @@ -136,7 +135,8 @@ const compileEditorESMTask = task.define('compile-editor-esm', () => { let result; if (process.platform === 'win32') { result = cp.spawnSync(`..\\node_modules\\.bin\\tsc.cmd`, { - cwd: path.join(__dirname, '../out-editor-esm') + cwd: path.join(__dirname, '../out-editor-esm'), + shell: true }); } else { result = cp.spawnSync(`node`, [`../node_modules/.bin/tsc`], { diff --git a/patched-vscode/build/gulpfile.extensions.js b/patched-vscode/build/gulpfile.extensions.js index 1b8bd9b3..4631b295 100644 --- a/patched-vscode/build/gulpfile.extensions.js +++ b/patched-vscode/build/gulpfile.extensions.js @@ -48,7 +48,6 @@ const compilations = [ 'extensions/json-language-features/client/tsconfig.json', 'extensions/json-language-features/server/tsconfig.json', 'extensions/markdown-language-features/preview-src/tsconfig.json', - 'extensions/markdown-language-features/server/tsconfig.json', 'extensions/markdown-language-features/tsconfig.json', 'extensions/markdown-math/tsconfig.json', 'extensions/media-preview/tsconfig.json', @@ -60,9 +59,6 @@ const compilations = [ 'extensions/references-view/tsconfig.json', 'extensions/search-result/tsconfig.json', 'extensions/simple-browser/tsconfig.json', - 'extensions/sagemaker-extension/tsconfig.json', - 'extensions/sagemaker-idle-extension/tsconfig.json', - 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', 'extensions/tunnel-forwarding/tsconfig.json', 'extensions/typescript-language-features/test-workspace/tsconfig.json', 'extensions/typescript-language-features/web/tsconfig.json', @@ -74,7 +70,7 @@ const compilations = [ '.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json', ]; -const getBaseUrl = out => `https://ticino.blob.core.windows.net/sourcemaps/${commit}/${out}`; +const getBaseUrl = out => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`; const tasks = compilations.map(function (tsconfigFile) { const absolutePath = path.join(root, tsconfigFile); diff --git a/patched-vscode/build/gulpfile.js b/patched-vscode/build/gulpfile.js index e945c06e..785719d3 100644 --- a/patched-vscode/build/gulpfile.js +++ b/patched-vscode/build/gulpfile.js @@ -34,11 +34,15 @@ gulp.task(compileClientTask); const watchClientTask = task.define('watch-client', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), task.parallel(watchTask('out', false), watchApiProposalNamesTask))); gulp.task(watchClientTask); +const watchClientESMTask = task.define('watch-client-esm', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), task.parallel(watchTask('out', false, 'src2'), watchApiProposalNamesTask))); +gulp.task(watchClientESMTask); + // All const _compileTask = task.define('compile', task.parallel(monacoTypecheckTask, compileClientTask, compileExtensionsTask, compileExtensionMediaTask)); gulp.task(_compileTask); gulp.task(task.define('watch', task.parallel(/* monacoTypecheckWatchTask, */ watchClientTask, watchExtensionsTask))); +gulp.task(task.define('watch-esm', task.parallel(/* monacoTypecheckWatchTask, */ watchClientESMTask, watchExtensionsTask))); // Default gulp.task('default', _compileTask); diff --git a/patched-vscode/build/gulpfile.reh.js b/patched-vscode/build/gulpfile.reh.js index c2b81d0c..e12a33e6 100644 --- a/patched-vscode/build/gulpfile.reh.js +++ b/patched-vscode/build/gulpfile.reh.js @@ -12,11 +12,13 @@ const util = require('./lib/util'); const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); const optimize = require('./lib/optimize'); +const { inlineMeta } = require('./lib/inlineMeta'); const product = require('../product.json'); const rename = require('gulp-rename'); const replace = require('gulp-replace'); const filter = require('gulp-filter'); const { getProductionDependencies } = require('./lib/dependencies'); +const { readISODate } = require('./lib/date'); const vfs = require('vinyl-fs'); const packageJson = require('../package.json'); const flatmap = require('gulp-flatmap'); @@ -26,9 +28,11 @@ const fs = require('fs'); const glob = require('glob'); const { compileBuildTask } = require('./gulpfile.compile'); const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); -const { vscodeWebEntryPoints, vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web'); +const { vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web'); const cp = require('child_process'); const log = require('fancy-log'); +const { isESM } = require('./lib/esm'); +const buildfile = require('./buildfile'); const REPO_ROOT = path.dirname(__dirname); const commit = getVersion(REPO_ROOT); @@ -39,6 +43,7 @@ const REMOTE_FOLDER = path.join(REPO_ROOT, 'remote'); const BUILD_TARGETS = [ { platform: 'win32', arch: 'x64' }, + { platform: 'win32', arch: 'arm64' }, { platform: 'darwin', arch: 'x64' }, { platform: 'darwin', arch: 'arm64' }, { platform: 'linux', arch: 'x64' }, @@ -50,16 +55,10 @@ const BUILD_TARGETS = [ { platform: 'linux', arch: 'alpine' }, ]; -const serverResources = [ - - // Bootstrap - 'out-build/bootstrap.js', - 'out-build/bootstrap-fork.js', - 'out-build/bootstrap-amd.js', - 'out-build/bootstrap-node.js', +const serverResourceIncludes = [ - // Performance - 'out-build/vs/base/common/performance.js', + // NLS + 'out-build/nls.messages.json', // Process monitor 'out-build/vs/base/node/cpuUsage.sh', @@ -67,55 +66,105 @@ const serverResources = [ // Terminal shell integration 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1', + 'out-build/vs/workbench/contrib/terminal/browser/media/CodeTabExpansion.psm1', + 'out-build/vs/workbench/contrib/terminal/browser/media/GitTabExpansion.psm1', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish', +]; +const serverResourceExcludes = [ + '!out-build/vs/**/{electron-sandbox,electron-main}/**', + '!out-build/vs/editor/standalone/**', + '!out-build/vs/workbench/**/*-tb.png', '!**/test/**' ]; -const serverWithWebResources = [ - - // Include all of server... - ...serverResources, +const serverResources = [ + ...serverResourceIncludes, + ...serverResourceExcludes +]; - // ...and all of web +const serverWithWebResourceIncludes = isESM() ? [ + ...serverResourceIncludes, + 'out-build/vs/code/browser/workbench/*.html', + ...vscodeWebResourceIncludes +] : [ + ...serverResourceIncludes, ...vscodeWebResourceIncludes ]; +const serverWithWebResourceExcludes = [ + ...serverResourceExcludes, + '!out-build/vs/code/**/*-dev.html', + '!out-build/vs/code/**/*-dev.esm.html', +]; + +const serverWithWebResources = [ + ...serverWithWebResourceIncludes, + ...serverWithWebResourceExcludes +]; + const serverEntryPoints = [ { name: 'vs/server/node/server.main', - exclude: ['vs/css', 'vs/nls'] + exclude: ['vs/css'] }, { name: 'vs/server/node/server.cli', - exclude: ['vs/css', 'vs/nls'] + exclude: ['vs/css'] }, { name: 'vs/workbench/api/node/extensionHostProcess', - exclude: ['vs/css', 'vs/nls'] + exclude: ['vs/css'] }, { name: 'vs/platform/files/node/watcher/watcherMain', - exclude: ['vs/css', 'vs/nls'] + exclude: ['vs/css'] }, { name: 'vs/platform/terminal/node/ptyHostMain', - exclude: ['vs/css', 'vs/nls'] + exclude: ['vs/css'] } ]; +const webEntryPoints = isESM() ? [ + buildfile.base, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.keyboardMaps, + buildfile.codeWeb +].flat() : [ + buildfile.entrypoint('vs/workbench/workbench.web.main'), + buildfile.base, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.keyboardMaps, + buildfile.workbenchWeb() +].flat(); + const serverWithWebEntryPoints = [ // Include all of server ...serverEntryPoints, - // Include workbench web - ...vscodeWebEntryPoints + // Include all of web + ...webEntryPoints, +].flat(); + +const commonJSEntryPoints = [ + 'out-build/server-main.js', + 'out-build/server-cli.js', + 'out-build/bootstrap-fork.js', ]; function getNodeVersion() { @@ -125,20 +174,7 @@ function getNodeVersion() { return { nodeVersion, internalNodeVersion }; } -function getNodeChecksum(nodeVersion, platform, arch, glibcPrefix) { - let expectedName; - switch (platform) { - case 'win32': - expectedName = `win-${arch}/node.exe`; - break; - - case 'darwin': - case 'alpine': - case 'linux': - expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`; - break; - } - +function getNodeChecksum(expectedName) { const nodeJsChecksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'nodejs.txt'), 'utf8'); for (const line of nodeJsChecksums.split('\n')) { const [checksum, name] = line.split(/\s+/); @@ -182,7 +218,6 @@ if (defaultNodeTask) { function nodejs(platform, arch) { const { fetchUrls, fetchGithub } = require('./lib/fetch'); const untar = require('gulp-untar'); - const crypto = require('crypto'); if (arch === 'armhf') { arch = 'armv7l'; @@ -194,7 +229,24 @@ function nodejs(platform, arch) { log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`); const glibcPrefix = process.env['VSCODE_NODE_GLIBC'] ?? ''; - const checksumSha256 = getNodeChecksum(nodeVersion, platform, arch, glibcPrefix); + let expectedName; + switch (platform) { + case 'win32': + expectedName = product.nodejsRepository !== 'https://nodejs.org' ? + `win-${arch}-node.exe` : `win-${arch}/node.exe`; + break; + + case 'darwin': + expectedName = `node-v${nodeVersion}-${platform}-${arch}.tar.gz`; + break; + case 'linux': + expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`; + break; + case 'alpine': + expectedName = `node-v${nodeVersion}-linux-${arch}-musl.tar.gz`; + break; + } + const checksumSha256 = getNodeChecksum(expectedName); if (checksumSha256) { log(`Using SHA256 checksum for checking integrity: ${checksumSha256}`); @@ -205,13 +257,13 @@ function nodejs(platform, arch) { switch (platform) { case 'win32': return (product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `win-${arch}-node.exe`, checksumSha256 }) : + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) : fetchUrls(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org', checksumSha256 })) .pipe(rename('node.exe')); case 'darwin': case 'linux': return (product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`, checksumSha256 }) : + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) : fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 }) ).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) .pipe(filter('**/node')) @@ -219,7 +271,7 @@ function nodejs(platform, arch) { .pipe(rename('node')); case 'alpine': return product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}-${platform}-${arch}.tar.gz`, checksumSha256 }) + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) .pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) .pipe(filter('**/node')) .pipe(util.setExecutableBit('**')) @@ -288,13 +340,22 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa } const name = product.nameShort; - const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' }) - .pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined })); - const date = new Date().toISOString(); + let packageJsonContents; + const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' }) + .pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined, ...(isESM(`Setting 'type: module' in top level package.json`) ? { type: 'module' } : {}) })) // TODO@esm this should be configured in the top level package.json + .pipe(es.through(function (file) { + packageJsonContents = file.contents.toString(); + this.emit('data', file); + })); + let productJsonContents; const productJsonStream = gulp.src(['product.json'], { base: '.' }) - .pipe(json({ commit, date, version })); + .pipe(json({ commit, date: readISODate('out-build'), version })) + .pipe(es.through(function (file) { + productJsonContents = file.contents.toString(); + this.emit('data', file); + })); const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true }); @@ -387,6 +448,12 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa ); } + result = inlineMeta(result, { + targetPaths: commonJSEntryPoints, + packageJsonFn: () => packageJsonContents, + productJsonFn: () => productJsonContents + }); + return result.pipe(vfs.dest(destination)); }; } @@ -418,16 +485,14 @@ function tweakProductForServerWeb(product) { }, commonJS: { src: 'out-build', - entryPoints: [ - 'out-build/server-main.js', - 'out-build/server-cli.js' - ], + entryPoints: commonJSEntryPoints, platform: 'node', external: [ 'minimist', - // TODO: we cannot inline `product.json` because + // We cannot inline `product.json` from here because // it is being changed during build time at a later // point in time (such as `checksums`) + // We have a manual step to inline these later. '../product.json', '../package.json' ] @@ -439,7 +504,7 @@ function tweakProductForServerWeb(product) { const minifyTask = task.define(`minify-vscode-${type}`, task.series( optimizeTask, util.rimraf(`out-vscode-${type}-min`), - optimize.minifyTask(`out-vscode-${type}`, `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) + optimize.minifyTask(`out-vscode-${type}`, `https://main.vscode-cdn.net/sourcemaps/${commit}/core`) )); gulp.task(minifyTask); diff --git a/patched-vscode/build/gulpfile.scan.js b/patched-vscode/build/gulpfile.scan.js index 6f8144b0..127720a0 100644 --- a/patched-vscode/build/gulpfile.scan.js +++ b/patched-vscode/build/gulpfile.scan.js @@ -26,6 +26,9 @@ const BUILD_TARGETS = [ { platform: 'linux', arch: 'arm64' }, ]; +// The following files do not have PDBs downloaded for them during the download symbols process. +const excludedCheckList = ['d3dcompiler_47.dll']; + BUILD_TARGETS.forEach(buildTarget => { const dashed = (/** @type {string | null} */ str) => (str ? `-${str}` : ``); const platform = buildTarget.platform; @@ -46,7 +49,6 @@ BUILD_TARGETS.forEach(buildTarget => { if (platform === 'win32') { tasks.push( () => electron.dest(destinationPdb, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, pdbs: true }), - util.rimraf(path.join(destinationExe, 'd3dcompiler_47.dll')), () => confirmPdbsExist(destinationExe, destinationPdb) ); } @@ -110,6 +112,10 @@ function nodeModules(destinationExe, destinationPdb, platform) { function confirmPdbsExist(destinationExe, destinationPdb) { readdirSync(destinationExe).forEach(file => { + if (excludedCheckList.includes(file)) { + return; + } + if (file.endsWith('.dll') || file.endsWith('.exe')) { const pdb = `${file}.pdb`; if (!existsSync(path.join(destinationPdb, pdb))) { diff --git a/patched-vscode/build/gulpfile.vscode.js b/patched-vscode/build/gulpfile.vscode.js index 3b1aeafd..7841bb90 100644 --- a/patched-vscode/build/gulpfile.vscode.js +++ b/patched-vscode/build/gulpfile.vscode.js @@ -6,10 +6,7 @@ 'use strict'; const gulp = require('gulp'); -const merge = require('gulp-merge-json'); const fs = require('fs'); -const os = require('os'); -const cp = require('child_process'); const path = require('path'); const es = require('event-stream'); const vfs = require('vinyl-fs'); @@ -18,9 +15,11 @@ const replace = require('gulp-replace'); const filter = require('gulp-filter'); const util = require('./lib/util'); const { getVersion } = require('./lib/getVersion'); +const { readISODate } = require('./lib/date'); const task = require('./lib/task'); -const buildfile = require('../src/buildfile'); +const buildfile = require('./buildfile'); const optimize = require('./lib/optimize'); +const { inlineMeta } = require('./lib/inlineMeta'); const root = path.dirname(__dirname); const commit = getVersion(root); const packageJson = require('../package.json'); @@ -34,11 +33,23 @@ const minimist = require('minimist'); const { compileBuildTask } = require('./gulpfile.compile'); const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); const { promisify } = require('util'); +const { isESM } = require('./lib/esm'); const glob = promisify(require('glob')); const rcedit = promisify(require('rcedit')); // Build -const vscodeEntryPoints = [ +const vscodeEntryPoints = isESM() ? [ + buildfile.base, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.workbenchDesktop(), + buildfile.code +].flat() : [ buildfile.entrypoint('vs/workbench/workbench.desktop.main'), buildfile.base, buildfile.workerExtensionHost, @@ -46,21 +57,72 @@ const vscodeEntryPoints = [ buildfile.workerLanguageDetection, buildfile.workerLocalFileSearch, buildfile.workerProfileAnalysis, - buildfile.workbenchDesktop, + buildfile.workbenchDesktop(), buildfile.code ].flat(); -const vscodeResources = [ - 'out-build/bootstrap.js', - 'out-build/bootstrap-fork.js', - 'out-build/bootstrap-amd.js', - 'out-build/bootstrap-node.js', - 'out-build/bootstrap-window.js', +const vscodeResourceIncludes = isESM() ? [ + + // NLS + 'out-build/nls.messages.json', + 'out-build/nls.keys.json', + + // Workbench + 'out-build/vs/code/electron-sandbox/workbench/workbench.esm.html', + + // Electron Preload + 'out-build/vs/base/parts/sandbox/electron-sandbox/preload.js', + 'out-build/vs/base/parts/sandbox/electron-sandbox/preload-aux.js', + + // Node Scripts + 'out-build/vs/base/node/{terminateProcess.sh,cpuUsage.sh,ps.sh}', + + // Touchbar + 'out-build/vs/workbench/browser/parts/editor/media/*.png', + 'out-build/vs/workbench/contrib/debug/browser/media/*.png', + + // External Terminal + 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + + // Terminal shell integration + 'out-build/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/*.fish', + 'out-build/vs/workbench/contrib/terminal/browser/media/*.ps1', + 'out-build/vs/workbench/contrib/terminal/browser/media/*.psm1', + 'out-build/vs/workbench/contrib/terminal/browser/media/*.sh', + 'out-build/vs/workbench/contrib/terminal/browser/media/*.zsh', + + // Accessibility Signals + 'out-build/vs/platform/accessibilitySignal/browser/media/*.mp3', + + // Welcome + 'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}', + + // Extensions + 'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}', + 'out-build/vs/workbench/services/extensionManagement/common/media/*.{svg,png}', + + // Webview + 'out-build/vs/workbench/contrib/webview/browser/pre/*.{js,html}', + + // Extension Host Worker + 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html', + + // Process Explorer + 'out-build/vs/code/electron-sandbox/processExplorer/processExplorer.esm.html', + + // Tree Sitter highlights + 'out-build/vs/editor/common/languages/highlights/*.scm', + + // Issue Reporter + 'out-build/vs/workbench/contrib/issue/electron-sandbox/issueReporter.esm.html' +] : [ + 'out-build/nls.messages.json', + 'out-build/nls.keys.json', 'out-build/vs/**/*.{svg,png,html,jpg,mp3}', '!out-build/vs/code/browser/**/*.html', '!out-build/vs/code/**/*-dev.html', + '!out-build/vs/code/**/*-dev.esm.html', '!out-build/vs/editor/standalone/**/*.svg', - 'out-build/vs/base/common/performance.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', 'out-build/vs/base/browser/ui/codicons/codicon/**', 'out-build/vs/base/parts/sandbox/electron-sandbox/preload.js', @@ -70,23 +132,46 @@ const vscodeResources = [ 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', 'out-build/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/*.fish', 'out-build/vs/workbench/contrib/terminal/browser/media/*.ps1', + 'out-build/vs/workbench/contrib/terminal/browser/media/*.psm1', 'out-build/vs/workbench/contrib/terminal/browser/media/*.sh', 'out-build/vs/workbench/contrib/terminal/browser/media/*.zsh', 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', - '!out-build/vs/workbench/contrib/issue/browser/*.html', '!out-build/vs/workbench/contrib/issue/**/*-dev.html', + '!out-build/vs/workbench/contrib/issue/**/*-dev.esm.html', + 'out-build/vs/editor/common/languages/highlights/*.scm', 'out-build/vs/**/markdown.css', 'out-build/vs/workbench/contrib/tasks/**/*.json', '!**/test/**' ]; +const vscodeResources = [ + + // Includes + ...vscodeResourceIncludes, + + // Excludes + '!out-build/vs/code/browser/**', + '!out-build/vs/editor/standalone/**', + '!out-build/vs/code/**/*-dev.html', + '!out-build/vs/code/**/*-dev.esm.html', + '!out-build/vs/workbench/contrib/issue/**/*-dev.html', + '!out-build/vs/workbench/contrib/issue/**/*-dev.esm.html', + '!**/test/**' +]; + // Do not change the order of these files! They will // be inlined into the target window file in this order // and they depend on each other in this way. -const windowBootstrapFiles = [ - 'out-build/bootstrap.js', - 'out-build/vs/loader.js', - 'out-build/bootstrap-window.js' +const windowBootstrapFiles = []; +if (!isESM('Skipping loader.js in window bootstrap files')) { + windowBootstrapFiles.push('out-build/vs/loader.js'); +} +windowBootstrapFiles.push('out-build/bootstrap-window.js'); + +const commonJSEntryPoints = [ + 'out-build/main.js', + 'out-build/cli.js', + 'out-build/bootstrap-fork.js' ]; const optimizeVSCodeTask = task.define('optimize-vscode', task.series( @@ -107,17 +192,16 @@ const optimizeVSCodeTask = task.define('optimize-vscode', task.series( }, commonJS: { src: 'out-build', - entryPoints: [ - 'out-build/main.js', - 'out-build/cli.js' - ], + entryPoints: commonJSEntryPoints, platform: 'node', external: [ 'electron', 'minimist', - // TODO: we cannot inline `product.json` because + 'original-fs', + // We cannot inline `product.json` from here because // it is being changed during build time at a later // point in time (such as `checksums`) + // We have a manual step to inline these later. '../product.json', '../package.json', ] @@ -133,7 +217,7 @@ const optimizeVSCodeTask = task.define('optimize-vscode', task.series( )); gulp.task(optimizeVSCodeTask); -const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; +const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; const minifyVSCodeTask = task.define('minify-vscode', task.series( optimizeVSCodeTask, util.rimraf('out-vscode-min'), @@ -212,7 +296,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op 'vs/workbench/workbench.desktop.main.js', 'vs/workbench/workbench.desktop.main.css', 'vs/workbench/api/node/extensionHostProcess.js', - 'vs/code/electron-sandbox/workbench/workbench.html', + isESM() ? 'vs/code/electron-sandbox/workbench/workbench.esm.html' : 'vs/code/electron-sandbox/workbench/workbench.html', 'vs/code/electron-sandbox/workbench/workbench.js' ]); @@ -241,22 +325,34 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op version += '-' + quality; } + if (isESM() && typeof quality === 'string' && quality !== 'exploration') { + // TODO@esm remove this safeguard + throw new Error('Refuse to build ESM on quality other than exploration'); + } + const name = product.nameShort; - const packageJsonUpdates = { name, version }; + const packageJsonUpdates = { name, version, ...(isESM(`Setting 'type: module' and 'main: out/main.js' in top level package.json`) ? { type: 'module', main: 'out/main.js' } : {}) }; // TODO@esm this should be configured in the top level package.json // for linux url handling if (platform === 'linux') { packageJsonUpdates.desktopName = `${product.applicationName}-url-handler.desktop`; } + let packageJsonContents; const packageJsonStream = gulp.src(['package.json'], { base: '.' }) - .pipe(json(packageJsonUpdates)); - - const date = new Date().toISOString(); - const productJsonUpdate = { commit, date, checksums, version }; + .pipe(json(packageJsonUpdates)) + .pipe(es.through(function (file) { + packageJsonContents = file.contents.toString(); + this.emit('data', file); + })); + let productJsonContents; const productJsonStream = gulp.src(['product.json'], { base: '.' }) - .pipe(json(productJsonUpdate)); + .pipe(json({ commit, date: readISODate('out-build'), checksums, version })) + .pipe(es.through(function (file) { + productJsonContents = file.contents.toString(); + this.emit('data', file); + })); const license = gulp.src([product.licenseFileName, 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.', allowEmpty: true }); @@ -268,24 +364,30 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); const root = path.resolve(path.join(__dirname, '..')); const productionDependencies = getProductionDependencies(root); - const dependenciesSrc = productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat(); + const dependenciesSrc = productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!**/*.mk`]).flat(); - const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) + let deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`))) .pipe(jsFilter) .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) - .pipe(jsFilter.restore) - .pipe(createAsar(path.join(process.cwd(), 'node_modules'), [ + .pipe(jsFilter.restore); + + if (!isESM('ASAR disabled in VS Code builds')) { // TODO@esm: ASAR disabled in ESM + deps = deps.pipe(createAsar(path.join(process.cwd(), 'node_modules'), [ '**/*.node', '**/@vscode/ripgrep/bin/*', '**/node-pty/build/Release/*', + '**/node-pty/build/Release/conpty/*', '**/node-pty/lib/worker/conoutSocketWorker.js', '**/node-pty/lib/shared/conout.js', '**/*.wasm', '**/@vscode/vsce-sign/bin/*', + ], [ + '**/*.mk', ], 'node_modules.asar')); + } let all = es.merge( packageJsonStream, @@ -389,6 +491,12 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(rename('bin/' + product.applicationName))); } + result = inlineMeta(result, { + targetPaths: commonJSEntryPoints, + packageJsonFn: () => packageJsonContents, + productJsonFn: () => productJsonContents + }); + return result.pipe(vfs.dest(destination)); }; } @@ -496,17 +604,12 @@ gulp.task(task.define( core, compileExtensionsBuildTask, function () { - const pathToMetadata = './out-vscode/nls.metadata.json'; - const pathToRehWebMetadata = './out-vscode-reh-web/nls.metadata.json'; + const pathToMetadata = './out-build/nls.metadata.json'; const pathToExtensions = '.build/extensions/*'; const pathToSetup = 'build/win32/i18n/messages.en.isl'; return es.merge( - gulp.src([pathToMetadata, pathToRehWebMetadata]).pipe(merge({ - fileName: 'nls.metadata.json', - jsonSpace: '', - concatArrays: true - })).pipe(i18n.createXlfFilesForCoreBundle()), + gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) ).pipe(vfs.dest('../vscode-translations-export')); diff --git a/patched-vscode/build/gulpfile.vscode.linux.js b/patched-vscode/build/gulpfile.vscode.linux.js index 8c2b62f7..79594543 100644 --- a/patched-vscode/build/gulpfile.vscode.linux.js +++ b/patched-vscode/build/gulpfile.vscode.linux.js @@ -8,10 +8,9 @@ const gulp = require('gulp'); const replace = require('gulp-replace'); const rename = require('gulp-rename'); -const shell = require('gulp-shell'); const es = require('event-stream'); const vfs = require('vinyl-fs'); -const util = require('./lib/util'); +const { rimraf } = require('./lib/util'); const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); const packageJson = require('../package.json'); @@ -19,6 +18,10 @@ const product = require('../product.json'); const dependenciesGenerator = require('./linux/dependencies-generator'); const debianRecommendedDependencies = require('./linux/debian/dep-lists').recommendedDeps; const path = require('path'); +const cp = require('child_process'); +const util = require('util'); + +const exec = util.promisify(cp.exec); const root = path.dirname(__dirname); const commit = getVersion(root); @@ -105,7 +108,11 @@ function prepareDebPackage(arch) { .pipe(replace('@@NAME@@', product.applicationName)) .pipe(rename('DEBIAN/postinst')); - const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, code); + const templates = gulp.src('resources/linux/debian/templates.template', { base: '.' }) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(rename('DEBIAN/templates')); + + const all = es.merge(control, templates, postinst, postrm, prerm, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, code); return all.pipe(vfs.dest(destination)); }; @@ -116,11 +123,13 @@ function prepareDebPackage(arch) { */ function buildDebPackage(arch) { const debArch = getDebPackageArch(arch); - return shell.task([ - 'chmod 755 ' + product.applicationName + '-' + debArch + '/DEBIAN/postinst ' + product.applicationName + '-' + debArch + '/DEBIAN/prerm ' + product.applicationName + '-' + debArch + '/DEBIAN/postrm', - 'mkdir -p deb', - 'fakeroot dpkg-deb -b ' + product.applicationName + '-' + debArch + ' deb' - ], { cwd: '.build/linux/deb/' + debArch }); + const cwd = `.build/linux/deb/${debArch}`; + + return async () => { + await exec(`chmod 755 ${product.applicationName}-${debArch}/DEBIAN/postinst ${product.applicationName}-${debArch}/DEBIAN/prerm ${product.applicationName}-${debArch}/DEBIAN/postrm`, { cwd }); + await exec('mkdir -p deb', { cwd }); + await exec(`fakeroot dpkg-deb -b ${product.applicationName}-${debArch} deb`, { cwd }); + }; } /** @@ -218,14 +227,14 @@ function prepareRpmPackage(arch) { function buildRpmPackage(arch) { const rpmArch = getRpmPackageArch(arch); const rpmBuildPath = getRpmBuildPath(rpmArch); - const rpmOut = rpmBuildPath + '/RPMS/' + rpmArch; - const destination = '.build/linux/rpm/' + rpmArch; - - return shell.task([ - 'mkdir -p ' + destination, - 'HOME="$(pwd)/' + destination + '" rpmbuild -bb ' + rpmBuildPath + '/SPECS/' + product.applicationName + '.spec --target=' + rpmArch, - 'cp "' + rpmOut + '/$(ls ' + rpmOut + ')" ' + destination + '/' - ]); + const rpmOut = `${rpmBuildPath}/RPMS/${rpmArch}`; + const destination = `.build/linux/rpm/${rpmArch}`; + + return async () => { + await exec(`mkdir -p ${destination}`); + await exec(`HOME="$(pwd)/${destination}" rpmbuild -bb ${rpmBuildPath}/SPECS/${product.applicationName}.spec --target=${rpmArch}`); + await exec(`cp "${rpmOut}/$(ls ${rpmOut})" ${destination}/`); + }; } /** @@ -286,9 +295,8 @@ function prepareSnapPackage(arch) { * @param {string} arch */ function buildSnapPackage(arch) { - const snapBuildPath = getSnapBuildPath(arch); - // Default target for snapcraft runs: pull, build, stage and prime, and finally assembles the snap. - return shell.task(`cd ${snapBuildPath} && snapcraft`); + const cwd = getSnapBuildPath(arch); + return () => exec('snapcraft', { cwd }); } const BUILD_TARGETS = [ @@ -299,18 +307,18 @@ const BUILD_TARGETS = [ BUILD_TARGETS.forEach(({ arch }) => { const debArch = getDebPackageArch(arch); - const prepareDebTask = task.define(`vscode-linux-${arch}-prepare-deb`, task.series(util.rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch))); + const prepareDebTask = task.define(`vscode-linux-${arch}-prepare-deb`, task.series(rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch))); gulp.task(prepareDebTask); const buildDebTask = task.define(`vscode-linux-${arch}-build-deb`, buildDebPackage(arch)); gulp.task(buildDebTask); const rpmArch = getRpmPackageArch(arch); - const prepareRpmTask = task.define(`vscode-linux-${arch}-prepare-rpm`, task.series(util.rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch))); + const prepareRpmTask = task.define(`vscode-linux-${arch}-prepare-rpm`, task.series(rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch))); gulp.task(prepareRpmTask); const buildRpmTask = task.define(`vscode-linux-${arch}-build-rpm`, buildRpmPackage(arch)); gulp.task(buildRpmTask); - const prepareSnapTask = task.define(`vscode-linux-${arch}-prepare-snap`, task.series(util.rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch))); + const prepareSnapTask = task.define(`vscode-linux-${arch}-prepare-snap`, task.series(rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch))); gulp.task(prepareSnapTask); const buildSnapTask = task.define(`vscode-linux-${arch}-build-snap`, task.series(prepareSnapTask, buildSnapPackage(arch))); gulp.task(buildSnapTask); diff --git a/patched-vscode/build/gulpfile.vscode.web.js b/patched-vscode/build/gulpfile.vscode.web.js index 85129a52..b6a26fc4 100644 --- a/patched-vscode/build/gulpfile.vscode.web.js +++ b/patched-vscode/build/gulpfile.vscode.web.js @@ -12,15 +12,16 @@ const util = require('./lib/util'); const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); const optimize = require('./lib/optimize'); +const { readISODate } = require('./lib/date'); const product = require('../product.json'); const rename = require('gulp-rename'); const filter = require('gulp-filter'); const { getProductionDependencies } = require('./lib/dependencies'); const vfs = require('vinyl-fs'); -const replace = require('gulp-replace'); const packageJson = require('../package.json'); const { compileBuildTask } = require('./gulpfile.compile'); const extensions = require('./lib/extensions'); +const { isESM } = require('./lib/esm'); const REPO_ROOT = path.dirname(__dirname); const BUILD_ROOT = path.dirname(REPO_ROOT); @@ -30,13 +31,40 @@ const commit = getVersion(REPO_ROOT); const quality = product.quality; const version = (quality && quality !== 'stable') ? `${packageJson.version}-${quality}` : packageJson.version; -const vscodeWebResourceIncludes = [ +const vscodeWebResourceIncludes = isESM() ? [ + + // NLS + 'out-build/nls.messages.js', + + // Accessibility Signals + 'out-build/vs/platform/accessibilitySignal/browser/media/*.mp3', + + // Welcome + 'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}', + + // Extensions + 'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}', + 'out-build/vs/workbench/services/extensionManagement/common/media/*.{svg,png}', + + // Webview + 'out-build/vs/workbench/contrib/webview/browser/pre/*.{js,html}', + + // Tree Sitter highlights + 'out-build/vs/editor/common/languages/highlights/*.scm', + + // Extension Host Worker + 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html', +] : [ + // Workbench 'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png,jpg,mp3}', 'out-build/vs/code/browser/workbench/*.html', 'out-build/vs/base/browser/ui/codicons/codicon/**/*.ttf', 'out-build/vs/**/markdown.css', + // NLS + 'out-build/nls.messages.js', + // Webview 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', 'out-build/vs/workbench/contrib/webview/browser/pre/*.html', @@ -44,6 +72,9 @@ const vscodeWebResourceIncludes = [ // Extension Worker 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', + // Tree Sitter highlights + 'out-build/vs/editor/common/languages/highlights/*.scm', + // Web node paths (needed for integration tests) 'out-build/vs/webPackagePaths.js', ]; @@ -58,25 +89,33 @@ const vscodeWebResources = [ '!out-build/vs/**/{node,electron-sandbox,electron-main}/**', '!out-build/vs/editor/standalone/**', '!out-build/vs/workbench/**/*-tb.png', + '!out-build/vs/code/**/*-dev.html', + '!out-build/vs/code/**/*-dev.esm.html', '!**/test/**' ]; -const buildfile = require('../src/buildfile'); +const buildfile = require('./buildfile'); -const vscodeWebEntryPoints = [ +const vscodeWebEntryPoints = isESM() ? [ + buildfile.base, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.keyboardMaps, + buildfile.workbenchWeb() +].flat() : [ buildfile.entrypoint('vs/workbench/workbench.web.main'), buildfile.base, buildfile.workerExtensionHost, buildfile.workerNotebook, buildfile.workerLanguageDetection, buildfile.workerLocalFileSearch, - buildfile.workerProfileAnalysis, buildfile.keyboardMaps, - buildfile.workbenchWeb + buildfile.workbenchWeb() ].flat(); -exports.vscodeWebEntryPoints = vscodeWebEntryPoints; - -const buildDate = new Date().toISOString(); /** * @param {object} product The parsed product.json file contents @@ -93,7 +132,7 @@ const createVSCodeWebProductConfigurationPatcher = (product) => { ...product, version, commit, - date: buildDate + date: readISODate('out-build') }); return content.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', () => productConfiguration.substr(1, productConfiguration.length - 2) /* without { and }*/); } @@ -175,7 +214,7 @@ const optimizeVSCodeWebTask = task.define('optimize-vscode-web', task.series( const minifyVSCodeWebTask = task.define('minify-vscode-web', task.series( optimizeVSCodeWebTask, util.rimraf('out-vscode-web-min'), - optimize.minifyTask('out-vscode-web', `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) + optimize.minifyTask('out-vscode-web', `https://main.vscode-cdn.net/sourcemaps/${commit}/core`) )); gulp.task(minifyVSCodeWebTask); diff --git a/patched-vscode/build/lib/asar.js b/patched-vscode/build/lib/asar.js index 31845f2f..07b39bf7 100644 --- a/patched-vscode/build/lib/asar.js +++ b/patched-vscode/build/lib/asar.js @@ -11,7 +11,7 @@ const pickle = require('chromium-pickle-js'); const Filesystem = require('asar/lib/filesystem'); const VinylFile = require("vinyl"); const minimatch = require("minimatch"); -function createAsar(folderPath, unpackGlobs, destFilename) { +function createAsar(folderPath, unpackGlobs, skipGlobs, destFilename) { const shouldUnpackFile = (file) => { for (let i = 0; i < unpackGlobs.length; i++) { if (minimatch(file.relative, unpackGlobs[i])) { @@ -20,6 +20,14 @@ function createAsar(folderPath, unpackGlobs, destFilename) { } return false; }; + const shouldSkipFile = (file) => { + for (const skipGlob of skipGlobs) { + if (minimatch(file.relative, skipGlob)) { + return true; + } + } + return false; + }; const filesystem = new Filesystem(folderPath); const out = []; // Keep track of pending inserts @@ -64,6 +72,9 @@ function createAsar(folderPath, unpackGlobs, destFilename) { if (!file.stat.isFile()) { throw new Error(`unknown item in stream!`); } + if (shouldSkipFile(file)) { + return; + } const shouldUnpack = shouldUnpackFile(file); insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); if (shouldUnpack) { diff --git a/patched-vscode/build/lib/asar.ts b/patched-vscode/build/lib/asar.ts index 44a6416b..7dc1dd3b 100644 --- a/patched-vscode/build/lib/asar.ts +++ b/patched-vscode/build/lib/asar.ts @@ -17,7 +17,7 @@ declare class AsarFilesystem { insertFile(path: string, shouldUnpack: boolean, file: { stat: { size: number; mode: number } }, options: {}): Promise; } -export function createAsar(folderPath: string, unpackGlobs: string[], destFilename: string): NodeJS.ReadWriteStream { +export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: string[], destFilename: string): NodeJS.ReadWriteStream { const shouldUnpackFile = (file: VinylFile): boolean => { for (let i = 0; i < unpackGlobs.length; i++) { @@ -28,6 +28,15 @@ export function createAsar(folderPath: string, unpackGlobs: string[], destFilena return false; }; + const shouldSkipFile = (file: VinylFile): boolean => { + for (const skipGlob of skipGlobs) { + if (minimatch(file.relative, skipGlob)) { + return true; + } + } + return false; + }; + const filesystem = new Filesystem(folderPath); const out: Buffer[] = []; @@ -78,6 +87,9 @@ export function createAsar(folderPath: string, unpackGlobs: string[], destFilena if (!file.stat.isFile()) { throw new Error(`unknown item in stream!`); } + if (shouldSkipFile(file)) { + return; + } const shouldUnpack = shouldUnpackFile(file); insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); diff --git a/patched-vscode/build/lib/bundle.js b/patched-vscode/build/lib/bundle.js index 61d9f015..627b9966 100644 --- a/patched-vscode/build/lib/bundle.js +++ b/patched-vscode/build/lib/bundle.js @@ -5,6 +5,7 @@ *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.bundle = bundle; +exports.removeAllTSBoilerplate = removeAllTSBoilerplate; const fs = require("fs"); const path = require("path"); const vm = require("vm"); @@ -36,14 +37,10 @@ function bundle(entryPoints, config, callback) { const loader = loaderModule.exports; config.isBuild = true; config.paths = config.paths || {}; - if (!config.paths['vs/nls']) { - config.paths['vs/nls'] = 'out-build/vs/nls.build'; - } if (!config.paths['vs/css']) { config.paths['vs/css'] = 'out-build/vs/css.build'; } config.buildForceInvokeFactory = config.buildForceInvokeFactory || {}; - config.buildForceInvokeFactory['vs/nls'] = true; config.buildForceInvokeFactory['vs/css'] = true; loader.config(config); loader(['require'], (localRequire) => { @@ -53,15 +50,11 @@ function bundle(entryPoints, config, callback) { r += '.js'; } // avoid packaging the build version of plugins: - r = r.replace('vs/nls.build.js', 'vs/nls.js'); r = r.replace('vs/css.build.js', 'vs/css.js'); return { path: r, amdModuleId: entry.amdModuleId }; }; for (const moduleId in entryPointsMap) { const entryPoint = entryPointsMap[moduleId]; - if (entryPoint.append) { - entryPoint.append = entryPoint.append.map(resolvePath); - } if (entryPoint.prepend) { entryPoint.prepend = entryPoint.prepend.map(resolvePath); } @@ -109,7 +102,7 @@ function emitEntryPoints(modules, entryPoints) { return allDependencies[module]; }); bundleData.bundles[moduleToBundle] = includedModules; - const res = emitEntryPoint(modulesMap, modulesGraph, moduleToBundle, includedModules, info.prepend || [], info.append || [], info.dest); + const res = emitEntryPoint(modulesMap, modulesGraph, moduleToBundle, includedModules, info.prepend || [], info.dest); result = result.concat(res.files); for (const pluginName in res.usedPlugins) { usedPlugins[pluginName] = usedPlugins[pluginName] || res.usedPlugins[pluginName]; @@ -132,7 +125,7 @@ function emitEntryPoints(modules, entryPoints) { }); return { // TODO@TS 2.1.2 - files: extractStrings(removeDuplicateTSBoilerplate(result)), + files: extractStrings(removeAllDuplicateTSBoilerplate(result)), bundleData: bundleData }; } @@ -221,58 +214,68 @@ function extractStrings(destFiles) { }); return destFiles; } -function removeDuplicateTSBoilerplate(destFiles) { - // Taken from typescript compiler => emitFiles - const BOILERPLATE = [ - { start: /^var __extends/, end: /^}\)\(\);$/ }, - { start: /^var __assign/, end: /^};$/ }, - { start: /^var __decorate/, end: /^};$/ }, - { start: /^var __metadata/, end: /^};$/ }, - { start: /^var __param/, end: /^};$/ }, - { start: /^var __awaiter/, end: /^};$/ }, - { start: /^var __generator/, end: /^};$/ }, - ]; +function removeAllDuplicateTSBoilerplate(destFiles) { destFiles.forEach((destFile) => { const SEEN_BOILERPLATE = []; destFile.sources.forEach((source) => { - const lines = source.contents.split(/\r\n|\n|\r/); - const newLines = []; - let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (IS_REMOVING_BOILERPLATE) { - newLines.push(''); - if (END_BOILERPLATE.test(line)) { - IS_REMOVING_BOILERPLATE = false; - } - } - else { - for (let j = 0; j < BOILERPLATE.length; j++) { - const boilerplate = BOILERPLATE[j]; - if (boilerplate.start.test(line)) { - if (SEEN_BOILERPLATE[j]) { - IS_REMOVING_BOILERPLATE = true; - END_BOILERPLATE = boilerplate.end; - } - else { - SEEN_BOILERPLATE[j] = true; - } - } - } - if (IS_REMOVING_BOILERPLATE) { - newLines.push(''); + source.contents = removeDuplicateTSBoilerplate(source.contents, SEEN_BOILERPLATE); + }); + }); + return destFiles; +} +function removeAllTSBoilerplate(source) { + const seen = new Array(BOILERPLATE.length).fill(true, 0, 10); + return removeDuplicateTSBoilerplate(source, seen); +} +// Taken from typescript compiler => emitFiles +const BOILERPLATE = [ + { start: /^var __extends/, end: /^}\)\(\);$/ }, + { start: /^var __assign/, end: /^};$/ }, + { start: /^var __decorate/, end: /^};$/ }, + { start: /^var __metadata/, end: /^};$/ }, + { start: /^var __param/, end: /^};$/ }, + { start: /^var __awaiter/, end: /^};$/ }, + { start: /^var __generator/, end: /^};$/ }, + { start: /^var __createBinding/, end: /^}\)\);$/ }, + { start: /^var __setModuleDefault/, end: /^}\);$/ }, + { start: /^var __importStar/, end: /^};$/ }, +]; +function removeDuplicateTSBoilerplate(source, SEEN_BOILERPLATE = []) { + const lines = source.split(/\r\n|\n|\r/); + const newLines = []; + let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (IS_REMOVING_BOILERPLATE) { + newLines.push(''); + if (END_BOILERPLATE.test(line)) { + IS_REMOVING_BOILERPLATE = false; + } + } + else { + for (let j = 0; j < BOILERPLATE.length; j++) { + const boilerplate = BOILERPLATE[j]; + if (boilerplate.start.test(line)) { + if (SEEN_BOILERPLATE[j]) { + IS_REMOVING_BOILERPLATE = true; + END_BOILERPLATE = boilerplate.end; } else { - newLines.push(line); + SEEN_BOILERPLATE[j] = true; } } } - source.contents = newLines.join('\n'); - }); - }); - return destFiles; + if (IS_REMOVING_BOILERPLATE) { + newLines.push(''); + } + else { + newLines.push(line); + } + } + } + return newLines.join('\n'); } -function emitEntryPoint(modulesMap, deps, entryPoint, includedModules, prepend, append, dest) { +function emitEntryPoint(modulesMap, deps, entryPoint, includedModules, prepend, dest) { if (!dest) { dest = entryPoint + '.js'; } @@ -346,8 +349,7 @@ function emitEntryPoint(modulesMap, deps, entryPoint, includedModules, prepend, }; }; const toPrepend = (prepend || []).map(toIFile); - const toAppend = (append || []).map(toIFile); - mainResult.sources = toPrepend.concat(mainResult.sources).concat(toAppend); + mainResult.sources = toPrepend.concat(mainResult.sources); return { files: results, usedPlugins: usedPlugins diff --git a/patched-vscode/build/lib/bundle.ts b/patched-vscode/build/lib/bundle.ts index c5fdc2da..6e3f96a5 100644 --- a/patched-vscode/build/lib/bundle.ts +++ b/patched-vscode/build/lib/bundle.ts @@ -52,8 +52,8 @@ export interface IEntryPoint { include?: string[]; exclude?: string[]; prepend?: IExtraFile[]; - append?: IExtraFile[]; dest?: string; + target?: 'amd' | 'esm'; } interface IEntryPointMap { @@ -138,14 +138,10 @@ export function bundle(entryPoints: IEntryPoint[], config: ILoaderConfig, callba const loader: any = loaderModule.exports; config.isBuild = true; config.paths = config.paths || {}; - if (!config.paths['vs/nls']) { - config.paths['vs/nls'] = 'out-build/vs/nls.build'; - } if (!config.paths['vs/css']) { config.paths['vs/css'] = 'out-build/vs/css.build'; } config.buildForceInvokeFactory = config.buildForceInvokeFactory || {}; - config.buildForceInvokeFactory['vs/nls'] = true; config.buildForceInvokeFactory['vs/css'] = true; loader.config(config); @@ -156,15 +152,11 @@ export function bundle(entryPoints: IEntryPoint[], config: ILoaderConfig, callba r += '.js'; } // avoid packaging the build version of plugins: - r = r.replace('vs/nls.build.js', 'vs/nls.js'); r = r.replace('vs/css.build.js', 'vs/css.js'); return { path: r, amdModuleId: entry.amdModuleId }; }; for (const moduleId in entryPointsMap) { const entryPoint = entryPointsMap[moduleId]; - if (entryPoint.append) { - entryPoint.append = entryPoint.append.map(resolvePath); - } if (entryPoint.prepend) { entryPoint.prepend = entryPoint.prepend.map(resolvePath); } @@ -228,7 +220,6 @@ function emitEntryPoints(modules: IBuildModuleInfo[], entryPoints: IEntryPointMa moduleToBundle, includedModules, info.prepend || [], - info.append || [], info.dest ); @@ -256,7 +247,7 @@ function emitEntryPoints(modules: IBuildModuleInfo[], entryPoints: IEntryPointMa return { // TODO@TS 2.1.2 - files: extractStrings(removeDuplicateTSBoilerplate(result)), + files: extractStrings(removeAllDuplicateTSBoilerplate(result)), bundleData: bundleData }; } @@ -355,58 +346,70 @@ function extractStrings(destFiles: IConcatFile[]): IConcatFile[] { return destFiles; } -function removeDuplicateTSBoilerplate(destFiles: IConcatFile[]): IConcatFile[] { - // Taken from typescript compiler => emitFiles - const BOILERPLATE = [ - { start: /^var __extends/, end: /^}\)\(\);$/ }, - { start: /^var __assign/, end: /^};$/ }, - { start: /^var __decorate/, end: /^};$/ }, - { start: /^var __metadata/, end: /^};$/ }, - { start: /^var __param/, end: /^};$/ }, - { start: /^var __awaiter/, end: /^};$/ }, - { start: /^var __generator/, end: /^};$/ }, - ]; - +function removeAllDuplicateTSBoilerplate(destFiles: IConcatFile[]): IConcatFile[] { destFiles.forEach((destFile) => { const SEEN_BOILERPLATE: boolean[] = []; destFile.sources.forEach((source) => { - const lines = source.contents.split(/\r\n|\n|\r/); - const newLines: string[] = []; - let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE: RegExp; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (IS_REMOVING_BOILERPLATE) { - newLines.push(''); - if (END_BOILERPLATE!.test(line)) { - IS_REMOVING_BOILERPLATE = false; - } - } else { - for (let j = 0; j < BOILERPLATE.length; j++) { - const boilerplate = BOILERPLATE[j]; - if (boilerplate.start.test(line)) { - if (SEEN_BOILERPLATE[j]) { - IS_REMOVING_BOILERPLATE = true; - END_BOILERPLATE = boilerplate.end; - } else { - SEEN_BOILERPLATE[j] = true; - } - } - } - if (IS_REMOVING_BOILERPLATE) { - newLines.push(''); - } else { - newLines.push(line); - } - } - } - source.contents = newLines.join('\n'); + source.contents = removeDuplicateTSBoilerplate(source.contents, SEEN_BOILERPLATE); }); }); return destFiles; } +export function removeAllTSBoilerplate(source: string) { + const seen = new Array(BOILERPLATE.length).fill(true, 0, 10); + return removeDuplicateTSBoilerplate(source, seen); +} + +// Taken from typescript compiler => emitFiles +const BOILERPLATE = [ + { start: /^var __extends/, end: /^}\)\(\);$/ }, + { start: /^var __assign/, end: /^};$/ }, + { start: /^var __decorate/, end: /^};$/ }, + { start: /^var __metadata/, end: /^};$/ }, + { start: /^var __param/, end: /^};$/ }, + { start: /^var __awaiter/, end: /^};$/ }, + { start: /^var __generator/, end: /^};$/ }, + { start: /^var __createBinding/, end: /^}\)\);$/ }, + { start: /^var __setModuleDefault/, end: /^}\);$/ }, + { start: /^var __importStar/, end: /^};$/ }, +]; + +function removeDuplicateTSBoilerplate(source: string, SEEN_BOILERPLATE: boolean[] = []): string { + const lines = source.split(/\r\n|\n|\r/); + const newLines: string[] = []; + let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE: RegExp; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (IS_REMOVING_BOILERPLATE) { + newLines.push(''); + if (END_BOILERPLATE!.test(line)) { + IS_REMOVING_BOILERPLATE = false; + } + } else { + for (let j = 0; j < BOILERPLATE.length; j++) { + const boilerplate = BOILERPLATE[j]; + if (boilerplate.start.test(line)) { + if (SEEN_BOILERPLATE[j]) { + IS_REMOVING_BOILERPLATE = true; + END_BOILERPLATE = boilerplate.end; + } else { + SEEN_BOILERPLATE[j] = true; + } + } + } + if (IS_REMOVING_BOILERPLATE) { + newLines.push(''); + } else { + newLines.push(line); + } + } + } + return newLines.join('\n'); +} + interface IPluginMap { [moduleId: string]: ILoaderPlugin; } @@ -422,7 +425,6 @@ function emitEntryPoint( entryPoint: string, includedModules: string[], prepend: IExtraFile[], - append: IExtraFile[], dest: string | undefined ): IEmitEntryPointResult { if (!dest) { @@ -508,9 +510,8 @@ function emitEntryPoint( }; const toPrepend = (prepend || []).map(toIFile); - const toAppend = (append || []).map(toIFile); - mainResult.sources = toPrepend.concat(mainResult.sources).concat(toAppend); + mainResult.sources = toPrepend.concat(mainResult.sources); return { files: results, diff --git a/patched-vscode/build/lib/compilation.js b/patched-vscode/build/lib/compilation.js index b44cbefe..e6fe4b59 100644 --- a/patched-vscode/build/lib/compilation.js +++ b/patched-vscode/build/lib/compilation.js @@ -41,7 +41,7 @@ function getTypeScriptCompilerOptions(src) { options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 0 : 1; return options; } -function createCompile(src, build, emitError, transpileOnly) { +function createCompile(src, { build, emitError, transpileOnly, preserveEnglish }) { const tsb = require('./tsb'); const sourcemaps = require('gulp-sourcemaps'); const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json'); @@ -71,7 +71,7 @@ function createCompile(src, build, emitError, transpileOnly) { .pipe(util.loadSourcemaps()) .pipe(compilation(token)) .pipe(noDeclarationsFilter) - .pipe(util.$if(build, nls.nls())) + .pipe(util.$if(build, nls.nls({ preserveEnglish }))) .pipe(noDeclarationsFilter.restore) .pipe(util.$if(!transpileOnly, sourcemaps.write('.', { addComment: false, @@ -90,7 +90,7 @@ function createCompile(src, build, emitError, transpileOnly) { } function transpileTask(src, out, swc) { const task = () => { - const transpile = createCompile(src, false, true, { swc }); + const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { swc }, preserveEnglish: false }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); return srcPipe .pipe(transpile()) @@ -104,7 +104,7 @@ function compileTask(src, out, build, options = {}) { if (os.totalmem() < 4_000_000_000) { throw new Error('compilation requires 4GB of RAM'); } - const compile = createCompile(src, build, true, false); + const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); const generator = new MonacoGenerator(false); if (src === 'src') { @@ -139,11 +139,11 @@ function compileTask(src, out, build, options = {}) { task.taskName = `compile-${path.basename(src)}`; return task; } -function watchTask(out, build) { +function watchTask(out, build, srcPath = 'src') { const task = () => { - const compile = createCompile('src', build, false, false); - const src = gulp.src('src/**', { base: 'src' }); - const watchSrc = watch('src/**', { base: 'src', readDelay: 200 }); + const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false }); + const src = gulp.src(`${srcPath}/**`, { base: srcPath }); + const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 }); const generator = new MonacoGenerator(true); generator.execute(); return watchSrc @@ -234,7 +234,7 @@ class MonacoGenerator { function generateApiProposalNames() { let eol; try { - const src = fs.readFileSync('src/vs/workbench/services/extensions/common/extensionsApiProposals.ts', 'utf-8'); + const src = fs.readFileSync('src/vs/platform/extensions/common/extensionsApiProposals.ts', 'utf-8'); const match = /\r?\n/m.exec(src); eol = match ? match[0] : os.EOL; } @@ -242,18 +242,27 @@ function generateApiProposalNames() { eol = os.EOL; } const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/; - const proposalNames = new Set(); + const versionPattern = /^\s*\/\/\s*version\s*:\s*(\d+)\s*$/mi; + const proposals = new Map(); const input = es.through(); const output = input .pipe(util.filter((f) => pattern.test(f.path))) .pipe(es.through((f) => { const name = path.basename(f.path); const match = pattern.exec(name); - if (match) { - proposalNames.add(match[1]); + if (!match) { + return; } + const proposalName = match[1]; + const contents = f.contents.toString('utf8'); + const versionMatch = versionPattern.exec(contents); + const version = versionMatch ? versionMatch[1] : undefined; + proposals.set(proposalName, { + proposal: `https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${proposalName}.d.ts`, + version: version ? parseInt(version) : undefined + }); }, function () { - const names = [...proposalNames.values()].sort(); + const names = [...proposals.keys()].sort(); const contents = [ '/*---------------------------------------------------------------------------------------------', ' * Copyright (c) Microsoft Corporation. All rights reserved.', @@ -262,14 +271,18 @@ function generateApiProposalNames() { '', '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', '', - 'export const allApiProposals = Object.freeze({', - `${names.map(name => `\t${name}: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${name}.d.ts'`).join(`,${eol}`)}`, - '});', - 'export type ApiProposalName = keyof typeof allApiProposals;', + 'const _allApiProposals = {', + `${names.map(proposalName => { + const proposal = proposals.get(proposalName); + return `\t${proposalName}: {${eol}\t\tproposal: '${proposal.proposal}',${eol}${proposal.version ? `\t\tversion: ${proposal.version}${eol}` : ''}\t}`; + }).join(`,${eol}`)}`, + '};', + 'export const allApiProposals = Object.freeze<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>(_allApiProposals);', + 'export type ApiProposalName = keyof typeof _allApiProposals;', '', ].join(eol); this.emit('data', new File({ - path: 'vs/workbench/services/extensions/common/extensionsApiProposals.ts', + path: 'vs/platform/extensions/common/extensionsApiProposals.ts', contents: Buffer.from(contents) })); this.emit('end'); diff --git a/patched-vscode/build/lib/compilation.ts b/patched-vscode/build/lib/compilation.ts index b88d0d29..978fb15d 100644 --- a/patched-vscode/build/lib/compilation.ts +++ b/patched-vscode/build/lib/compilation.ts @@ -42,7 +42,14 @@ function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { return options; } -function createCompile(src: string, build: boolean, emitError: boolean, transpileOnly: boolean | { swc: boolean }) { +interface ICompileTaskOptions { + readonly build: boolean; + readonly emitError: boolean; + readonly transpileOnly: boolean | { swc: boolean }; + readonly preserveEnglish: boolean; +} + +function createCompile(src: string, { build, emitError, transpileOnly, preserveEnglish }: ICompileTaskOptions) { const tsb = require('./tsb') as typeof import('./tsb'); const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); @@ -79,7 +86,7 @@ function createCompile(src: string, build: boolean, emitError: boolean, transpil .pipe(util.loadSourcemaps()) .pipe(compilation(token)) .pipe(noDeclarationsFilter) - .pipe(util.$if(build, nls.nls())) + .pipe(util.$if(build, nls.nls({ preserveEnglish }))) .pipe(noDeclarationsFilter.restore) .pipe(util.$if(!transpileOnly, sourcemaps.write('.', { addComment: false, @@ -102,7 +109,7 @@ export function transpileTask(src: string, out: string, swc: boolean): task.Stre const task = () => { - const transpile = createCompile(src, false, true, { swc }); + const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { swc }, preserveEnglish: false }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); return srcPipe @@ -114,7 +121,7 @@ export function transpileTask(src: string, out: string, swc: boolean): task.Stre return task; } -export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean } = {}): task.StreamTask { +export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean; preserveEnglish?: boolean } = {}): task.StreamTask { const task = () => { @@ -122,7 +129,7 @@ export function compileTask(src: string, out: string, build: boolean, options: { throw new Error('compilation requires 4GB of RAM'); } - const compile = createCompile(src, build, true, false); + const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); const generator = new MonacoGenerator(false); if (src === 'src') { @@ -163,13 +170,13 @@ export function compileTask(src: string, out: string, build: boolean, options: { return task; } -export function watchTask(out: string, build: boolean): task.StreamTask { +export function watchTask(out: string, build: boolean, srcPath: string = 'src'): task.StreamTask { const task = () => { - const compile = createCompile('src', build, false, false); + const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false }); - const src = gulp.src('src/**', { base: 'src' }); - const watchSrc = watch('src/**', { base: 'src', readDelay: 200 }); + const src = gulp.src(`${srcPath}/**`, { base: srcPath }); + const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 }); const generator = new MonacoGenerator(true); generator.execute(); @@ -275,7 +282,7 @@ function generateApiProposalNames() { let eol: string; try { - const src = fs.readFileSync('src/vs/workbench/services/extensions/common/extensionsApiProposals.ts', 'utf-8'); + const src = fs.readFileSync('src/vs/platform/extensions/common/extensionsApiProposals.ts', 'utf-8'); const match = /\r?\n/m.exec(src); eol = match ? match[0] : os.EOL; } catch { @@ -283,7 +290,8 @@ function generateApiProposalNames() { } const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/; - const proposalNames = new Set(); + const versionPattern = /^\s*\/\/\s*version\s*:\s*(\d+)\s*$/mi; + const proposals = new Map(); const input = es.through(); const output = input @@ -292,11 +300,22 @@ function generateApiProposalNames() { const name = path.basename(f.path); const match = pattern.exec(name); - if (match) { - proposalNames.add(match[1]); + if (!match) { + return; } + + const proposalName = match[1]; + + const contents = f.contents.toString('utf8'); + const versionMatch = versionPattern.exec(contents); + const version = versionMatch ? versionMatch[1] : undefined; + + proposals.set(proposalName, { + proposal: `https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${proposalName}.d.ts`, + version: version ? parseInt(version) : undefined + }); }, function () { - const names = [...proposalNames.values()].sort(); + const names = [...proposals.keys()].sort(); const contents = [ '/*---------------------------------------------------------------------------------------------', ' * Copyright (c) Microsoft Corporation. All rights reserved.', @@ -305,15 +324,19 @@ function generateApiProposalNames() { '', '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', '', - 'export const allApiProposals = Object.freeze({', - `${names.map(name => `\t${name}: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${name}.d.ts'`).join(`,${eol}`)}`, - '});', - 'export type ApiProposalName = keyof typeof allApiProposals;', + 'const _allApiProposals = {', + `${names.map(proposalName => { + const proposal = proposals.get(proposalName)!; + return `\t${proposalName}: {${eol}\t\tproposal: '${proposal.proposal}',${eol}${proposal.version ? `\t\tversion: ${proposal.version}${eol}` : ''}\t}`; + }).join(`,${eol}`)}`, + '};', + 'export const allApiProposals = Object.freeze<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>(_allApiProposals);', + 'export type ApiProposalName = keyof typeof _allApiProposals;', '', ].join(eol); this.emit('data', new File({ - path: 'vs/workbench/services/extensions/common/extensionsApiProposals.ts', + path: 'vs/platform/extensions/common/extensionsApiProposals.ts', contents: Buffer.from(contents) })); this.emit('end'); diff --git a/patched-vscode/build/lib/date.js b/patched-vscode/build/lib/date.js new file mode 100644 index 00000000..77fff0e5 --- /dev/null +++ b/patched-vscode/build/lib/date.js @@ -0,0 +1,32 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.writeISODate = writeISODate; +exports.readISODate = readISODate; +const path = require("path"); +const fs = require("fs"); +const root = path.join(__dirname, '..', '..'); +/** + * Writes a `outDir/date` file with the contents of the build + * so that other tasks during the build process can use it and + * all use the same date. + */ +function writeISODate(outDir) { + const result = () => new Promise((resolve, _) => { + const outDirectory = path.join(root, outDir); + fs.mkdirSync(outDirectory, { recursive: true }); + const date = new Date().toISOString(); + fs.writeFileSync(path.join(outDirectory, 'date'), date, 'utf8'); + resolve(); + }); + result.taskName = 'build-date-file'; + return result; +} +function readISODate(outDir) { + const outDirectory = path.join(root, outDir); + return fs.readFileSync(path.join(outDirectory, 'date'), 'utf8'); +} +//# sourceMappingURL=date.js.map \ No newline at end of file diff --git a/patched-vscode/build/lib/date.ts b/patched-vscode/build/lib/date.ts new file mode 100644 index 00000000..998e89f8 --- /dev/null +++ b/patched-vscode/build/lib/date.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as fs from 'fs'; + +const root = path.join(__dirname, '..', '..'); + +/** + * Writes a `outDir/date` file with the contents of the build + * so that other tasks during the build process can use it and + * all use the same date. + */ +export function writeISODate(outDir: string) { + const result = () => new Promise((resolve, _) => { + const outDirectory = path.join(root, outDir); + fs.mkdirSync(outDirectory, { recursive: true }); + + const date = new Date().toISOString(); + fs.writeFileSync(path.join(outDirectory, 'date'), date, 'utf8'); + + resolve(); + }); + result.taskName = 'build-date-file'; + return result; +} + +export function readISODate(outDir: string): string { + const outDirectory = path.join(root, outDir); + return fs.readFileSync(path.join(outDirectory, 'date'), 'utf8'); +} diff --git a/patched-vscode/build/lib/electron.js b/patched-vscode/build/lib/electron.js index 8524b188..99252e4e 100644 --- a/patched-vscode/build/lib/electron.js +++ b/patched-vscode/build/lib/electron.js @@ -54,7 +54,7 @@ function darwinBundleDocumentType(extensions, icon, nameOrSuffix, utis) { role: 'Editor', ostypes: ['TEXT', 'utxt', 'TUTX', '****'], extensions, - iconFile: 'resources/darwin/' + icon + '.icns', + iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', utis }; } diff --git a/patched-vscode/build/lib/electron.ts b/patched-vscode/build/lib/electron.ts index ba93c3a2..7a2a2a19 100644 --- a/patched-vscode/build/lib/electron.ts +++ b/patched-vscode/build/lib/electron.ts @@ -68,7 +68,7 @@ function darwinBundleDocumentType(extensions: string[], icon: string, nameOrSuff role: 'Editor', ostypes: ['TEXT', 'utxt', 'TUTX', '****'], extensions, - iconFile: 'resources/darwin/' + icon + '.icns', + iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', utis }; } diff --git a/patched-vscode/build/lib/esm.js b/patched-vscode/build/lib/esm.js new file mode 100644 index 00000000..db268344 --- /dev/null +++ b/patched-vscode/build/lib/esm.js @@ -0,0 +1,41 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.setESM = setESM; +exports.isESM = isESM; +const path = require("path"); +const fs = require("fs"); +// TODO@esm remove this +const outDirectory = path.join(__dirname, '..', '..', 'out-build'); +const esmMarkerFile = path.join(outDirectory, 'esm'); +function setESM(enabled) { + const result = () => new Promise((resolve, _) => { + if (enabled) { + fs.mkdirSync(outDirectory, { recursive: true }); + fs.writeFileSync(esmMarkerFile, 'true', 'utf8'); + console.warn(`Setting build to ESM: true`); + } + else { + console.warn(`Setting build to ESM: false`); + } + resolve(); + }); + result.taskName = 'set-esm'; + return result; +} +function isESM(logWarning) { + try { + const res = (typeof process.env.VSCODE_BUILD_ESM === 'string' && process.env.VSCODE_BUILD_ESM.toLowerCase() === 'true') || (fs.readFileSync(esmMarkerFile, 'utf8') === 'true'); + if (res && logWarning) { + console.warn(`[esm] ${logWarning}`); + } + return res; + } + catch (error) { + return false; + } +} +//# sourceMappingURL=esm.js.map \ No newline at end of file diff --git a/patched-vscode/build/lib/esm.ts b/patched-vscode/build/lib/esm.ts new file mode 100644 index 00000000..2fc5215e --- /dev/null +++ b/patched-vscode/build/lib/esm.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as fs from 'fs'; + +// TODO@esm remove this + +const outDirectory = path.join(__dirname, '..', '..', 'out-build'); +const esmMarkerFile = path.join(outDirectory, 'esm'); + +export function setESM(enabled: boolean) { + const result = () => new Promise((resolve, _) => { + if (enabled) { + fs.mkdirSync(outDirectory, { recursive: true }); + fs.writeFileSync(esmMarkerFile, 'true', 'utf8'); + console.warn(`Setting build to ESM: true`); + } else { + console.warn(`Setting build to ESM: false`); + } + + resolve(); + }); + result.taskName = 'set-esm'; + return result; +} + +export function isESM(logWarning?: string): boolean { + try { + const res = (typeof process.env.VSCODE_BUILD_ESM === 'string' && process.env.VSCODE_BUILD_ESM.toLowerCase() === 'true') || (fs.readFileSync(esmMarkerFile, 'utf8') === 'true'); + if (res && logWarning) { + console.warn(`[esm] ${logWarning}`); + } + return res; + } catch (error) { + return false; + } +} diff --git a/patched-vscode/build/lib/extensions.js b/patched-vscode/build/lib/extensions.js index 6a6c0a7b..7bc9cb51 100644 --- a/patched-vscode/build/lib/extensions.js +++ b/patched-vscode/build/lib/extensions.js @@ -34,7 +34,7 @@ const getVersion_1 = require("./getVersion"); const fetch_1 = require("./fetch"); const root = path.dirname(path.dirname(__dirname)); const commit = (0, getVersion_1.getVersion)(root); -const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; +const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; function minifyExtensionResources(input) { const jsonFilter = filter(['**/*.json', '**/*.code-snippets'], { restore: true }); return input @@ -482,9 +482,6 @@ async function esbuildExtensions(taskName, isWatch, scripts) { return reject(error); } reporter(stderr, script); - if (stderr) { - return reject(); - } return resolve(); }); proc.stdout.on('data', (data) => { diff --git a/patched-vscode/build/lib/extensions.ts b/patched-vscode/build/lib/extensions.ts index 6edfdcb6..5c7fa453 100644 --- a/patched-vscode/build/lib/extensions.ts +++ b/patched-vscode/build/lib/extensions.ts @@ -28,7 +28,7 @@ import { fetchUrls, fetchGithub } from './fetch'; const root = path.dirname(path.dirname(__dirname)); const commit = getVersion(root); -const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; +const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; function minifyExtensionResources(input: Stream): Stream { const jsonFilter = filter(['**/*.json', '**/*.code-snippets'], { restore: true }); @@ -563,9 +563,6 @@ async function esbuildExtensions(taskName: string, isWatch: boolean, scripts: { return reject(error); } reporter(stderr, script); - if (stderr) { - return reject(); - } return resolve(); }); diff --git a/patched-vscode/build/lib/fetch.js b/patched-vscode/build/lib/fetch.js index 2fed63bc..b7da65f4 100644 --- a/patched-vscode/build/lib/fetch.js +++ b/patched-vscode/build/lib/fetch.js @@ -33,7 +33,7 @@ function fetchUrls(urls, options) { })); } async function fetchUrl(url, options, retries = 10, retryDelay = 1000) { - const verbose = !!options.verbose ?? (!!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY']); + const verbose = !!options.verbose || !!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY']; try { let startTime = 0; if (verbose) { diff --git a/patched-vscode/build/lib/fetch.ts b/patched-vscode/build/lib/fetch.ts index dc1de777..0c44b8e5 100644 --- a/patched-vscode/build/lib/fetch.ts +++ b/patched-vscode/build/lib/fetch.ts @@ -42,7 +42,7 @@ export function fetchUrls(urls: string[] | string, options: IFetchOptions): es.T } export async function fetchUrl(url: string, options: IFetchOptions, retries = 10, retryDelay = 1000): Promise { - const verbose = !!options.verbose ?? (!!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY']); + const verbose = !!options.verbose || !!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY']; try { let startTime = 0; if (verbose) { diff --git a/patched-vscode/build/lib/i18n.js b/patched-vscode/build/lib/i18n.js index c3399498..69646162 100644 --- a/patched-vscode/build/lib/i18n.js +++ b/patched-vscode/build/lib/i18n.js @@ -23,6 +23,7 @@ const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); const iconv = require("@vscode/iconv-lite-umd"); const l10n_dev_1 = require("@vscode/l10n-dev"); +const REPO_ROOT_PATH = path.join(__dirname, '../..'); function log(message, ...rest) { fancyLog(ansiColors.green('[i18n]'), message, ...rest); } @@ -63,6 +64,17 @@ var BundledFormat; } BundledFormat.is = is; })(BundledFormat || (BundledFormat = {})); +var NLSKeysFormat; +(function (NLSKeysFormat) { + function is(value) { + if (value === undefined) { + return false; + } + const candidate = value; + return Array.isArray(candidate) && Array.isArray(candidate[1]); + } + NLSKeysFormat.is = is; +})(NLSKeysFormat || (NLSKeysFormat = {})); class Line { buffer = []; constructor(indent = 0) { @@ -265,67 +277,8 @@ function stripComments(content) { }); return result; } -function escapeCharacters(value) { - const result = []; - for (let i = 0; i < value.length; i++) { - const ch = value.charAt(i); - switch (ch) { - case '\'': - result.push('\\\''); - break; - case '"': - result.push('\\"'); - break; - case '\\': - result.push('\\\\'); - break; - case '\n': - result.push('\\n'); - break; - case '\r': - result.push('\\r'); - break; - case '\t': - result.push('\\t'); - break; - case '\b': - result.push('\\b'); - break; - case '\f': - result.push('\\f'); - break; - default: - result.push(ch); - } - } - return result.join(''); -} -function processCoreBundleFormat(fileHeader, languages, json, emitter) { - const keysSection = json.keys; - const messageSection = json.messages; - const bundleSection = json.bundles; - const statistics = Object.create(null); - const defaultMessages = Object.create(null); - const modules = Object.keys(keysSection); - modules.forEach((module) => { - const keys = keysSection[module]; - const messages = messageSection[module]; - if (!messages || keys.length !== messages.length) { - emitter.emit('error', `Message for module ${module} corrupted. Mismatch in number of keys and messages.`); - return; - } - const messageMap = Object.create(null); - defaultMessages[module] = messageMap; - keys.map((key, i) => { - if (typeof key === 'string') { - messageMap[key] = messages[i]; - } - else { - messageMap[key.key] = messages[i]; - } - }); - }); - const languageDirectory = path.join(__dirname, '..', '..', '..', 'vscode-loc', 'i18n'); +function processCoreBundleFormat(base, fileHeader, languages, json, emitter) { + const languageDirectory = path.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n'); if (!fs.existsSync(languageDirectory)) { log(`No VS Code localization repository found. Looking at ${languageDirectory}`); log(`To bundle translations please check out the vscode-loc repository as a sibling of the vscode repository.`); @@ -335,8 +288,6 @@ function processCoreBundleFormat(fileHeader, languages, json, emitter) { if (process.env['VSCODE_BUILD_VERBOSE']) { log(`Generating nls bundles for: ${language.id}`); } - statistics[language.id] = 0; - const localizedModules = Object.create(null); const languageFolderName = language.translationId || language.id; const i18nFile = path.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json'); let allMessages; @@ -344,87 +295,36 @@ function processCoreBundleFormat(fileHeader, languages, json, emitter) { const content = stripComments(fs.readFileSync(i18nFile, 'utf8')); allMessages = JSON.parse(content); } - modules.forEach((module) => { - const order = keysSection[module]; - let moduleMessage; - if (allMessages) { - moduleMessage = allMessages.contents[module]; + let nlsIndex = 0; + const nlsResult = []; + for (const [moduleId, nlsKeys] of json) { + const moduleTranslations = allMessages?.contents[moduleId]; + for (const nlsKey of nlsKeys) { + nlsResult.push(moduleTranslations?.[nlsKey]); // pushing `undefined` is fine, as we keep english strings as fallback for monaco editor in the build + nlsIndex++; } - if (!moduleMessage) { - if (process.env['VSCODE_BUILD_VERBOSE']) { - log(`No localized messages found for module ${module}. Using default messages.`); - } - moduleMessage = defaultMessages[module]; - statistics[language.id] = statistics[language.id] + Object.keys(moduleMessage).length; - } - const localizedMessages = []; - order.forEach((keyInfo) => { - let key = null; - if (typeof keyInfo === 'string') { - key = keyInfo; - } - else { - key = keyInfo.key; - } - let message = moduleMessage[key]; - if (!message) { - if (process.env['VSCODE_BUILD_VERBOSE']) { - log(`No localized message found for key ${key} in module ${module}. Using default message.`); - } - message = defaultMessages[module][key]; - statistics[language.id] = statistics[language.id] + 1; - } - localizedMessages.push(message); - }); - localizedModules[module] = localizedMessages; - }); - Object.keys(bundleSection).forEach((bundle) => { - const modules = bundleSection[bundle]; - const contents = [ - fileHeader, - `define("${bundle}.nls.${language.id}", {` - ]; - modules.forEach((module, index) => { - contents.push(`\t"${module}": [`); - const messages = localizedModules[module]; - if (!messages) { - emitter.emit('error', `Didn't find messages for module ${module}.`); - return; - } - messages.forEach((message, index) => { - contents.push(`\t\t"${escapeCharacters(message)}${index < messages.length ? '",' : '"'}`); - }); - contents.push(index < modules.length - 1 ? '\t],' : '\t]'); - }); - contents.push('});'); - emitter.queue(new File({ path: bundle + '.nls.' + language.id + '.js', contents: Buffer.from(contents.join('\n'), 'utf-8') })); - }); - }); - Object.keys(statistics).forEach(key => { - const value = statistics[key]; - log(`${key} has ${value} untranslated strings.`); - }); - sortedLanguages.forEach(language => { - const stats = statistics[language.id]; - if (!stats) { - log(`\tNo translations found for language ${language.id}. Using default language instead.`); } + emitter.queue(new File({ + contents: Buffer.from(`${fileHeader} +globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(nlsResult)}; +globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(language.id)};`), + base, + path: `${base}/nls.messages.${language.id}.js` + })); }); } function processNlsFiles(opts) { return (0, event_stream_1.through)(function (file) { const fileName = path.basename(file.path); - if (fileName === 'nls.metadata.json') { - let json = null; - if (file.isBuffer()) { - json = JSON.parse(file.contents.toString('utf8')); - } - else { - this.emit('error', `Failed to read component file: ${file.relative}`); - return; + if (fileName === 'bundleInfo.json') { // pick a root level file to put the core bundles (TODO@esm this file is not created anymore, pick another) + try { + const json = JSON.parse(fs.readFileSync(path.join(REPO_ROOT_PATH, opts.out, 'nls.keys.json')).toString()); + if (NLSKeysFormat.is(json)) { + processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this); + } } - if (BundledFormat.is(json)) { - processCoreBundleFormat(opts.fileHeader, opts.languages, json, this); + catch (error) { + this.emit('error', `Failed to read component file: ${error}`); } } this.queue(file); diff --git a/patched-vscode/build/lib/i18n.resources.json b/patched-vscode/build/lib/i18n.resources.json index b080b05f..fb732ae1 100644 --- a/patched-vscode/build/lib/i18n.resources.json +++ b/patched-vscode/build/lib/i18n.resources.json @@ -561,6 +561,14 @@ { "name": "vs/workbench/contrib/authentication", "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/replNotebook", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/list", + "project": "vscode-workbench" } ] } diff --git a/patched-vscode/build/lib/i18n.ts b/patched-vscode/build/lib/i18n.ts index 444e3abe..cd7e522a 100644 --- a/patched-vscode/build/lib/i18n.ts +++ b/patched-vscode/build/lib/i18n.ts @@ -16,6 +16,8 @@ import * as ansiColors from 'ansi-colors'; import * as iconv from '@vscode/iconv-lite-umd'; import { l10nJsonFormat, getL10nXlf, l10nJsonDetails, getL10nFilesFromXlf, getL10nJson } from '@vscode/l10n-dev'; +const REPO_ROOT_PATH = path.join(__dirname, '../..'); + function log(message: any, ...rest: any[]): void { fancyLog(ansiColors.green('[i18n]'), message, ...rest); } @@ -91,6 +93,19 @@ module BundledFormat { } } +type NLSKeysFormat = [string /* module ID */, string[] /* keys */]; + +module NLSKeysFormat { + export function is(value: any): value is NLSKeysFormat { + if (value === undefined) { + return false; + } + + const candidate = value as NLSKeysFormat; + return Array.isArray(candidate) && Array.isArray(candidate[1]); + } +} + interface BundledExtensionFormat { [key: string]: { messages: string[]; @@ -329,70 +344,8 @@ function stripComments(content: string): string { return result; } -function escapeCharacters(value: string): string { - const result: string[] = []; - for (let i = 0; i < value.length; i++) { - const ch = value.charAt(i); - switch (ch) { - case '\'': - result.push('\\\''); - break; - case '"': - result.push('\\"'); - break; - case '\\': - result.push('\\\\'); - break; - case '\n': - result.push('\\n'); - break; - case '\r': - result.push('\\r'); - break; - case '\t': - result.push('\\t'); - break; - case '\b': - result.push('\\b'); - break; - case '\f': - result.push('\\f'); - break; - default: - result.push(ch); - } - } - return result.join(''); -} - -function processCoreBundleFormat(fileHeader: string, languages: Language[], json: BundledFormat, emitter: ThroughStream) { - const keysSection = json.keys; - const messageSection = json.messages; - const bundleSection = json.bundles; - - const statistics: Record = Object.create(null); - - const defaultMessages: Record> = Object.create(null); - const modules = Object.keys(keysSection); - modules.forEach((module) => { - const keys = keysSection[module]; - const messages = messageSection[module]; - if (!messages || keys.length !== messages.length) { - emitter.emit('error', `Message for module ${module} corrupted. Mismatch in number of keys and messages.`); - return; - } - const messageMap: Record = Object.create(null); - defaultMessages[module] = messageMap; - keys.map((key, i) => { - if (typeof key === 'string') { - messageMap[key] = messages[i]; - } else { - messageMap[key.key] = messages[i]; - } - }); - }); - - const languageDirectory = path.join(__dirname, '..', '..', '..', 'vscode-loc', 'i18n'); +function processCoreBundleFormat(base: string, fileHeader: string, languages: Language[], json: NLSKeysFormat, emitter: ThroughStream) { + const languageDirectory = path.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n'); if (!fs.existsSync(languageDirectory)) { log(`No VS Code localization repository found. Looking at ${languageDirectory}`); log(`To bundle translations please check out the vscode-loc repository as a sibling of the vscode repository.`); @@ -403,8 +356,6 @@ function processCoreBundleFormat(fileHeader: string, languages: Language[], json log(`Generating nls bundles for: ${language.id}`); } - statistics[language.id] = 0; - const localizedModules: Record = Object.create(null); const languageFolderName = language.translationId || language.id; const i18nFile = path.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json'); let allMessages: I18nFormat | undefined; @@ -412,86 +363,38 @@ function processCoreBundleFormat(fileHeader: string, languages: Language[], json const content = stripComments(fs.readFileSync(i18nFile, 'utf8')); allMessages = JSON.parse(content); } - modules.forEach((module) => { - const order = keysSection[module]; - let moduleMessage: { [messageKey: string]: string } | undefined; - if (allMessages) { - moduleMessage = allMessages.contents[module]; - } - if (!moduleMessage) { - if (process.env['VSCODE_BUILD_VERBOSE']) { - log(`No localized messages found for module ${module}. Using default messages.`); - } - moduleMessage = defaultMessages[module]; - statistics[language.id] = statistics[language.id] + Object.keys(moduleMessage).length; + + let nlsIndex = 0; + const nlsResult: Array = []; + for (const [moduleId, nlsKeys] of json) { + const moduleTranslations = allMessages?.contents[moduleId]; + for (const nlsKey of nlsKeys) { + nlsResult.push(moduleTranslations?.[nlsKey]); // pushing `undefined` is fine, as we keep english strings as fallback for monaco editor in the build + nlsIndex++; } - const localizedMessages: string[] = []; - order.forEach((keyInfo) => { - let key: string | null = null; - if (typeof keyInfo === 'string') { - key = keyInfo; - } else { - key = keyInfo.key; - } - let message: string = moduleMessage![key]; - if (!message) { - if (process.env['VSCODE_BUILD_VERBOSE']) { - log(`No localized message found for key ${key} in module ${module}. Using default message.`); - } - message = defaultMessages[module][key]; - statistics[language.id] = statistics[language.id] + 1; - } - localizedMessages.push(message); - }); - localizedModules[module] = localizedMessages; - }); - Object.keys(bundleSection).forEach((bundle) => { - const modules = bundleSection[bundle]; - const contents: string[] = [ - fileHeader, - `define("${bundle}.nls.${language.id}", {` - ]; - modules.forEach((module, index) => { - contents.push(`\t"${module}": [`); - const messages = localizedModules[module]; - if (!messages) { - emitter.emit('error', `Didn't find messages for module ${module}.`); - return; - } - messages.forEach((message, index) => { - contents.push(`\t\t"${escapeCharacters(message)}${index < messages.length ? '",' : '"'}`); - }); - contents.push(index < modules.length - 1 ? '\t],' : '\t]'); - }); - contents.push('});'); - emitter.queue(new File({ path: bundle + '.nls.' + language.id + '.js', contents: Buffer.from(contents.join('\n'), 'utf-8') })); - }); - }); - Object.keys(statistics).forEach(key => { - const value = statistics[key]; - log(`${key} has ${value} untranslated strings.`); - }); - sortedLanguages.forEach(language => { - const stats = statistics[language.id]; - if (!stats) { - log(`\tNo translations found for language ${language.id}. Using default language instead.`); } + + emitter.queue(new File({ + contents: Buffer.from(`${fileHeader} +globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(nlsResult)}; +globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(language.id)};`), + base, + path: `${base}/nls.messages.${language.id}.js` + })); }); } -export function processNlsFiles(opts: { fileHeader: string; languages: Language[] }): ThroughStream { +export function processNlsFiles(opts: { out: string; fileHeader: string; languages: Language[] }): ThroughStream { return through(function (this: ThroughStream, file: File) { const fileName = path.basename(file.path); - if (fileName === 'nls.metadata.json') { - let json = null; - if (file.isBuffer()) { - json = JSON.parse((file.contents).toString('utf8')); - } else { - this.emit('error', `Failed to read component file: ${file.relative}`); - return; - } - if (BundledFormat.is(json)) { - processCoreBundleFormat(opts.fileHeader, opts.languages, json, this); + if (fileName === 'bundleInfo.json') { // pick a root level file to put the core bundles (TODO@esm this file is not created anymore, pick another) + try { + const json = JSON.parse(fs.readFileSync(path.join(REPO_ROOT_PATH, opts.out, 'nls.keys.json')).toString()); + if (NLSKeysFormat.is(json)) { + processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this); + } + } catch (error) { + this.emit('error', `Failed to read component file: ${error}`); } } this.queue(file); diff --git a/patched-vscode/build/lib/inlineMeta.js b/patched-vscode/build/lib/inlineMeta.js new file mode 100644 index 00000000..f1dbfa83 --- /dev/null +++ b/patched-vscode/build/lib/inlineMeta.js @@ -0,0 +1,48 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.inlineMeta = inlineMeta; +const es = require("event-stream"); +const path_1 = require("path"); +const packageJsonMarkerId = 'BUILD_INSERT_PACKAGE_CONFIGURATION'; +// TODO@bpasero in order to inline `product.json`, more work is +// needed to ensure that we cover all cases where modifications +// are done to the product configuration during build. There are +// at least 2 more changes that kick in very late: +// - a `darwinUniversalAssetId` is added in`create-universal-app.ts` +// - a `target` is added in `gulpfile.vscode.win32.js` +// const productJsonMarkerId = 'BUILD_INSERT_PRODUCT_CONFIGURATION'; +function inlineMeta(result, ctx) { + return result.pipe(es.through(function (file) { + if (matchesFile(file, ctx)) { + let content = file.contents.toString(); + let markerFound = false; + const packageMarker = `${packageJsonMarkerId}:"${packageJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) + if (content.includes(packageMarker)) { + content = content.replace(packageMarker, JSON.stringify(JSON.parse(ctx.packageJsonFn())).slice(1, -1) /* trim braces */); + markerFound = true; + } + // const productMarker = `${productJsonMarkerId}:"${productJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) + // if (content.includes(productMarker)) { + // content = content.replace(productMarker, JSON.stringify(JSON.parse(ctx.productJsonFn())).slice(1, -1) /* trim braces */); + // markerFound = true; + // } + if (markerFound) { + file.contents = Buffer.from(content); + } + } + this.emit('data', file); + })); +} +function matchesFile(file, ctx) { + for (const targetPath of ctx.targetPaths) { + if (file.basename === (0, path_1.basename)(targetPath)) { // TODO would be nicer to figure out root relative path to not match on false positives + return true; + } + } + return false; +} +//# sourceMappingURL=inlineMeta.js.map \ No newline at end of file diff --git a/patched-vscode/build/lib/inlineMeta.ts b/patched-vscode/build/lib/inlineMeta.ts new file mode 100644 index 00000000..ef3987fc --- /dev/null +++ b/patched-vscode/build/lib/inlineMeta.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as es from 'event-stream'; +import { basename } from 'path'; +import * as File from 'vinyl'; + +export interface IInlineMetaContext { + readonly targetPaths: string[]; + readonly packageJsonFn: () => string; + readonly productJsonFn: () => string; +} + +const packageJsonMarkerId = 'BUILD_INSERT_PACKAGE_CONFIGURATION'; + +// TODO@bpasero in order to inline `product.json`, more work is +// needed to ensure that we cover all cases where modifications +// are done to the product configuration during build. There are +// at least 2 more changes that kick in very late: +// - a `darwinUniversalAssetId` is added in`create-universal-app.ts` +// - a `target` is added in `gulpfile.vscode.win32.js` +// const productJsonMarkerId = 'BUILD_INSERT_PRODUCT_CONFIGURATION'; + +export function inlineMeta(result: NodeJS.ReadWriteStream, ctx: IInlineMetaContext): NodeJS.ReadWriteStream { + return result.pipe(es.through(function (file: File) { + if (matchesFile(file, ctx)) { + let content = file.contents.toString(); + let markerFound = false; + + const packageMarker = `${packageJsonMarkerId}:"${packageJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) + if (content.includes(packageMarker)) { + content = content.replace(packageMarker, JSON.stringify(JSON.parse(ctx.packageJsonFn())).slice(1, -1) /* trim braces */); + markerFound = true; + } + + // const productMarker = `${productJsonMarkerId}:"${productJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) + // if (content.includes(productMarker)) { + // content = content.replace(productMarker, JSON.stringify(JSON.parse(ctx.productJsonFn())).slice(1, -1) /* trim braces */); + // markerFound = true; + // } + + if (markerFound) { + file.contents = Buffer.from(content); + } + } + + this.emit('data', file); + })); +} + +function matchesFile(file: File, ctx: IInlineMetaContext): boolean { + for (const targetPath of ctx.targetPaths) { + if (file.basename === basename(targetPath)) { // TODO would be nicer to figure out root relative path to not match on false positives + return true; + } + } + return false; +} diff --git a/patched-vscode/build/lib/layersChecker.js b/patched-vscode/build/lib/layersChecker.js index dce2b85d..7c7fd3bc 100644 --- a/patched-vscode/build/lib/layersChecker.js +++ b/patched-vscode/build/lib/layersChecker.js @@ -68,7 +68,10 @@ const CORE_TYPES = [ 'fetch', 'RequestInit', 'Headers', - 'Response' + 'Response', + '__global', + 'PerformanceMark', + 'PerformanceObserver', ]; // Types that are defined in a common layer but are known to be only // available in native environments should not be allowed in browser @@ -170,59 +173,17 @@ const RULES = [ '@types/node' // no node.js ] }, - // Common: vs/workbench/api/common/extHostTypes.ts + // Common: vs/base/parts/sandbox/electron-sandbox/preload.js { - target: '**/vs/workbench/api/common/extHostTypes.ts', + target: '**/vs/base/parts/sandbox/electron-sandbox/preload.js', allowedTypes: [ ...CORE_TYPES, - // Safe access to global - '__global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - // Common: vs/workbench/api/common/extHostChatAgents2.ts - { - target: '**/vs/workbench/api/common/extHostChatAgents2.ts', - allowedTypes: [ - ...CORE_TYPES, - // Safe access to global - '__global' + // Safe access to a very small subset of node.js + 'process', + 'NodeJS' ], disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - // Common: vs/workbench/api/common/extHostChatVariables.ts - { - target: '**/vs/workbench/api/common/extHostChatVariables.ts', - allowedTypes: [ - ...CORE_TYPES, - // Safe access to global - '__global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - // Common: vs/workbench/api/common/extensionHostMain.ts - { - target: '**/vs/workbench/api/common/extensionHostMain.ts', - allowedTypes: [ - ...CORE_TYPES, - // Safe access to global - '__global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM '@types/node' // no node.js ] }, diff --git a/patched-vscode/build/lib/layersChecker.ts b/patched-vscode/build/lib/layersChecker.ts index 039f2221..60939fe2 100644 --- a/patched-vscode/build/lib/layersChecker.ts +++ b/patched-vscode/build/lib/layersChecker.ts @@ -69,7 +69,10 @@ const CORE_TYPES = [ 'fetch', 'RequestInit', 'Headers', - 'Response' + 'Response', + '__global', + 'PerformanceMark', + 'PerformanceObserver', ]; // Types that are defined in a common layer but are known to be only @@ -185,66 +188,18 @@ const RULES: IRule[] = [ ] }, - // Common: vs/workbench/api/common/extHostTypes.ts + // Common: vs/base/parts/sandbox/electron-sandbox/preload.js { - target: '**/vs/workbench/api/common/extHostTypes.ts', + target: '**/vs/base/parts/sandbox/electron-sandbox/preload.js', allowedTypes: [ ...CORE_TYPES, - // Safe access to global - '__global' + // Safe access to a very small subset of node.js + 'process', + 'NodeJS' ], disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/workbench/api/common/extHostChatAgents2.ts - { - target: '**/vs/workbench/api/common/extHostChatAgents2.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to global - '__global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/workbench/api/common/extHostChatVariables.ts - { - target: '**/vs/workbench/api/common/extHostChatVariables.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to global - '__global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/workbench/api/common/extensionHostMain.ts - { - target: '**/vs/workbench/api/common/extensionHostMain.ts', - allowedTypes: [ - ...CORE_TYPES, - - // Safe access to global - '__global' - ], - disallowedTypes: NATIVE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM '@types/node' // no node.js ] }, diff --git a/patched-vscode/build/lib/mangle/index.js b/patched-vscode/build/lib/mangle/index.js index bb6b414e..f72d2999 100644 --- a/patched-vscode/build/lib/mangle/index.js +++ b/patched-vscode/build/lib/mangle/index.js @@ -14,7 +14,8 @@ const ts = require("typescript"); const url_1 = require("url"); const workerpool = require("workerpool"); const staticLanguageServiceHost_1 = require("./staticLanguageServiceHost"); -const buildfile = require('../../../src/buildfile'); +const esm_1 = require("../esm"); +const buildfile = require('../../buildfile'); class ShortIdent { prefix; static _keywords = new Set(['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', @@ -247,36 +248,51 @@ function isNameTakenInFile(node, name) { } return false; } -const skippedExportMangledFiles = [ - // Build - 'css.build', - 'nls.build', - // Monaco - 'editorCommon', - 'editorOptions', - 'editorZoom', - 'standaloneEditor', - 'standaloneEnums', - 'standaloneLanguages', - // Generated - 'extensionsApiProposals', - // Module passed around as type - 'pfs', - // entry points - ...[ - buildfile.entrypoint('vs/server/node/server.main', []), - buildfile.entrypoint('vs/workbench/workbench.desktop.main', []), - buildfile.base, - buildfile.workerExtensionHost, - buildfile.workerNotebook, - buildfile.workerLanguageDetection, - buildfile.workerLocalFileSearch, - buildfile.workerProfileAnalysis, - buildfile.workbenchDesktop, - buildfile.workbenchWeb, - buildfile.code - ].flat().map(x => x.name), -]; +const skippedExportMangledFiles = function () { + return [ + // Build + 'css.build', + // Monaco + 'editorCommon', + 'editorOptions', + 'editorZoom', + 'standaloneEditor', + 'standaloneEnums', + 'standaloneLanguages', + // Generated + 'extensionsApiProposals', + // Module passed around as type + 'pfs', + // entry points + ...(0, esm_1.isESM)() ? [ + buildfile.entrypoint('vs/server/node/server.main'), + buildfile.base, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.workbenchDesktop(), + buildfile.workbenchWeb(), + buildfile.code, + buildfile.codeWeb + ].flat().map(x => x.name) : [ + buildfile.entrypoint('vs/server/node/server.main'), + buildfile.entrypoint('vs/workbench/workbench.desktop.main'), + buildfile.base, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, + buildfile.workbenchDesktop(), + buildfile.workbenchWeb(), + buildfile.code + ].flat().map(x => x.name), + ]; +}; const skippedExportMangledProjects = [ // Test projects 'vscode-api-tests', @@ -520,7 +536,7 @@ class Mangler { for (const data of this.allExportedSymbols.values()) { if (data.fileName.endsWith('.d.ts') || skippedExportMangledProjects.some(proj => data.fileName.includes(proj)) - || skippedExportMangledFiles.some(file => data.fileName.endsWith(file + '.ts'))) { + || skippedExportMangledFiles().some(file => data.fileName.endsWith(file + '.ts'))) { continue; } if (!data.shouldMangle(data.replacementName)) { diff --git a/patched-vscode/build/lib/mangle/index.ts b/patched-vscode/build/lib/mangle/index.ts index 4a7544f1..7b6c6d20 100644 --- a/patched-vscode/build/lib/mangle/index.ts +++ b/patched-vscode/build/lib/mangle/index.ts @@ -12,7 +12,8 @@ import * as ts from 'typescript'; import { pathToFileURL } from 'url'; import * as workerpool from 'workerpool'; import { StaticLanguageServiceHost } from './staticLanguageServiceHost'; -const buildfile = require('../../../src/buildfile'); +import { isESM } from '../esm'; +const buildfile = require('../../buildfile'); class ShortIdent { @@ -279,41 +280,55 @@ function isNameTakenInFile(node: ts.Node, name: string): boolean { return false; } - -const skippedExportMangledFiles = [ - // Build - 'css.build', - 'nls.build', - - // Monaco - 'editorCommon', - 'editorOptions', - 'editorZoom', - 'standaloneEditor', - 'standaloneEnums', - 'standaloneLanguages', - - // Generated - 'extensionsApiProposals', - - // Module passed around as type - 'pfs', - - // entry points - ...[ - buildfile.entrypoint('vs/server/node/server.main', []), - buildfile.entrypoint('vs/workbench/workbench.desktop.main', []), - buildfile.base, - buildfile.workerExtensionHost, - buildfile.workerNotebook, - buildfile.workerLanguageDetection, - buildfile.workerLocalFileSearch, - buildfile.workerProfileAnalysis, - buildfile.workbenchDesktop, - buildfile.workbenchWeb, - buildfile.code - ].flat().map(x => x.name), -]; +const skippedExportMangledFiles = function () { // using a function() to ensure late isESM() check + return [ + // Build + 'css.build', + + // Monaco + 'editorCommon', + 'editorOptions', + 'editorZoom', + 'standaloneEditor', + 'standaloneEnums', + 'standaloneLanguages', + + // Generated + 'extensionsApiProposals', + + // Module passed around as type + 'pfs', + + // entry points + ...isESM() ? [ + buildfile.entrypoint('vs/server/node/server.main'), + buildfile.base, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.workbenchDesktop(), + buildfile.workbenchWeb(), + buildfile.code, + buildfile.codeWeb + ].flat().map(x => x.name) : [ + buildfile.entrypoint('vs/server/node/server.main'), + buildfile.entrypoint('vs/workbench/workbench.desktop.main'), + buildfile.base, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, + buildfile.workbenchDesktop(), + buildfile.workbenchWeb(), + buildfile.code + ].flat().map(x => x.name), + ]; +}; const skippedExportMangledProjects = [ // Test projects @@ -610,7 +625,7 @@ export class Mangler { for (const data of this.allExportedSymbols.values()) { if (data.fileName.endsWith('.d.ts') || skippedExportMangledProjects.some(proj => data.fileName.includes(proj)) - || skippedExportMangledFiles.some(file => data.fileName.endsWith(file + '.ts')) + || skippedExportMangledFiles().some(file => data.fileName.endsWith(file + '.ts')) ) { continue; } diff --git a/patched-vscode/build/lib/nls.js b/patched-vscode/build/lib/nls.js index 48ca84f2..3fc4ba3e 100644 --- a/patched-vscode/build/lib/nls.js +++ b/patched-vscode/build/lib/nls.js @@ -10,6 +10,8 @@ const event_stream_1 = require("event-stream"); const File = require("vinyl"); const sm = require("source-map"); const path = require("path"); +const sort = require("gulp-sort"); +const esm_1 = require("./esm"); var CollectStepResult; (function (CollectStepResult) { CollectStepResult[CollectStepResult["Yes"] = 0] = "Yes"; @@ -38,23 +40,15 @@ function clone(object) { } return result; } -function template(lines) { - let indent = '', wrap = ''; - if (lines.length > 1) { - indent = '\t'; - wrap = '\n'; - } - return `/*--------------------------------------------------------- - * Copyright (C) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------*/ -define([], [${wrap + lines.map(l => indent + l).join(',\n') + wrap}]);`; -} /** * Returns a stream containing the patched JavaScript and source maps. */ -function nls() { +function nls(options) { + let base; const input = (0, event_stream_1.through)(); - const output = input.pipe((0, event_stream_1.through)(function (f) { + const output = input + .pipe(sort()) // IMPORTANT: to ensure stable NLS metadata generation, we must sort the files because NLS messages are globally extracted and indexed across all files + .pipe((0, event_stream_1.through)(function (f) { if (!f.sourceMap) { return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`)); } @@ -70,7 +64,40 @@ function nls() { if (!typescript) { return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`)); } - _nls.patchFiles(f, typescript).forEach(f => this.emit('data', f)); + base = f.base; + this.emit('data', _nls.patchFile(f, typescript, options)); + }, function () { + for (const file of [ + new File({ + contents: Buffer.from(JSON.stringify({ + keys: _nls.moduleToNLSKeys, + messages: _nls.moduleToNLSMessages, + }, null, '\t')), + base, + path: `${base}/nls.metadata.json` + }), + new File({ + contents: Buffer.from(JSON.stringify(_nls.allNLSMessages)), + base, + path: `${base}/nls.messages.json` + }), + new File({ + contents: Buffer.from(JSON.stringify(_nls.allNLSModulesAndKeys)), + base, + path: `${base}/nls.keys.json` + }), + new File({ + contents: Buffer.from(`/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ +globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`), + base, + path: `${base}/nls.messages.js` + }) + ]) { + this.emit('data', file); + } + this.emit('end'); })); return (0, event_stream_1.duplex)(input, output); } @@ -79,6 +106,11 @@ function isImportNode(ts, node) { } var _nls; (function (_nls) { + _nls.moduleToNLSKeys = {}; + _nls.moduleToNLSMessages = {}; + _nls.allNLSMessages = []; + _nls.allNLSModulesAndKeys = []; + let allNLSMessagesIndex = 0; function fileFrom(file, contents, path = file.path) { return new File({ contents: Buffer.from(contents), @@ -138,21 +170,24 @@ var _nls; .filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration) .map(n => n) .filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) - .filter(d => d.moduleReference.expression.getText() === '\'vs/nls\''); + .filter(d => { + if ((0, esm_1.isESM)()) { + return d.moduleReference.expression.getText().endsWith(`/nls.js'`); + } + return d.moduleReference.expression.getText() === '\'vs/nls\''; + }); // import ... from 'vs/nls'; const importDeclarations = imports .filter(n => n.kind === ts.SyntaxKind.ImportDeclaration) .map(n => n) .filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) - .filter(d => d.moduleSpecifier.getText() === '\'vs/nls\'') + .filter(d => { + if ((0, esm_1.isESM)()) { + return d.moduleSpecifier.getText().endsWith(`/nls.js'`); + } + return d.moduleSpecifier.getText() === '\'vs/nls\''; + }) .filter(d => !!d.importClause && !!d.importClause.namedBindings); - const nlsExpressions = importEqualsDeclarations - .map(d => d.moduleReference.expression) - .concat(importDeclarations.map(d => d.moduleSpecifier)) - .map(d => ({ - start: ts.getLineAndCharacterOfPosition(sourceFile, d.getStart()), - end: ts.getLineAndCharacterOfPosition(sourceFile, d.getEnd()) - })); // `nls.localize(...)` calls const nlsLocalizeCallExpressions = importDeclarations .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport)) @@ -206,8 +241,7 @@ var _nls; value: a[1].getText() })); return { - localizeCalls: localizeCalls.toArray(), - nlsExpressions: nlsExpressions.toArray() + localizeCalls: localizeCalls.toArray() }; } class TextModel { @@ -262,14 +296,10 @@ var _nls; .flatten().toArray().join(''); } } - function patchJavascript(patches, contents, moduleId) { + function patchJavascript(patches, contents) { const model = new TextModel(contents); // patch the localize calls lazy(patches).reverse().each(p => model.apply(p)); - // patch the 'vs/nls' imports - const firstLine = model.get(0); - const patchedFirstLine = firstLine.replace(/(['"])vs\/nls\1/g, `$1vs/nls!${moduleId}$1`); - model.set(0, patchedFirstLine); return model.toString(); } function patchSourcemap(patches, rsm, smc) { @@ -307,14 +337,21 @@ var _nls; } return JSON.parse(smg.toString()); } - function patch(ts, moduleId, typescript, javascript, sourcemap) { - const { localizeCalls, nlsExpressions } = analyze(ts, typescript, 'localize'); - const { localizeCalls: localize2Calls, nlsExpressions: nls2Expressions } = analyze(ts, typescript, 'localize2'); + function parseLocalizeKeyOrValue(sourceExpression) { + // sourceValue can be "foo", 'foo', `foo` or { .... } + // in its evalulated form + // we want to return either the string or the object + // eslint-disable-next-line no-eval + return eval(`(${sourceExpression})`); + } + function patch(ts, typescript, javascript, sourcemap, options) { + const { localizeCalls } = analyze(ts, typescript, 'localize'); + const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2'); if (localizeCalls.length === 0 && localize2Calls.length === 0) { return { javascript, sourcemap }; } - const nlsKeys = template(localizeCalls.map(lc => lc.key).concat(localize2Calls.map(lc => lc.key))); - const nls = template(localizeCalls.map(lc => lc.value).concat(localize2Calls.map(lc => lc.value))); + const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key))); + const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value))); const smc = new sm.SourceMapConsumer(sourcemap); const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]); // build patches @@ -323,16 +360,18 @@ var _nls; const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); return { span: { start, end }, content: c.content }; }; - let i = 0; const localizePatches = lazy(localizeCalls) - .map(lc => ([ - { range: lc.keySpan, content: '' + (i++) }, + .map(lc => (options.preserveEnglish ? [ + { range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize('key', "message") => localize(, "message") + ] : [ + { range: lc.keySpan, content: `${allNLSMessagesIndex++}` }, // localize('key', "message") => localize(, null) { range: lc.valueSpan, content: 'null' } ])) .flatten() .map(toPatch); const localize2Patches = lazy(localize2Calls) - .map(lc => ({ range: lc.keySpan, content: '' + (i++) })) + .map(lc => ({ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize2('key', "message") => localize(, "message") + )) .map(toPatch); // Sort patches by their start position const patches = localizePatches.concat(localize2Patches).toArray().sort((a, b) => { @@ -352,34 +391,29 @@ var _nls; return 0; } }); - javascript = patchJavascript(patches, javascript, moduleId); - // since imports are not within the sourcemap information, - // we must do this MacGyver style - if (nlsExpressions.length || nls2Expressions.length) { - javascript = javascript.replace(/^define\(.*$/m, line => { - return line.replace(/(['"])vs\/nls\1/g, `$1vs/nls!${moduleId}$1`); - }); - } + javascript = patchJavascript(patches, javascript); sourcemap = patchSourcemap(patches, sourcemap, smc); - return { javascript, sourcemap, nlsKeys, nls }; + return { javascript, sourcemap, nlsKeys, nlsMessages }; } - function patchFiles(javascriptFile, typescript) { + function patchFile(javascriptFile, typescript, options) { const ts = require('typescript'); // hack? const moduleId = javascriptFile.relative .replace(/\.js$/, '') .replace(/\\/g, '/'); - const { javascript, sourcemap, nlsKeys, nls } = patch(ts, moduleId, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap); - const result = [fileFrom(javascriptFile, javascript)]; - result[0].sourceMap = sourcemap; + const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap, options); + const result = fileFrom(javascriptFile, javascript); + result.sourceMap = sourcemap; if (nlsKeys) { - result.push(fileFrom(javascriptFile, nlsKeys, javascriptFile.path.replace(/\.js$/, '.nls.keys.js'))); + _nls.moduleToNLSKeys[moduleId] = nlsKeys; + _nls.allNLSModulesAndKeys.push([moduleId, nlsKeys.map(nlsKey => typeof nlsKey === 'string' ? nlsKey : nlsKey.key)]); } - if (nls) { - result.push(fileFrom(javascriptFile, nls, javascriptFile.path.replace(/\.js$/, '.nls.js'))); + if (nlsMessages) { + _nls.moduleToNLSMessages[moduleId] = nlsMessages; + _nls.allNLSMessages.push(...nlsMessages); } return result; } - _nls.patchFiles = patchFiles; + _nls.patchFile = patchFile; })(_nls || (_nls = {})); //# sourceMappingURL=nls.js.map \ No newline at end of file diff --git a/patched-vscode/build/lib/nls.ts b/patched-vscode/build/lib/nls.ts index c4ee031b..0a60f74a 100644 --- a/patched-vscode/build/lib/nls.ts +++ b/patched-vscode/build/lib/nls.ts @@ -8,7 +8,9 @@ import * as lazy from 'lazy.js'; import { duplex, through } from 'event-stream'; import * as File from 'vinyl'; import * as sm from 'source-map'; -import * as path from 'path'; +import * as path from 'path'; +import * as sort from 'gulp-sort'; +import { isESM } from './esm'; declare class FileSourceMap extends File { public sourceMap: sm.RawSourceMap; @@ -48,47 +50,70 @@ function clone(object: T): T { return result; } -function template(lines: string[]): string { - let indent = '', wrap = ''; - - if (lines.length > 1) { - indent = '\t'; - wrap = '\n'; - } - - return `/*--------------------------------------------------------- - * Copyright (C) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------*/ -define([], [${wrap + lines.map(l => indent + l).join(',\n') + wrap}]);`; -} - /** * Returns a stream containing the patched JavaScript and source maps. */ -export function nls(): NodeJS.ReadWriteStream { +export function nls(options: { preserveEnglish: boolean }): NodeJS.ReadWriteStream { + let base: string; const input = through(); - const output = input.pipe(through(function (f: FileSourceMap) { - if (!f.sourceMap) { - return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`)); - } + const output = input + .pipe(sort()) // IMPORTANT: to ensure stable NLS metadata generation, we must sort the files because NLS messages are globally extracted and indexed across all files + .pipe(through(function (f: FileSourceMap) { + if (!f.sourceMap) { + return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`)); + } - let source = f.sourceMap.sources[0]; - if (!source) { - return this.emit('error', new Error(`File ${f.relative} does not have a source in the source map.`)); - } + let source = f.sourceMap.sources[0]; + if (!source) { + return this.emit('error', new Error(`File ${f.relative} does not have a source in the source map.`)); + } - const root = f.sourceMap.sourceRoot; - if (root) { - source = path.join(root, source); - } + const root = f.sourceMap.sourceRoot; + if (root) { + source = path.join(root, source); + } - const typescript = f.sourceMap.sourcesContent![0]; - if (!typescript) { - return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`)); - } + const typescript = f.sourceMap.sourcesContent![0]; + if (!typescript) { + return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`)); + } - _nls.patchFiles(f, typescript).forEach(f => this.emit('data', f)); - })); + base = f.base; + this.emit('data', _nls.patchFile(f, typescript, options)); + }, function () { + for (const file of [ + new File({ + contents: Buffer.from(JSON.stringify({ + keys: _nls.moduleToNLSKeys, + messages: _nls.moduleToNLSMessages, + }, null, '\t')), + base, + path: `${base}/nls.metadata.json` + }), + new File({ + contents: Buffer.from(JSON.stringify(_nls.allNLSMessages)), + base, + path: `${base}/nls.messages.json` + }), + new File({ + contents: Buffer.from(JSON.stringify(_nls.allNLSModulesAndKeys)), + base, + path: `${base}/nls.keys.json` + }), + new File({ + contents: Buffer.from(`/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ +globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`), + base, + path: `${base}/nls.messages.js` + }) + ]) { + this.emit('data', file); + } + + this.emit('end'); + })); return duplex(input, output); } @@ -99,11 +124,19 @@ function isImportNode(ts: typeof import('typescript'), node: ts.Node): boolean { module _nls { - interface INlsStringResult { + export const moduleToNLSKeys: { [name: string /* module ID */]: ILocalizeKey[] /* keys */ } = {}; + export const moduleToNLSMessages: { [name: string /* module ID */]: string[] /* messages */ } = {}; + export const allNLSMessages: string[] = []; + export const allNLSModulesAndKeys: Array<[string /* module ID */, string[] /* keys */]> = []; + let allNLSMessagesIndex = 0; + + type ILocalizeKey = string | { key: string }; // key might contain metadata for translators and then is not just a string + + interface INlsPatchResult { javascript: string; sourcemap: sm.RawSourceMap; - nls?: string; - nlsKeys?: string; + nlsMessages?: string[]; + nlsKeys?: ILocalizeKey[]; } interface ISpan { @@ -120,7 +153,6 @@ module _nls { interface ILocalizeAnalysisResult { localizeCalls: ILocalizeCall[]; - nlsExpressions: ISpan[]; } interface IPatch { @@ -200,24 +232,26 @@ module _nls { .filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration) .map(n => n) .filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) - .filter(d => (d.moduleReference).expression.getText() === '\'vs/nls\''); + .filter(d => { + if (isESM()) { + return (d.moduleReference).expression.getText().endsWith(`/nls.js'`); + } + return (d.moduleReference).expression.getText() === '\'vs/nls\''; + }); // import ... from 'vs/nls'; const importDeclarations = imports .filter(n => n.kind === ts.SyntaxKind.ImportDeclaration) .map(n => n) .filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) - .filter(d => d.moduleSpecifier.getText() === '\'vs/nls\'') + .filter(d => { + if (isESM()) { + return d.moduleSpecifier.getText().endsWith(`/nls.js'`); + } + return d.moduleSpecifier.getText() === '\'vs/nls\''; + }) .filter(d => !!d.importClause && !!d.importClause.namedBindings); - const nlsExpressions = importEqualsDeclarations - .map(d => (d.moduleReference).expression) - .concat(importDeclarations.map(d => d.moduleSpecifier)) - .map(d => ({ - start: ts.getLineAndCharacterOfPosition(sourceFile, d.getStart()), - end: ts.getLineAndCharacterOfPosition(sourceFile, d.getEnd()) - })); - // `nls.localize(...)` calls const nlsLocalizeCallExpressions = importDeclarations .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport)) @@ -280,8 +314,7 @@ module _nls { })); return { - localizeCalls: localizeCalls.toArray(), - nlsExpressions: nlsExpressions.toArray() + localizeCalls: localizeCalls.toArray() }; } @@ -351,17 +384,12 @@ module _nls { } } - function patchJavascript(patches: IPatch[], contents: string, moduleId: string): string { + function patchJavascript(patches: IPatch[], contents: string): string { const model = new TextModel(contents); // patch the localize calls lazy(patches).reverse().each(p => model.apply(p)); - // patch the 'vs/nls' imports - const firstLine = model.get(0); - const patchedFirstLine = firstLine.replace(/(['"])vs\/nls\1/g, `$1vs/nls!${moduleId}$1`); - model.set(0, patchedFirstLine); - return model.toString(); } @@ -410,16 +438,24 @@ module _nls { return JSON.parse(smg.toString()); } - function patch(ts: typeof import('typescript'), moduleId: string, typescript: string, javascript: string, sourcemap: sm.RawSourceMap): INlsStringResult { - const { localizeCalls, nlsExpressions } = analyze(ts, typescript, 'localize'); - const { localizeCalls: localize2Calls, nlsExpressions: nls2Expressions } = analyze(ts, typescript, 'localize2'); + function parseLocalizeKeyOrValue(sourceExpression: string) { + // sourceValue can be "foo", 'foo', `foo` or { .... } + // in its evalulated form + // we want to return either the string or the object + // eslint-disable-next-line no-eval + return eval(`(${sourceExpression})`); + } + + function patch(ts: typeof import('typescript'), typescript: string, javascript: string, sourcemap: sm.RawSourceMap, options: { preserveEnglish: boolean }): INlsPatchResult { + const { localizeCalls } = analyze(ts, typescript, 'localize'); + const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2'); if (localizeCalls.length === 0 && localize2Calls.length === 0) { return { javascript, sourcemap }; } - const nlsKeys = template(localizeCalls.map(lc => lc.key).concat(localize2Calls.map(lc => lc.key))); - const nls = template(localizeCalls.map(lc => lc.value).concat(localize2Calls.map(lc => lc.value))); + const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key))); + const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value))); const smc = new sm.SourceMapConsumer(sourcemap); const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]); @@ -430,18 +466,20 @@ module _nls { return { span: { start, end }, content: c.content }; }; - let i = 0; const localizePatches = lazy(localizeCalls) - .map(lc => ([ - { range: lc.keySpan, content: '' + (i++) }, - { range: lc.valueSpan, content: 'null' } - ])) + .map(lc => ( + options.preserveEnglish ? [ + { range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize('key', "message") => localize(, "message") + ] : [ + { range: lc.keySpan, content: `${allNLSMessagesIndex++}` }, // localize('key', "message") => localize(, null) + { range: lc.valueSpan, content: 'null' } + ])) .flatten() .map(toPatch); const localize2Patches = lazy(localize2Calls) .map(lc => ( - { range: lc.keySpan, content: '' + (i++) } + { range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize2('key', "message") => localize(, "message") )) .map(toPatch); @@ -460,45 +498,39 @@ module _nls { } }); - javascript = patchJavascript(patches, javascript, moduleId); - - // since imports are not within the sourcemap information, - // we must do this MacGyver style - if (nlsExpressions.length || nls2Expressions.length) { - javascript = javascript.replace(/^define\(.*$/m, line => { - return line.replace(/(['"])vs\/nls\1/g, `$1vs/nls!${moduleId}$1`); - }); - } + javascript = patchJavascript(patches, javascript); sourcemap = patchSourcemap(patches, sourcemap, smc); - return { javascript, sourcemap, nlsKeys, nls }; + return { javascript, sourcemap, nlsKeys, nlsMessages }; } - export function patchFiles(javascriptFile: File, typescript: string): File[] { + export function patchFile(javascriptFile: File, typescript: string, options: { preserveEnglish: boolean }): File { const ts = require('typescript') as typeof import('typescript'); // hack? const moduleId = javascriptFile.relative .replace(/\.js$/, '') .replace(/\\/g, '/'); - const { javascript, sourcemap, nlsKeys, nls } = patch( + const { javascript, sourcemap, nlsKeys, nlsMessages } = patch( ts, - moduleId, typescript, javascriptFile.contents.toString(), - (javascriptFile).sourceMap + (javascriptFile).sourceMap, + options ); - const result: File[] = [fileFrom(javascriptFile, javascript)]; - (result[0]).sourceMap = sourcemap; + const result = fileFrom(javascriptFile, javascript); + (result).sourceMap = sourcemap; if (nlsKeys) { - result.push(fileFrom(javascriptFile, nlsKeys, javascriptFile.path.replace(/\.js$/, '.nls.keys.js'))); + moduleToNLSKeys[moduleId] = nlsKeys; + allNLSModulesAndKeys.push([moduleId, nlsKeys.map(nlsKey => typeof nlsKey === 'string' ? nlsKey : nlsKey.key)]); } - if (nls) { - result.push(fileFrom(javascriptFile, nls, javascriptFile.path.replace(/\.js$/, '.nls.js'))); + if (nlsMessages) { + moduleToNLSMessages[moduleId] = nlsMessages; + allNLSMessages.push(...nlsMessages); } return result; diff --git a/patched-vscode/build/lib/optimize.js b/patched-vscode/build/lib/optimize.js index d48235eb..fdf73857 100644 --- a/patched-vscode/build/lib/optimize.js +++ b/patched-vscode/build/lib/optimize.js @@ -15,6 +15,7 @@ const filter = require("gulp-filter"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); const path = require("path"); +const fs = require("fs"); const pump = require("pump"); const VinylFile = require("vinyl"); const bundle = require("./bundle"); @@ -22,6 +23,9 @@ const i18n_1 = require("./i18n"); const stats_1 = require("./stats"); const util = require("./util"); const postcss_1 = require("./postcss"); +const esbuild = require("esbuild"); +const sourcemaps = require("gulp-sourcemaps"); +const esm_1 = require("./esm"); const REPO_ROOT_PATH = path.join(__dirname, '../..'); function log(prefix, message) { fancyLog(ansiColors.cyan('[' + prefix + ']'), message); @@ -53,7 +57,7 @@ function loaderPlugin(src, base, amdModuleId) { function loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo) { let loaderStream = gulp.src(`${src}/vs/loader.js`, { base: `${src}` }); if (bundleLoader) { - loaderStream = es.merge(loaderStream, loaderPlugin(`${src}/vs/css.js`, `${src}`, 'vs/css'), loaderPlugin(`${src}/vs/nls.js`, `${src}`, 'vs/nls')); + loaderStream = es.merge(loaderStream, loaderPlugin(`${src}/vs/css.js`, `${src}`, 'vs/css')); } const files = []; const order = (f) => { @@ -63,10 +67,7 @@ function loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo) { if (f.path.endsWith('css.js')) { return 1; } - if (f.path.endsWith('nls.js')) { - return 2; - } - return 3; + return 2; }; return (loaderStream .pipe(es.through(function (data) { @@ -151,12 +152,11 @@ const DEFAULT_FILE_HEADER = [ ].join('\n'); function optimizeAMDTask(opts) { const src = opts.src; - const entryPoints = opts.entryPoints; + const entryPoints = opts.entryPoints.filter(d => d.target !== 'esm'); const resources = opts.resources; const loaderConfig = opts.loaderConfig; const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER; const fileContentMapper = opts.fileContentMapper || ((contents, _path) => contents); - const sourcemaps = require('gulp-sourcemaps'); const bundlesStream = es.through(); // this stream will contain the bundled files const resourcesStream = es.through(); // this stream will contain the resources const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json @@ -192,12 +192,127 @@ function optimizeAMDTask(opts) { includeContent: true })) .pipe(opts.languages && opts.languages.length ? (0, i18n_1.processNlsFiles)({ + out: opts.src, fileHeader: bundledFileHeader, languages: opts.languages }) : es.through()); } +function optimizeESMTask(opts, cjsOpts) { + const resourcesStream = es.through(); // this stream will contain the resources + const bundlesStream = es.through(); // this stream will contain the bundled files + const entryPoints = opts.entryPoints.filter(d => d.target !== 'amd'); + if (cjsOpts) { + cjsOpts.entryPoints.forEach(entryPoint => entryPoints.push({ name: path.parse(entryPoint).name })); + } + const allMentionedModules = new Set(); + for (const entryPoint of entryPoints) { + allMentionedModules.add(entryPoint.name); + entryPoint.include?.forEach(allMentionedModules.add, allMentionedModules); + entryPoint.exclude?.forEach(allMentionedModules.add, allMentionedModules); + } + allMentionedModules.delete('vs/css'); // TODO@esm remove this when vs/css is removed + const bundleAsync = async () => { + const files = []; + const tasks = []; + for (const entryPoint of entryPoints) { + console.log(`[bundle] '${entryPoint.name}'`); + // support for 'dest' via esbuild#in/out + const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name; + // boilerplate massage + const banner = { js: '' }; + const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js'); + banner.js += await fs.promises.readFile(tslibPath, 'utf-8'); + const boilerplateTrimmer = { + name: 'boilerplate-trimmer', + setup(build) { + build.onLoad({ filter: /\.js$/ }, async (args) => { + const contents = await fs.promises.readFile(args.path, 'utf-8'); + const newContents = bundle.removeAllTSBoilerplate(contents); + return { contents: newContents }; + }); + } + }; + // support for 'preprend' via the esbuild#banner + if (entryPoint.prepend?.length) { + for (const item of entryPoint.prepend) { + const fullpath = path.join(REPO_ROOT_PATH, opts.src, item.path); + const source = await fs.promises.readFile(fullpath, 'utf8'); + banner.js += source + '\n'; + } + } + const task = esbuild.build({ + bundle: true, + external: entryPoint.exclude, + packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages + platform: 'neutral', // makes esm + format: 'esm', + plugins: [boilerplateTrimmer], + target: ['es2022'], + loader: { + '.ttf': 'file', + '.svg': 'file', + '.png': 'file', + '.sh': 'file', + }, + assetNames: 'media/[name]', // moves media assets into a sub-folder "media" + banner, + entryPoints: [ + { + in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`), + out: dest, + } + ], + outdir: path.join(REPO_ROOT_PATH, opts.src), + write: false, // enables res.outputFiles + metafile: true, // enables res.metafile + }).then(res => { + for (const file of res.outputFiles) { + let contents = file.contents; + if (file.path.endsWith('.js')) { + if (opts.fileContentMapper) { + // UGLY the fileContentMapper is per file but at this point we have all files + // bundled already. So, we call the mapper for the same contents but each file + // that has been included in the bundle... + let newText = file.text; + for (const input of Object.keys(res.metafile.inputs)) { + newText = opts.fileContentMapper(newText, input); + } + contents = Buffer.from(newText); + } + } + files.push(new VinylFile({ + contents: Buffer.from(contents), + path: file.path, + base: path.join(REPO_ROOT_PATH, opts.src) + })); + } + }); + // await task; // FORCE serial bundling (makes debugging easier) + tasks.push(task); + } + await Promise.all(tasks); + return { files }; + }; + bundleAsync().then((output) => { + // bundle output (JS, CSS, SVG...) + es.readArray(output.files).pipe(bundlesStream); + // forward all resources + gulp.src(opts.resources, { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream); + }); + const result = es.merge(bundlesStream, resourcesStream); + return result + .pipe(sourcemaps.write('./', { + sourceRoot: undefined, + addComment: true, + includeContent: true + })) + .pipe(opts.languages && opts.languages.length ? (0, i18n_1.processNlsFiles)({ + out: opts.src, + fileHeader: opts.header || DEFAULT_FILE_HEADER, + languages: opts.languages + }) : es.through()); +} function optimizeCommonJSTask(opts) { - const esbuild = require('esbuild'); const src = opts.src; const entryPoints = opts.entryPoints; return gulp.src(entryPoints, { base: `${src}`, allowEmpty: true }) @@ -228,9 +343,15 @@ function optimizeLoaderTask(src, out, bundleLoader, bundledFileHeader = '', exte } function optimizeTask(opts) { return function () { - const optimizers = [optimizeAMDTask(opts.amd)]; - if (opts.commonJS) { - optimizers.push(optimizeCommonJSTask(opts.commonJS)); + const optimizers = []; + if ((0, esm_1.isESM)('Running optimizer in ESM mode')) { + optimizers.push(optimizeESMTask(opts.amd, opts.commonJS)); + } + else { + optimizers.push(optimizeAMDTask(opts.amd)); + if (opts.commonJS) { + optimizers.push(optimizeCommonJSTask(opts.commonJS)); + } } if (opts.manual) { optimizers.push(optimizeManualTask(opts.manual)); @@ -239,11 +360,9 @@ function optimizeTask(opts) { }; } function minifyTask(src, sourceMapBaseUrl) { - const esbuild = require('esbuild'); const sourceMappingURL = sourceMapBaseUrl ? ((f) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; return cb => { const cssnano = require('cssnano'); - const sourcemaps = require('gulp-sourcemaps'); const svgmin = require('gulp-svgmin'); const jsFilter = filter('**/*.js', { restore: true }); const cssFilter = filter('**/*.css', { restore: true }); @@ -255,7 +374,7 @@ function minifyTask(src, sourceMapBaseUrl) { sourcemap: 'external', outdir: '.', platform: 'node', - target: ['esnext'], + target: ['es2022'], write: false }).then(res => { const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path)); diff --git a/patched-vscode/build/lib/optimize.ts b/patched-vscode/build/lib/optimize.ts index 5b6dee9b..c7503213 100644 --- a/patched-vscode/build/lib/optimize.ts +++ b/patched-vscode/build/lib/optimize.ts @@ -10,6 +10,7 @@ import * as filter from 'gulp-filter'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; import * as path from 'path'; +import * as fs from 'fs'; import * as pump from 'pump'; import * as VinylFile from 'vinyl'; import * as bundle from './bundle'; @@ -17,6 +18,9 @@ import { Language, processNlsFiles } from './i18n'; import { createStatsStream } from './stats'; import * as util from './util'; import { gulpPostcss } from './postcss'; +import * as esbuild from 'esbuild'; +import * as sourcemaps from 'gulp-sourcemaps'; +import { isESM } from './esm'; const REPO_ROOT_PATH = path.join(__dirname, '../..'); @@ -60,8 +64,7 @@ function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, e if (bundleLoader) { loaderStream = es.merge( loaderStream, - loaderPlugin(`${src}/vs/css.js`, `${src}`, 'vs/css'), - loaderPlugin(`${src}/vs/nls.js`, `${src}`, 'vs/nls'), + loaderPlugin(`${src}/vs/css.js`, `${src}`, 'vs/css') ); } @@ -73,10 +76,7 @@ function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, e if (f.path.endsWith('css.js')) { return 1; } - if (f.path.endsWith('nls.js')) { - return 2; - } - return 3; + return 2; }; return ( @@ -217,14 +217,12 @@ const DEFAULT_FILE_HEADER = [ function optimizeAMDTask(opts: IOptimizeAMDTaskOpts): NodeJS.ReadWriteStream { const src = opts.src; - const entryPoints = opts.entryPoints; + const entryPoints = opts.entryPoints.filter(d => d.target !== 'esm'); const resources = opts.resources; const loaderConfig = opts.loaderConfig; const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER; const fileContentMapper = opts.fileContentMapper || ((contents: string, _path: string) => contents); - const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); - const bundlesStream = es.through(); // this stream will contain the bundled files const resourcesStream = es.through(); // this stream will contain the resources const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json @@ -269,11 +267,155 @@ function optimizeAMDTask(opts: IOptimizeAMDTaskOpts): NodeJS.ReadWriteStream { includeContent: true })) .pipe(opts.languages && opts.languages.length ? processNlsFiles({ + out: opts.src, fileHeader: bundledFileHeader, languages: opts.languages }) : es.through()); } +function optimizeESMTask(opts: IOptimizeAMDTaskOpts, cjsOpts?: IOptimizeCommonJSTaskOpts): NodeJS.ReadWriteStream { + const resourcesStream = es.through(); // this stream will contain the resources + const bundlesStream = es.through(); // this stream will contain the bundled files + + const entryPoints = opts.entryPoints.filter(d => d.target !== 'amd'); + if (cjsOpts) { + cjsOpts.entryPoints.forEach(entryPoint => entryPoints.push({ name: path.parse(entryPoint).name })); + } + + const allMentionedModules = new Set(); + for (const entryPoint of entryPoints) { + allMentionedModules.add(entryPoint.name); + entryPoint.include?.forEach(allMentionedModules.add, allMentionedModules); + entryPoint.exclude?.forEach(allMentionedModules.add, allMentionedModules); + } + + allMentionedModules.delete('vs/css'); // TODO@esm remove this when vs/css is removed + + const bundleAsync = async () => { + + const files: VinylFile[] = []; + const tasks: Promise[] = []; + + for (const entryPoint of entryPoints) { + + console.log(`[bundle] '${entryPoint.name}'`); + + // support for 'dest' via esbuild#in/out + const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name; + + // boilerplate massage + const banner = { js: '' }; + const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js'); + banner.js += await fs.promises.readFile(tslibPath, 'utf-8'); + + const boilerplateTrimmer: esbuild.Plugin = { + name: 'boilerplate-trimmer', + setup(build) { + build.onLoad({ filter: /\.js$/ }, async args => { + const contents = await fs.promises.readFile(args.path, 'utf-8'); + const newContents = bundle.removeAllTSBoilerplate(contents); + return { contents: newContents }; + }); + } + }; + + // support for 'preprend' via the esbuild#banner + if (entryPoint.prepend?.length) { + for (const item of entryPoint.prepend) { + const fullpath = path.join(REPO_ROOT_PATH, opts.src, item.path); + const source = await fs.promises.readFile(fullpath, 'utf8'); + banner.js += source + '\n'; + } + } + + const task = esbuild.build({ + bundle: true, + external: entryPoint.exclude, + packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages + platform: 'neutral', // makes esm + format: 'esm', + plugins: [boilerplateTrimmer], + target: ['es2022'], + loader: { + '.ttf': 'file', + '.svg': 'file', + '.png': 'file', + '.sh': 'file', + }, + assetNames: 'media/[name]', // moves media assets into a sub-folder "media" + banner, + entryPoints: [ + { + in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`), + out: dest, + } + ], + outdir: path.join(REPO_ROOT_PATH, opts.src), + write: false, // enables res.outputFiles + metafile: true, // enables res.metafile + + }).then(res => { + for (const file of res.outputFiles) { + + let contents = file.contents; + + if (file.path.endsWith('.js')) { + + if (opts.fileContentMapper) { + // UGLY the fileContentMapper is per file but at this point we have all files + // bundled already. So, we call the mapper for the same contents but each file + // that has been included in the bundle... + let newText = file.text; + for (const input of Object.keys(res.metafile.inputs)) { + newText = opts.fileContentMapper(newText, input); + } + contents = Buffer.from(newText); + } + } + + files.push(new VinylFile({ + contents: Buffer.from(contents), + path: file.path, + base: path.join(REPO_ROOT_PATH, opts.src) + })); + } + }); + + // await task; // FORCE serial bundling (makes debugging easier) + tasks.push(task); + } + + await Promise.all(tasks); + return { files }; + }; + + bundleAsync().then((output) => { + + // bundle output (JS, CSS, SVG...) + es.readArray(output.files).pipe(bundlesStream); + + // forward all resources + gulp.src(opts.resources, { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream); + }); + + const result = es.merge( + bundlesStream, + resourcesStream + ); + + return result + .pipe(sourcemaps.write('./', { + sourceRoot: undefined, + addComment: true, + includeContent: true + })) + .pipe(opts.languages && opts.languages.length ? processNlsFiles({ + out: opts.src, + fileHeader: opts.header || DEFAULT_FILE_HEADER, + languages: opts.languages + }) : es.through()); +} + export interface IOptimizeCommonJSTaskOpts { /** * The paths to consider for optimizing. @@ -294,8 +436,6 @@ export interface IOptimizeCommonJSTaskOpts { } function optimizeCommonJSTask(opts: IOptimizeCommonJSTaskOpts): NodeJS.ReadWriteStream { - const esbuild = require('esbuild') as typeof import('esbuild'); - const src = opts.src; const entryPoints = opts.entryPoints; @@ -363,9 +503,15 @@ export interface IOptimizeTaskOpts { export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream { return function () { - const optimizers = [optimizeAMDTask(opts.amd)]; - if (opts.commonJS) { - optimizers.push(optimizeCommonJSTask(opts.commonJS)); + const optimizers: NodeJS.ReadWriteStream[] = []; + if (isESM('Running optimizer in ESM mode')) { + optimizers.push(optimizeESMTask(opts.amd, opts.commonJS)); + } else { + optimizers.push(optimizeAMDTask(opts.amd)); + + if (opts.commonJS) { + optimizers.push(optimizeCommonJSTask(opts.commonJS)); + } } if (opts.manual) { @@ -377,12 +523,10 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr } export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void { - const esbuild = require('esbuild') as typeof import('esbuild'); const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; return cb => { const cssnano = require('cssnano') as typeof import('cssnano'); - const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin'); const jsFilter = filter('**/*.js', { restore: true }); @@ -400,7 +544,7 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => sourcemap: 'external', outdir: '.', platform: 'node', - target: ['esnext'], + target: ['es2022'], write: false }).then(res => { const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path))!; diff --git a/patched-vscode/build/lib/standalone.js b/patched-vscode/build/lib/standalone.js index dbc47db0..78030842 100644 --- a/patched-vscode/build/lib/standalone.js +++ b/patched-vscode/build/lib/standalone.js @@ -106,10 +106,7 @@ function extractEditor(options) { 'vs/css.build.ts', 'vs/css.ts', 'vs/loader.js', - 'vs/loader.d.ts', - 'vs/nls.build.ts', - 'vs/nls.ts', - 'vs/nls.mock.ts', + 'vs/loader.d.ts' ].forEach(copyFile); } function createESMSourcesAndResources2(options) { @@ -134,7 +131,7 @@ function createESMSourcesAndResources2(options) { } if (file === 'tsconfig.json') { const tsConfig = JSON.parse(fs.readFileSync(path.join(SRC_FOLDER, file)).toString()); - tsConfig.compilerOptions.module = 'es6'; + tsConfig.compilerOptions.module = 'es2022'; tsConfig.compilerOptions.outDir = path.join(path.relative(OUT_FOLDER, OUT_RESOURCES_FOLDER), 'vs').replace(/\\/g, '/'); write(getDestAbsoluteFilePath(file), JSON.stringify(tsConfig, null, '\t')); continue; diff --git a/patched-vscode/build/lib/standalone.ts b/patched-vscode/build/lib/standalone.ts index 775a1be5..e1b9db65 100644 --- a/patched-vscode/build/lib/standalone.ts +++ b/patched-vscode/build/lib/standalone.ts @@ -118,10 +118,7 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str 'vs/css.build.ts', 'vs/css.ts', 'vs/loader.js', - 'vs/loader.d.ts', - 'vs/nls.build.ts', - 'vs/nls.ts', - 'vs/nls.mock.ts', + 'vs/loader.d.ts' ].forEach(copyFile); } @@ -160,7 +157,7 @@ export function createESMSourcesAndResources2(options: IOptions2): void { if (file === 'tsconfig.json') { const tsConfig = JSON.parse(fs.readFileSync(path.join(SRC_FOLDER, file)).toString()); - tsConfig.compilerOptions.module = 'es6'; + tsConfig.compilerOptions.module = 'es2022'; tsConfig.compilerOptions.outDir = path.join(path.relative(OUT_FOLDER, OUT_RESOURCES_FOLDER), 'vs').replace(/\\/g, '/'); write(getDestAbsoluteFilePath(file), JSON.stringify(tsConfig, null, '\t')); continue; diff --git a/patched-vscode/build/lib/stylelint/vscode-known-variables.json b/patched-vscode/build/lib/stylelint/vscode-known-variables.json index d8b28e0d..e499732a 100644 --- a/patched-vscode/build/lib/stylelint/vscode-known-variables.json +++ b/patched-vscode/build/lib/stylelint/vscode-known-variables.json @@ -94,6 +94,7 @@ "--vscode-debugTokenExpression-name", "--vscode-debugTokenExpression-number", "--vscode-debugTokenExpression-string", + "--vscode-debugTokenExpression-type", "--vscode-debugTokenExpression-value", "--vscode-debugToolBar-background", "--vscode-debugToolBar-border", @@ -128,15 +129,16 @@ "--vscode-dropdown-listBackground", "--vscode-editor-background", "--vscode-editor-findMatchBackground", - "--vscode-editor-findMatchForeground", "--vscode-editor-findMatchBorder", + "--vscode-editor-findMatchForeground", "--vscode-editor-findMatchHighlightBackground", - "--vscode-editor-findMatchHighlightForeground", "--vscode-editor-findMatchHighlightBorder", + "--vscode-editor-findMatchHighlightForeground", "--vscode-editor-findRangeHighlightBackground", "--vscode-editor-findRangeHighlightBorder", "--vscode-editor-focusedStackFrameHighlightBackground", "--vscode-editor-foldBackground", + "--vscode-editor-foldPlaceholderForeground", "--vscode-editor-foreground", "--vscode-editor-hoverHighlightBackground", "--vscode-editor-inactiveSelectionBackground", @@ -145,6 +147,7 @@ "--vscode-editor-lineHighlightBackground", "--vscode-editor-lineHighlightBorder", "--vscode-editor-linkedEditingBackground", + "--vscode-editor-placeholder-foreground", "--vscode-editor-rangeHighlightBackground", "--vscode-editor-rangeHighlightBorder", "--vscode-editor-selectionBackground", @@ -252,6 +255,10 @@ "--vscode-editorLightBulb-foreground", "--vscode-editorLightBulbAi-foreground", "--vscode-editorLightBulbAutoFix-foreground", + "--vscode-editorActionList-background", + "--vscode-editorActionList-foreground", + "--vscode-editorActionList-focusForeground", + "--vscode-editorActionList-focusBackground", "--vscode-editorLineNumber-activeForeground", "--vscode-editorLineNumber-dimmedForeground", "--vscode-editorLineNumber-foreground", @@ -336,7 +343,7 @@ "--vscode-icon-foreground", "--vscode-inlineChat-background", "--vscode-inlineChat-border", - "--vscode-inlineChat-regionHighlight", + "--vscode-inlineChat-foreground", "--vscode-inlineChat-shadow", "--vscode-inlineChatDiff-inserted", "--vscode-inlineChatDiff-removed", @@ -490,12 +497,12 @@ "--vscode-panelSectionHeader-background", "--vscode-panelSectionHeader-border", "--vscode-panelSectionHeader-foreground", - "--vscode-panelTitle-activeBorder", - "--vscode-panelTitle-activeForeground", - "--vscode-panelTitle-inactiveForeground", "--vscode-panelStickyScroll-background", "--vscode-panelStickyScroll-border", "--vscode-panelStickyScroll-shadow", + "--vscode-panelTitle-activeBorder", + "--vscode-panelTitle-activeForeground", + "--vscode-panelTitle-inactiveForeground", "--vscode-peekView-border", "--vscode-peekViewEditor-background", "--vscode-peekViewEditor-matchHighlightBackground", @@ -519,6 +526,7 @@ "--vscode-problemsWarningIcon-foreground", "--vscode-profileBadge-background", "--vscode-profileBadge-foreground", + "--vscode-profiles-sashBorder", "--vscode-progressBar-background", "--vscode-quickInput-background", "--vscode-quickInput-foreground", @@ -527,7 +535,21 @@ "--vscode-quickInputList-focusForeground", "--vscode-quickInputList-focusIconForeground", "--vscode-quickInputTitle-background", + "--vscode-radio-activeBackground", + "--vscode-radio-activeBorder", + "--vscode-radio-activeForeground", + "--vscode-radio-inactiveBackground", + "--vscode-radio-inactiveBorder", + "--vscode-radio-inactiveForeground", + "--vscode-radio-inactiveHoverBackground", "--vscode-sash-hoverBorder", + "--vscode-scm-historyGraph-green", + "--vscode-scm-historyGraph-historyItemGroupBase", + "--vscode-scm-historyGraph-historyItemGroupHoverLabelForeground", + "--vscode-scm-historyGraph-historyItemGroupLocal", + "--vscode-scm-historyGraph-historyItemGroupRemote", + "--vscode-scm-historyGraph-red", + "--vscode-scm-historyGraph-yellow", "--vscode-scm-historyItemAdditionsForeground", "--vscode-scm-historyItemDeletionsForeground", "--vscode-scm-historyItemSelectedStatisticsBorder", @@ -570,11 +592,11 @@ "--vscode-sideBarSectionHeader-background", "--vscode-sideBarSectionHeader-border", "--vscode-sideBarSectionHeader-foreground", - "--vscode-sideBarTitle-background", - "--vscode-sideBarTitle-foreground", "--vscode-sideBarStickyScroll-background", "--vscode-sideBarStickyScroll-border", "--vscode-sideBarStickyScroll-shadow", + "--vscode-sideBarTitle-background", + "--vscode-sideBarTitle-foreground", "--vscode-sideBySideEditor-horizontalBorder", "--vscode-sideBySideEditor-verticalBorder", "--vscode-simpleFindWidget-sashBorder", @@ -649,9 +671,6 @@ "--vscode-tab-activeBackground", "--vscode-tab-activeBorder", "--vscode-tab-activeBorderTop", - "--vscode-tab-selectedBorderTop", - "--vscode-tab-selectedBackground", - "--vscode-tab-selectedForeground", "--vscode-tab-activeForeground", "--vscode-tab-activeModifiedBorder", "--vscode-tab-border", @@ -663,6 +682,9 @@ "--vscode-tab-inactiveForeground", "--vscode-tab-inactiveModifiedBorder", "--vscode-tab-lastPinnedBorder", + "--vscode-tab-selectedBackground", + "--vscode-tab-selectedBorderTop", + "--vscode-tab-selectedForeground", "--vscode-tab-unfocusedActiveBackground", "--vscode-tab-unfocusedActiveBorder", "--vscode-tab-unfocusedActiveBorderTop", @@ -700,17 +722,21 @@ "--vscode-terminal-foreground", "--vscode-terminal-hoverHighlightBackground", "--vscode-terminal-inactiveSelectionBackground", + "--vscode-terminal-initialHintForeground", "--vscode-terminal-selectionBackground", "--vscode-terminal-selectionForeground", "--vscode-terminal-tab-activeBorder", "--vscode-terminalCommandDecoration-defaultBackground", "--vscode-terminalCommandDecoration-errorBackground", "--vscode-terminalCommandDecoration-successBackground", + "--vscode-terminalCommandGuide-foreground", "--vscode-terminalCursor-background", "--vscode-terminalCursor-foreground", + "--vscode-terminalOverviewRuler-border", "--vscode-terminalOverviewRuler-cursorForeground", "--vscode-terminalOverviewRuler-findMatchForeground", "--vscode-terminalStickyScroll-background", + "--vscode-terminalStickyScroll-border", "--vscode-terminalStickyScrollHover-background", "--vscode-testing-coverCountBadgeBackground", "--vscode-testing-coverCountBadgeForeground", @@ -805,6 +831,7 @@ "--testMessageDecorationFontFamily", "--testMessageDecorationFontSize", "--title-border-bottom-color", + "--title-wco-width", "--vscode-chat-list-background", "--vscode-editorCodeLens-fontFamily", "--vscode-editorCodeLens-fontFamilyDefault", @@ -815,8 +842,6 @@ "--vscode-hover-maxWidth", "--vscode-hover-sourceWhiteSpace", "--vscode-hover-whiteSpace", - "--vscode-inline-chat-quick-voice-height", - "--vscode-inline-chat-quick-voice-width", "--vscode-editor-dictation-widget-height", "--vscode-editor-dictation-widget-width", "--vscode-interactive-session-foreground", @@ -831,6 +856,8 @@ "--vscode-editorStickyScroll-scrollableWidth", "--vscode-editorStickyScroll-foldingOpacityTransition", "--window-border-color", + "--vscode-parameterHintsWidget-editorFontFamily", + "--vscode-parameterHintsWidget-editorFontFamilyDefault", "--workspace-trust-check-color", "--workspace-trust-selected-color", "--workspace-trust-unselected-color", @@ -850,6 +877,8 @@ "--z-index-notebook-scrollbar", "--z-index-run-button-container", "--zoom-factor", - "--test-bar-width" + "--test-bar-width", + "--widget-color", + "--text-link-decoration" ] } diff --git a/patched-vscode/build/lib/treeshaking.js b/patched-vscode/build/lib/treeshaking.js index c8e95511..842f691a 100644 --- a/patched-vscode/build/lib/treeshaking.js +++ b/patched-vscode/build/lib/treeshaking.js @@ -100,24 +100,22 @@ function discoverAndReadFiles(ts, options) { options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); while (queue.length > 0) { const moduleId = queue.shift(); - const dts_filename = path.join(options.sourcesRoot, moduleId + '.d.ts'); + let redirectedModuleId = moduleId; + if (options.redirects[moduleId]) { + redirectedModuleId = options.redirects[moduleId]; + } + const dts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); if (fs.existsSync(dts_filename)) { const dts_filecontents = fs.readFileSync(dts_filename).toString(); FILES[`${moduleId}.d.ts`] = dts_filecontents; continue; } - const js_filename = path.join(options.sourcesRoot, moduleId + '.js'); + const js_filename = path.join(options.sourcesRoot, redirectedModuleId + '.js'); if (fs.existsSync(js_filename)) { // This is an import for a .js file, so ignore it... continue; } - let ts_filename; - if (options.redirects[moduleId]) { - ts_filename = path.join(options.sourcesRoot, options.redirects[moduleId] + '.ts'); - } - else { - ts_filename = path.join(options.sourcesRoot, moduleId + '.ts'); - } + const ts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.ts'); const ts_filecontents = fs.readFileSync(ts_filename).toString(); const info = ts.preProcessFile(ts_filecontents); for (let i = info.importedFiles.length - 1; i >= 0; i--) { diff --git a/patched-vscode/build/lib/treeshaking.ts b/patched-vscode/build/lib/treeshaking.ts index 020e567e..d01b34d2 100644 --- a/patched-vscode/build/lib/treeshaking.ts +++ b/patched-vscode/build/lib/treeshaking.ts @@ -155,25 +155,27 @@ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeSha while (queue.length > 0) { const moduleId = queue.shift()!; - const dts_filename = path.join(options.sourcesRoot, moduleId + '.d.ts'); + let redirectedModuleId: string = moduleId; + if (options.redirects[moduleId]) { + redirectedModuleId = options.redirects[moduleId]; + } + + const dts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); if (fs.existsSync(dts_filename)) { const dts_filecontents = fs.readFileSync(dts_filename).toString(); FILES[`${moduleId}.d.ts`] = dts_filecontents; continue; } - const js_filename = path.join(options.sourcesRoot, moduleId + '.js'); + + const js_filename = path.join(options.sourcesRoot, redirectedModuleId + '.js'); if (fs.existsSync(js_filename)) { // This is an import for a .js file, so ignore it... continue; } - let ts_filename: string; - if (options.redirects[moduleId]) { - ts_filename = path.join(options.sourcesRoot, options.redirects[moduleId] + '.ts'); - } else { - ts_filename = path.join(options.sourcesRoot, moduleId + '.ts'); - } + const ts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.ts'); + const ts_filecontents = fs.readFileSync(ts_filename).toString(); const info = ts.preProcessFile(ts_filecontents); for (let i = info.importedFiles.length - 1; i >= 0; i--) { diff --git a/patched-vscode/build/lib/tsb/transpiler.js b/patched-vscode/build/lib/tsb/transpiler.js index afec9062..5dcc4ca1 100644 --- a/patched-vscode/build/lib/tsb/transpiler.js +++ b/patched-vscode/build/lib/tsb/transpiler.js @@ -305,7 +305,7 @@ class SwcTranspiler { }, module: { type: 'amd', - noInterop: true + noInterop: false }, minify: false, }; @@ -313,7 +313,7 @@ class SwcTranspiler { ...this._swcrcAmd, module: { type: 'commonjs', - importInterop: 'none' + importInterop: 'swc' } }; static _swcrcEsm = { diff --git a/patched-vscode/build/lib/tsb/transpiler.ts b/patched-vscode/build/lib/tsb/transpiler.ts index a546ea63..cbc3d9e8 100644 --- a/patched-vscode/build/lib/tsb/transpiler.ts +++ b/patched-vscode/build/lib/tsb/transpiler.ts @@ -388,7 +388,7 @@ export class SwcTranspiler implements ITranspiler { }, module: { type: 'amd', - noInterop: true + noInterop: false }, minify: false, }; @@ -397,7 +397,7 @@ export class SwcTranspiler implements ITranspiler { ...this._swcrcAmd, module: { type: 'commonjs', - importInterop: 'none' + importInterop: 'swc' } }; diff --git a/patched-vscode/build/linux/debian/dep-lists.js b/patched-vscode/build/linux/debian/dep-lists.js index d843c090..3bb58fb1 100644 --- a/patched-vscode/build/linux/debian/dep-lists.js +++ b/patched-vscode/build/linux/debian/dep-lists.js @@ -31,6 +31,7 @@ exports.referenceGeneratedDepsByArch = { 'libc6 (>= 2.16)', 'libc6 (>= 2.17)', 'libc6 (>= 2.2.5)', + 'libc6 (>= 2.25)', 'libc6 (>= 2.28)', 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', @@ -39,10 +40,8 @@ exports.referenceGeneratedDepsByArch = { 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.37.3)', - 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.30)', 'libnss3 (>= 3.26)', @@ -67,6 +66,7 @@ exports.referenceGeneratedDepsByArch = { 'libatspi2.0-0 (>= 2.9.90)', 'libc6 (>= 2.16)', 'libc6 (>= 2.17)', + 'libc6 (>= 2.25)', 'libc6 (>= 2.28)', 'libc6 (>= 2.4)', 'libc6 (>= 2.9)', @@ -77,10 +77,8 @@ exports.referenceGeneratedDepsByArch = { 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.37.3)', - 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.30)', 'libnss3 (>= 3.26)', @@ -108,6 +106,7 @@ exports.referenceGeneratedDepsByArch = { 'libatk1.0-0 (>= 2.2.0)', 'libatspi2.0-0 (>= 2.9.90)', 'libc6 (>= 2.17)', + 'libc6 (>= 2.25)', 'libc6 (>= 2.28)', 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', @@ -116,10 +115,8 @@ exports.referenceGeneratedDepsByArch = { 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.37.3)', - 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.30)', 'libnss3 (>= 3.26)', diff --git a/patched-vscode/build/linux/debian/dep-lists.ts b/patched-vscode/build/linux/debian/dep-lists.ts index 4028370c..e3d78d11 100644 --- a/patched-vscode/build/linux/debian/dep-lists.ts +++ b/patched-vscode/build/linux/debian/dep-lists.ts @@ -31,6 +31,7 @@ export const referenceGeneratedDepsByArch = { 'libc6 (>= 2.16)', 'libc6 (>= 2.17)', 'libc6 (>= 2.2.5)', + 'libc6 (>= 2.25)', 'libc6 (>= 2.28)', 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', @@ -39,10 +40,8 @@ export const referenceGeneratedDepsByArch = { 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.37.3)', - 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.30)', 'libnss3 (>= 3.26)', @@ -67,6 +66,7 @@ export const referenceGeneratedDepsByArch = { 'libatspi2.0-0 (>= 2.9.90)', 'libc6 (>= 2.16)', 'libc6 (>= 2.17)', + 'libc6 (>= 2.25)', 'libc6 (>= 2.28)', 'libc6 (>= 2.4)', 'libc6 (>= 2.9)', @@ -77,10 +77,8 @@ export const referenceGeneratedDepsByArch = { 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.37.3)', - 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.30)', 'libnss3 (>= 3.26)', @@ -108,6 +106,7 @@ export const referenceGeneratedDepsByArch = { 'libatk1.0-0 (>= 2.2.0)', 'libatspi2.0-0 (>= 2.9.90)', 'libc6 (>= 2.17)', + 'libc6 (>= 2.25)', 'libc6 (>= 2.28)', 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', @@ -116,10 +115,8 @@ export const referenceGeneratedDepsByArch = { 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.37.3)', - 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.30)', 'libnss3 (>= 3.26)', diff --git a/patched-vscode/build/linux/dependencies-generator.js b/patched-vscode/build/linux/dependencies-generator.js index bff0c9a2..21c4236a 100644 --- a/patched-vscode/build/linux/dependencies-generator.js +++ b/patched-vscode/build/linux/dependencies-generator.js @@ -15,6 +15,7 @@ const dep_lists_2 = require("./rpm/dep-lists"); const types_1 = require("./debian/types"); const types_2 = require("./rpm/types"); const product = require("../../product.json"); +const esm_1 = require("../lib/esm"); // A flag that can easily be toggled. // Make sure to compile the build directory after toggling the value. // If false, we warn about new dependencies if they show up @@ -23,7 +24,7 @@ const product = require("../../product.json"); // The reference dependencies, which one has to update when the new dependencies // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES = true; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/122.0.6261.156:chrome/installer/linux/BUILD.gn;l=64-80 +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/124.0.6367.243:chrome/installer/linux/BUILD.gn;l=64-80 // and the Linux Archive build // Shared library dependencies that we already bundle. const bundledDeps = [ @@ -43,7 +44,8 @@ async function getDependencies(packageType, buildDir, applicationName, arch) { throw new Error('Invalid RPM arch string ' + arch); } // Get the files for which we want to find dependencies. - const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); + const canAsar = !(0, esm_1.isESM)('ASAR disabled in Linux builds'); // TODO@esm ASAR disabled in ESM + const nativeModulesPath = path.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules'); const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']); if (findResult.status) { console.error('Error finding files:'); diff --git a/patched-vscode/build/linux/dependencies-generator.ts b/patched-vscode/build/linux/dependencies-generator.ts index 226310e1..45eeab4d 100644 --- a/patched-vscode/build/linux/dependencies-generator.ts +++ b/patched-vscode/build/linux/dependencies-generator.ts @@ -15,6 +15,7 @@ import { referenceGeneratedDepsByArch as rpmGeneratedDeps } from './rpm/dep-list import { DebianArchString, isDebianArchString } from './debian/types'; import { isRpmArchString, RpmArchString } from './rpm/types'; import product = require('../../product.json'); +import { isESM } from '../lib/esm'; // A flag that can easily be toggled. // Make sure to compile the build directory after toggling the value. @@ -25,7 +26,7 @@ import product = require('../../product.json'); // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/122.0.6261.156:chrome/installer/linux/BUILD.gn;l=64-80 +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/124.0.6367.243:chrome/installer/linux/BUILD.gn;l=64-80 // and the Linux Archive build // Shared library dependencies that we already bundle. const bundledDeps = [ @@ -47,7 +48,8 @@ export async function getDependencies(packageType: 'deb' | 'rpm', buildDir: stri } // Get the files for which we want to find dependencies. - const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); + const canAsar = !isESM('ASAR disabled in Linux builds'); // TODO@esm ASAR disabled in ESM + const nativeModulesPath = path.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules'); const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); if (findResult.status) { console.error('Error finding files:'); diff --git a/patched-vscode/build/linux/rpm/dep-lists.js b/patched-vscode/build/linux/rpm/dep-lists.js index 8be47729..fa393808 100644 --- a/patched-vscode/build/linux/rpm/dep-lists.js +++ b/patched-vscode/build/linux/rpm/dep-lists.js @@ -45,12 +45,14 @@ exports.referenceGeneratedDepsByArch = { 'libc.so.6(GLIBC_2.17)(64bit)', 'libc.so.6(GLIBC_2.18)(64bit)', 'libc.so.6(GLIBC_2.2.5)(64bit)', + 'libc.so.6(GLIBC_2.25)(64bit)', 'libc.so.6(GLIBC_2.28)(64bit)', 'libc.so.6(GLIBC_2.3)(64bit)', 'libc.so.6(GLIBC_2.3.2)(64bit)', 'libc.so.6(GLIBC_2.3.3)(64bit)', 'libc.so.6(GLIBC_2.3.4)(64bit)', 'libc.so.6(GLIBC_2.4)(64bit)', + 'libc.so.6(GLIBC_2.5)(64bit)', 'libc.so.6(GLIBC_2.6)(64bit)', 'libc.so.6(GLIBC_2.7)(64bit)', 'libc.so.6(GLIBC_2.8)(64bit)', @@ -71,11 +73,7 @@ exports.referenceGeneratedDepsByArch = { 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', - 'libgssapi_krb5.so.2()(64bit)', - 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)(64bit)', 'libgtk-3.so.0()(64bit)', - 'libkrb5.so.3()(64bit)', - 'libkrb5.so.3(krb5_3_MIT)(64bit)', 'libm.so.6()(64bit)', 'libm.so.6(GLIBC_2.2.5)(64bit)', 'libnspr4.so()(64bit)', @@ -140,8 +138,10 @@ exports.referenceGeneratedDepsByArch = { 'libc.so.6(GLIBC_2.16)', 'libc.so.6(GLIBC_2.17)', 'libc.so.6(GLIBC_2.18)', + 'libc.so.6(GLIBC_2.25)', 'libc.so.6(GLIBC_2.28)', 'libc.so.6(GLIBC_2.4)', + 'libc.so.6(GLIBC_2.5)', 'libc.so.6(GLIBC_2.6)', 'libc.so.6(GLIBC_2.7)', 'libc.so.6(GLIBC_2.8)', @@ -162,12 +162,8 @@ exports.referenceGeneratedDepsByArch = { 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', - 'libgssapi_krb5.so.2', - 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)', 'libgtk-3.so.0', 'libgtk-3.so.0()(64bit)', - 'libkrb5.so.3', - 'libkrb5.so.3(krb5_3_MIT)', 'libm.so.6', 'libm.so.6(GLIBC_2.4)', 'libnspr4.so', @@ -241,6 +237,7 @@ exports.referenceGeneratedDepsByArch = { 'libc.so.6()(64bit)', 'libc.so.6(GLIBC_2.17)(64bit)', 'libc.so.6(GLIBC_2.18)(64bit)', + 'libc.so.6(GLIBC_2.25)(64bit)', 'libc.so.6(GLIBC_2.28)(64bit)', 'libcairo.so.2()(64bit)', 'libcurl.so.4()(64bit)', @@ -259,11 +256,7 @@ exports.referenceGeneratedDepsByArch = { 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', - 'libgssapi_krb5.so.2()(64bit)', - 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)(64bit)', 'libgtk-3.so.0()(64bit)', - 'libkrb5.so.3()(64bit)', - 'libkrb5.so.3(krb5_3_MIT)(64bit)', 'libm.so.6()(64bit)', 'libm.so.6(GLIBC_2.17)(64bit)', 'libnspr4.so()(64bit)', diff --git a/patched-vscode/build/linux/rpm/dep-lists.ts b/patched-vscode/build/linux/rpm/dep-lists.ts index 24b18d50..9eed3a79 100644 --- a/patched-vscode/build/linux/rpm/dep-lists.ts +++ b/patched-vscode/build/linux/rpm/dep-lists.ts @@ -44,12 +44,14 @@ export const referenceGeneratedDepsByArch = { 'libc.so.6(GLIBC_2.17)(64bit)', 'libc.so.6(GLIBC_2.18)(64bit)', 'libc.so.6(GLIBC_2.2.5)(64bit)', + 'libc.so.6(GLIBC_2.25)(64bit)', 'libc.so.6(GLIBC_2.28)(64bit)', 'libc.so.6(GLIBC_2.3)(64bit)', 'libc.so.6(GLIBC_2.3.2)(64bit)', 'libc.so.6(GLIBC_2.3.3)(64bit)', 'libc.so.6(GLIBC_2.3.4)(64bit)', 'libc.so.6(GLIBC_2.4)(64bit)', + 'libc.so.6(GLIBC_2.5)(64bit)', 'libc.so.6(GLIBC_2.6)(64bit)', 'libc.so.6(GLIBC_2.7)(64bit)', 'libc.so.6(GLIBC_2.8)(64bit)', @@ -70,11 +72,7 @@ export const referenceGeneratedDepsByArch = { 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', - 'libgssapi_krb5.so.2()(64bit)', - 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)(64bit)', 'libgtk-3.so.0()(64bit)', - 'libkrb5.so.3()(64bit)', - 'libkrb5.so.3(krb5_3_MIT)(64bit)', 'libm.so.6()(64bit)', 'libm.so.6(GLIBC_2.2.5)(64bit)', 'libnspr4.so()(64bit)', @@ -139,8 +137,10 @@ export const referenceGeneratedDepsByArch = { 'libc.so.6(GLIBC_2.16)', 'libc.so.6(GLIBC_2.17)', 'libc.so.6(GLIBC_2.18)', + 'libc.so.6(GLIBC_2.25)', 'libc.so.6(GLIBC_2.28)', 'libc.so.6(GLIBC_2.4)', + 'libc.so.6(GLIBC_2.5)', 'libc.so.6(GLIBC_2.6)', 'libc.so.6(GLIBC_2.7)', 'libc.so.6(GLIBC_2.8)', @@ -161,12 +161,8 @@ export const referenceGeneratedDepsByArch = { 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', - 'libgssapi_krb5.so.2', - 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)', 'libgtk-3.so.0', 'libgtk-3.so.0()(64bit)', - 'libkrb5.so.3', - 'libkrb5.so.3(krb5_3_MIT)', 'libm.so.6', 'libm.so.6(GLIBC_2.4)', 'libnspr4.so', @@ -240,6 +236,7 @@ export const referenceGeneratedDepsByArch = { 'libc.so.6()(64bit)', 'libc.so.6(GLIBC_2.17)(64bit)', 'libc.so.6(GLIBC_2.18)(64bit)', + 'libc.so.6(GLIBC_2.25)(64bit)', 'libc.so.6(GLIBC_2.28)(64bit)', 'libcairo.so.2()(64bit)', 'libcurl.so.4()(64bit)', @@ -258,11 +255,7 @@ export const referenceGeneratedDepsByArch = { 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', - 'libgssapi_krb5.so.2()(64bit)', - 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)(64bit)', 'libgtk-3.so.0()(64bit)', - 'libkrb5.so.3()(64bit)', - 'libkrb5.so.3(krb5_3_MIT)(64bit)', 'libm.so.6()(64bit)', 'libm.so.6(GLIBC_2.17)(64bit)', 'libnspr4.so()(64bit)', diff --git a/patched-vscode/build/monaco/monaco.d.ts.recipe b/patched-vscode/build/monaco/monaco.d.ts.recipe index a6eb3b71..ef5c2e39 100644 --- a/patched-vscode/build/monaco/monaco.d.ts.recipe +++ b/patched-vscode/build/monaco/monaco.d.ts.recipe @@ -92,7 +92,7 @@ declare namespace monaco.editor { #includeAll(vs/editor/standalone/browser/standaloneEditor;languages.Token=>Token): #include(vs/editor/standalone/common/standaloneTheme): BuiltinTheme, IStandaloneThemeData, IColors #include(vs/editor/common/languages/supports/tokenization): ITokenThemeRule -#include(vs/editor/browser/services/webWorker): MonacoWebWorker, IWebWorkerOptions +#include(vs/editor/standalone/browser/standaloneWebWorker): MonacoWebWorker, IWebWorkerOptions #include(vs/editor/standalone/browser/standaloneCodeEditor): IActionDescriptor, IGlobalEditorOptions, IStandaloneEditorConstructionOptions, IStandaloneDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor export interface ICommandHandler { (...args: any[]): void; diff --git a/patched-vscode/build/npm/dirs.js b/patched-vscode/build/npm/dirs.js index 2aa1c43f..fbefd418 100644 --- a/patched-vscode/build/npm/dirs.js +++ b/patched-vscode/build/npm/dirs.js @@ -29,7 +29,6 @@ const dirs = [ 'extensions/jake', 'extensions/json-language-features', 'extensions/json-language-features/server', - 'extensions/markdown-language-features/server', 'extensions/markdown-language-features', 'extensions/markdown-math', 'extensions/media-preview', @@ -39,9 +38,6 @@ const dirs = [ 'extensions/npm', 'extensions/php-language-features', 'extensions/references-view', - 'extensions/sagemaker-extension', - 'extensions/sagemaker-idle-extension', - 'extensions/sagemaker-terminal-crash-mitigation', 'extensions/search-result', 'extensions/simple-browser', 'extensions/tunnel-forwarding', diff --git a/patched-vscode/build/npm/gyp/package.json b/patched-vscode/build/npm/gyp/package.json index 3961e955..a1564133 100644 --- a/patched-vscode/build/npm/gyp/package.json +++ b/patched-vscode/build/npm/gyp/package.json @@ -4,7 +4,7 @@ "private": true, "license": "MIT", "devDependencies": { - "node-gyp": "^9.4.0" + "node-gyp": "^10.1.0" }, "scripts": {} } diff --git a/patched-vscode/build/npm/gyp/yarn.lock b/patched-vscode/build/npm/gyp/yarn.lock index 96d132e7..a9bf9017 100644 --- a/patched-vscode/build/npm/gyp/yarn.lock +++ b/patched-vscode/build/npm/gyp/yarn.lock @@ -14,10 +14,21 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@npmcli/agent@^2.0.0": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-2.2.2.tgz#967604918e62f620a648c7975461c9c9e74fc5d5" + integrity sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og== + dependencies: + agent-base "^7.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + lru-cache "^10.0.1" + socks-proxy-agent "^8.0.3" + "@npmcli/fs@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz#233d43a25a91d68c3a863ba0da6a3f00924a173e" - integrity sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w== + version "3.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.1.tgz#59cdaa5adca95d135fc00f2bb53f5771575ce726" + integrity sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg== dependencies: semver "^7.3.5" @@ -26,31 +37,17 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@tootallnate/once@2": +abbrev@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - -abbrev@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" + integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== -agent-base@6, agent-base@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== +agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== dependencies: - debug "4" - -agentkeepalive@^4.2.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" - integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== - dependencies: - debug "^4.1.0" - depd "^2.0.0" - humanize-ms "^1.2.1" + debug "^4.3.4" aggregate-error@^3.0.0: version "3.1.0" @@ -82,32 +79,11 @@ ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -"aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== - -are-we-there-yet@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" - integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - brace-expansion@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" @@ -115,17 +91,17 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -cacache@^17.0.0: - version "17.1.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.1.3.tgz#c6ac23bec56516a7c0c52020fd48b4909d7c7044" - integrity sha512-jAdjGxmPxZh0IipMdR7fK/4sDSrHMLUV0+GvVUsjwyGNKHsh79kW/otg+GkbXwl6Uzvy9wsvHOX4nUoWldeZMg== +cacache@^18.0.0: + version "18.0.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-18.0.3.tgz#864e2c18414e1e141ae8763f31e46c2cb96d1b21" + integrity sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg== dependencies: "@npmcli/fs" "^3.1.0" fs-minipass "^3.0.0" glob "^10.2.2" - lru-cache "^7.7.1" - minipass "^5.0.0" - minipass-collect "^1.0.2" + lru-cache "^10.0.1" + minipass "^7.0.3" + minipass-collect "^2.0.1" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" p-map "^4.0.0" @@ -155,21 +131,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -console-control-strings@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== - cross-spawn@^7.0.0: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -179,22 +140,19 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -debug@4, debug@^4.1.0, debug@^4.3.3: +debug@4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== - -depd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" eastasianwidth@^0.2.0: version "0.2.0" @@ -234,9 +192,9 @@ exponential-backoff@^3.1.1: integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== dependencies: cross-spawn "^7.0.0" signal-exit "^4.0.1" @@ -249,93 +207,50 @@ fs-minipass@^2.0.0: minipass "^3.0.0" fs-minipass@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.2.tgz#5b383858efa8c1eb8c33b39e994f7e8555b8b3a3" - integrity sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g== + version "3.0.3" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54" + integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== dependencies: - minipass "^5.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -gauge@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" - integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.3" - console-control-strings "^1.1.0" - has-unicode "^2.0.1" - signal-exit "^3.0.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.5" + minipass "^7.0.3" -glob@^10.2.2: - version "10.3.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.3.tgz#8360a4ffdd6ed90df84aa8d52f21f452e86a123b" - integrity sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw== +glob@^10.2.2, glob@^10.3.10: + version "10.4.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" + integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== dependencies: foreground-child "^3.1.0" - jackspeak "^2.0.3" - minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" graceful-fs@^4.2.6: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== - http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" + agent-base "^7.1.0" + debug "^4.3.4" -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== +https-proxy-agent@^7.0.1: + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== dependencies: - agent-base "6" + agent-base "^7.0.2" debug "4" -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -353,23 +268,13 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ip@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" - integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== + jsbn "1.1.0" + sprintf-js "^1.1.3" is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -386,15 +291,30 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jackspeak@^2.0.3: - version "2.2.2" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.2.tgz#707c62733924b8dc2a0a629dc6248577788b5385" - integrity sha512-mgNtVv4vUuaKA97yxUHoA3+FkuhtxkjdXEWOyB/N76fjy0FjezEt34oy3epBtvCvS+7DyKwqCFWx/oJLV5+kCg== +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== + +jackspeak@^3.1.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.0.tgz#a75763ff36ad778ede6a156d8ee8b124de445b4a" + integrity sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +lru-cache@^10.0.1, lru-cache@^10.2.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.3.0.tgz#4a4aaf10c84658ab70f79a85a9a3f1e1fb11196b" + integrity sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -402,64 +322,44 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^7.7.1: - version "7.18.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== - -"lru-cache@^9.1.1 || ^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" - integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== - -make-fetch-happen@^11.0.3: - version "11.1.1" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz#85ceb98079584a9523d4bf71d32996e7e208549f" - integrity sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w== +make-fetch-happen@^13.0.0: + version "13.0.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz#273ba2f78f45e1f3a6dca91cede87d9fa4821e36" + integrity sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA== dependencies: - agentkeepalive "^4.2.1" - cacache "^17.0.0" + "@npmcli/agent" "^2.0.0" + cacache "^18.0.0" http-cache-semantics "^4.1.1" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" is-lambda "^1.0.1" - lru-cache "^7.7.1" - minipass "^5.0.0" + minipass "^7.0.2" minipass-fetch "^3.0.0" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" negotiator "^0.6.3" + proc-log "^4.2.0" promise-retry "^2.0.1" - socks-proxy-agent "^7.0.0" ssri "^10.0.0" -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^9.0.1: - version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== +minipass-collect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-2.0.1.tgz#1621bc77e12258a12c60d34e2276ec5c20680863" + integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw== dependencies: - minipass "^3.0.0" + minipass "^7.0.3" minipass-fetch@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.3.tgz#d9df70085609864331b533c960fd4ffaa78d15ce" - integrity sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.5.tgz#f0f97e40580affc4a35cc4a1349f05ae36cb1e4c" + integrity sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg== dependencies: - minipass "^5.0.0" + minipass "^7.0.3" minipass-sized "^1.0.3" minizlib "^2.1.2" optionalDependencies: @@ -498,10 +398,10 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": - version "7.0.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.2.tgz#58a82b7d81c7010da5bd4b2c0c85ac4b4ec5131e" - integrity sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" @@ -521,56 +421,33 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - negotiator@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -node-gyp@^9.4.0: - version "9.4.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.0.tgz#2a7a91c7cba4eccfd95e949369f27c9ba704f369" - integrity sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg== +node-gyp@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.1.0.tgz#75e6f223f2acb4026866c26a2ead6aab75a8ca7e" + integrity sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA== dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" - glob "^7.1.4" + glob "^10.3.10" graceful-fs "^4.2.6" - make-fetch-happen "^11.0.3" - nopt "^6.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" + make-fetch-happen "^13.0.0" + nopt "^7.0.0" + proc-log "^3.0.0" semver "^7.3.5" tar "^6.1.2" - which "^2.0.2" + which "^4.0.0" -nopt@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" - integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== - dependencies: - abbrev "^1.0.0" - -npmlog@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" - integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== +nopt@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7" + integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w== dependencies: - are-we-there-yet "^3.0.0" - console-control-strings "^1.1.0" - gauge "^4.0.3" - set-blocking "^2.0.0" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" + abbrev "^2.0.0" p-map@^4.0.0: version "4.0.0" @@ -579,24 +456,34 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-scurry@^1.10.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" - integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== dependencies: - lru-cache "^9.1.1 || ^10.0.0" + lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +proc-log@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" + integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== + +proc-log@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-4.2.0.tgz#b6f461e4026e75fdfe228b265e9f7a00779d7034" + integrity sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA== + promise-retry@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" @@ -605,32 +492,11 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -643,11 +509,6 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0" -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -660,11 +521,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" @@ -675,31 +531,36 @@ smart-buffer@^4.2.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== -socks-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" - integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== +socks-proxy-agent@^8.0.3: + version "8.0.4" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz#9071dca17af95f483300316f4b063578fa0db08c" + integrity sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw== dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" + agent-base "^7.1.1" + debug "^4.3.4" + socks "^2.8.3" -socks@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== +socks@^2.8.3: + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== dependencies: - ip "^2.0.0" + ip-address "^9.0.5" smart-buffer "^4.2.0" +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + ssri@^10.0.0: - version "10.0.4" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.4.tgz#5a20af378be586df139ddb2dfb3bf992cf0daba6" - integrity sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ== + version "10.0.6" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.6.tgz#a8aade2de60ba2bce8688e3fa349bad05c7dc1e5" + integrity sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ== dependencies: - minipass "^5.0.0" + minipass "^7.0.3" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -717,14 +578,8 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -764,24 +619,19 @@ unique-slug@^4.0.0: dependencies: imurmurhash "^0.1.4" -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -which@^2.0.1, which@^2.0.2: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -wide-align@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" - integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== +which@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" + integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== dependencies: - string-width "^1.0.2 || 2 || 3 || 4" + isexe "^3.1.1" "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" @@ -801,11 +651,6 @@ wrap-ansi@^8.1.0: string-width "^5.0.1" strip-ansi "^7.0.1" -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" diff --git a/patched-vscode/build/npm/postinstall.js b/patched-vscode/build/npm/postinstall.js index 616e0663..9ca239e3 100644 --- a/patched-vscode/build/npm/postinstall.js +++ b/patched-vscode/build/npm/postinstall.js @@ -54,14 +54,10 @@ function yarnInstall(dir, opts) { console.log(`Installing dependencies in ${dir} inside container ${process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME']}...`); opts.cwd = root; - if (process.env['npm_config_arch'] === 'arm64' || process.env['npm_config_arch'] === 'arm') { + if (process.env['npm_config_arch'] === 'arm64') { run('sudo', ['docker', 'run', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], opts); } - if (process.env['npm_config_arch'] === 'arm') { - run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/home/builduser`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/home/builduser/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); - } else { - run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); - } + run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${dir}/node_modules`], opts); } else { console.log(`Installing dependencies in ${dir}...`); @@ -85,11 +81,6 @@ for (let dir of dirs) { } } - if (/^(.build\/distro\/npm\/)?remote/.test(dir) && process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64')) { - // windows arm: do not execute `yarn` on remote folder - continue; - } - let opts; if (dir === 'build') { @@ -132,5 +123,5 @@ for (let dir of dirs) { yarnInstall(dir, opts); } -// cp.execSync('git config pull.rebase merges'); -// cp.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); +cp.execSync('git config pull.rebase merges'); +cp.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); diff --git a/patched-vscode/build/npm/preinstall.js b/patched-vscode/build/npm/preinstall.js index fdb01f57..f359215e 100644 --- a/patched-vscode/build/npm/preinstall.js +++ b/patched-vscode/build/npm/preinstall.js @@ -73,9 +73,9 @@ function hasSupportedVisualStudioVersion() { const programFiles86Path = process.env['ProgramFiles(x86)']; const programFiles64Path = process.env['ProgramFiles']; + const vsTypes = ['Enterprise', 'Professional', 'Community', 'Preview', 'BuildTools', 'IntPreview']; if (programFiles64Path) { vsPath = `${programFiles64Path}/Microsoft Visual Studio/${version}`; - const vsTypes = ['Enterprise', 'Professional', 'Community', 'Preview', 'BuildTools']; if (vsTypes.some(vsType => fs.existsSync(path.join(vsPath, vsType)))) { availableVersions.push(version); break; @@ -84,7 +84,6 @@ function hasSupportedVisualStudioVersion() { if (programFiles86Path) { vsPath = `${programFiles86Path}/Microsoft Visual Studio/${version}`; - const vsTypes = ['Enterprise', 'Professional', 'Community', 'Preview', 'BuildTools']; if (vsTypes.some(vsType => fs.existsSync(path.join(vsPath, vsType)))) { availableVersions.push(version); break; @@ -123,19 +122,7 @@ function installHeaders() { cp.execFileSync(node_gyp, ['install', '--dist-url', local.disturl, local.target], { shell: true }); } - // Avoid downloading headers for Windows arm64 till we move to Nodejs v19 in remote - // which is the first official release with support for the architecture. Downloading - // the headers for older versions now redirect to https://origin.nodejs.org/404.html - // which causes checksum validation error in node-gyp. - // - // gyp http 200 https://origin.nodejs.org/404.html - // gyp WARN install got an error, rolling back install - // gyp ERR! install error - // gyp ERR! stack Error: win-arm64/node.lib local checksum 4c62bed7a032f7b36984321b7ffdd60b596fac870672037ff879ae9ac9548fb7 not match remote undefined - // - if (remote !== undefined && !versions.has(remote.target) && - process.env['npm_config_arch'] !== "arm64" && - process.arch !== "arm64") { + if (remote !== undefined && !versions.has(remote.target)) { // Both disturl and target come from a file checked into our repository cp.execFileSync(node_gyp, ['install', '--dist-url', remote.disturl, remote.target], { shell: true }); } diff --git a/patched-vscode/build/package.json b/patched-vscode/build/package.json index 2b89bbc1..c93b66e0 100644 --- a/patched-vscode/build/package.json +++ b/patched-vscode/build/package.json @@ -4,7 +4,7 @@ "license": "MIT", "devDependencies": { "@azure/cosmos": "^3", - "@azure/identity": "^3.4.1", + "@azure/identity": "^4.2.1", "@azure/storage-blob": "^12.17.0", "@electron/get": "^2.0.0", "@types/ansi-colors": "^3.2.0", @@ -20,6 +20,7 @@ "@types/gulp-gzip": "^0.0.31", "@types/gulp-json-editor": "^2.2.31", "@types/gulp-rename": "^0.0.33", + "@types/gulp-sort": "^2.0.4", "@types/gulp-sourcemaps": "^0.0.32", "@types/mime": "0.0.29", "@types/minimatch": "^3.0.3", @@ -41,10 +42,10 @@ "commander": "^7.0.0", "debug": "^4.3.2", "electron-osx-sign": "^0.4.16", - "esbuild": "0.20.0", + "esbuild": "0.23.0", "extract-zip": "^2.0.1", "gulp-merge-json": "^2.1.1", - "gulp-shell": "^0.8.0", + "gulp-sort": "^2.0.0", "jsonc-parser": "^2.3.0", "mime": "^1.4.1", "mkdirp": "^1.0.4", @@ -52,10 +53,11 @@ "ternary-stream": "^3.0.0", "through2": "^4.0.2", "tmp": "^0.2.1", - "vscode-universal-bundler": "^0.0.2", + "vscode-universal-bundler": "^0.1.3", "workerpool": "^6.4.0", "yauzl": "^2.10.0" }, + "type": "commonjs", "scripts": { "compile": "../node_modules/.bin/tsc -p tsconfig.build.json", "watch": "../node_modules/.bin/tsc -p tsconfig.build.json --watch", diff --git a/patched-vscode/build/yarn.lock b/patched-vscode/build/yarn.lock index 3131c432..1e3928aa 100644 --- a/patched-vscode/build/yarn.lock +++ b/patched-vscode/build/yarn.lock @@ -9,20 +9,19 @@ dependencies: tslib "^2.0.0" +"@azure/abort-controller@^2.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-2.1.2.tgz#42fe0ccab23841d9905812c58f1082d27784566d" + integrity sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA== + dependencies: + tslib "^2.6.2" + "@azure/core-asynciterator-polyfill@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7" integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== -"@azure/core-auth@^1.3.0": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0" - integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA== - dependencies: - "@azure/abort-controller" "^1.0.0" - tslib "^2.2.0" - -"@azure/core-auth@^1.5.0": +"@azure/core-auth@^1.3.0", "@azure/core-auth@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.5.0.tgz#a41848c5c31cb3b7c84c409885267d55a2c92e44" integrity sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw== @@ -81,22 +80,7 @@ dependencies: "@azure/core-asynciterator-polyfill" "^1.0.0" -"@azure/core-rest-pipeline@^1.1.0", "@azure/core-rest-pipeline@^1.2.0": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.3.2.tgz#82bfb4e960b4ecf4f1a1cdb1afde4ce9192aef09" - integrity sha512-kymICKESeHBpVLgQiAxllgWdSTopkqtmfPac8ITwMCxNEC6hzbSpqApYbjzxbBNkBMgoD4GESo6LLhR/sPh6kA== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-auth" "^1.3.0" - "@azure/core-tracing" "1.0.0-preview.13" - "@azure/logger" "^1.0.0" - form-data "^4.0.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - tslib "^2.2.0" - uuid "^8.3.0" - -"@azure/core-rest-pipeline@^1.5.0": +"@azure/core-rest-pipeline@^1.1.0", "@azure/core-rest-pipeline@^1.2.0", "@azure/core-rest-pipeline@^1.5.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.7.0.tgz#71f42c19af160422cc84513809ff9668d8047087" integrity sha512-e2awPzwMKHrmvYgZ0qIKNkqnCM1QoDs7A0rOiS3OSAlOQOz/kL7PPKHXwFMuBeaRvS8i7fgobJn79q2Cji5f+Q== @@ -126,21 +110,13 @@ dependencies: tslib "^2.2.0" -"@azure/core-util@^1.1.0", "@azure/core-util@^1.6.1": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.6.1.tgz#fea221c4fa43c26543bccf799beb30c1c7878f5a" - integrity sha512-h5taHeySlsV9qxuK64KZxy4iln1BtMYlNt5jbuEFN3UFSAd1EwKg/Gjl5a6tZ/W8t6li3xPnutOx7zbDyXnPmQ== - dependencies: - "@azure/abort-controller" "^1.0.0" - tslib "^2.2.0" - -"@azure/core-util@^1.1.1": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.2.0.tgz#3499deba1fc36dda6f1912b791809b6f15d4a392" - integrity sha512-ffGIw+Qs8bNKNLxz5UPkz4/VBM/EZY07mPve1ZYFqYUdPwFqRj0RPk0U7LZMOfT7GCck9YjuT1Rfp1PApNl1ng== +"@azure/core-util@^1.1.0", "@azure/core-util@^1.1.1", "@azure/core-util@^1.3.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.9.0.tgz#469afd7e6452d5388b189f90d33f7756b0b210d1" + integrity sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw== dependencies: - "@azure/abort-controller" "^1.0.0" - tslib "^2.2.0" + "@azure/abort-controller" "^2.0.0" + tslib "^2.6.2" "@azure/cosmos@^3": version "3.17.3" @@ -161,20 +137,20 @@ universal-user-agent "^6.0.0" uuid "^8.3.0" -"@azure/identity@^3.4.1": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-3.4.1.tgz#18ba48b7421c818ef8116e8eec3c03ec1a62649a" - integrity sha512-oQ/r5MBdfZTMIUcY5Ch8G7Vv9aIXDkEYyU4Dfqjim4MQN+LY2uiQ57P1JDopMLeHCsZxM4yy8lEdne3tM9Xhzg== +"@azure/identity@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.2.1.tgz#22b366201e989b7b41c0e1690e103bd579c31e4c" + integrity sha512-U8hsyC9YPcEIzoaObJlRDvp7KiF0MGS7xcWbyJSVvXRkC/HXo1f0oYeBYmEvVgRfacw7GHf6D6yAoh9JHz6A5Q== dependencies: "@azure/abort-controller" "^1.0.0" "@azure/core-auth" "^1.5.0" "@azure/core-client" "^1.4.0" "@azure/core-rest-pipeline" "^1.1.0" "@azure/core-tracing" "^1.0.0" - "@azure/core-util" "^1.6.1" + "@azure/core-util" "^1.3.0" "@azure/logger" "^1.0.0" - "@azure/msal-browser" "^3.5.0" - "@azure/msal-node" "^2.5.1" + "@azure/msal-browser" "^3.11.1" + "@azure/msal-node" "^2.9.2" events "^3.0.0" jws "^4.0.0" open "^8.0.0" @@ -188,24 +164,24 @@ dependencies: tslib "^2.0.0" -"@azure/msal-browser@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.5.0.tgz#eb64c931c78c2b75c70807f618e1284bbb183380" - integrity sha512-2NtMuel4CI3UEelCPKkNRXgKzpWEX48fvxIvPz7s0/sTcCaI08r05IOkH2GkXW+czUOtuY6+oGafJCpumnjRLg== +"@azure/msal-browser@^3.11.1": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.17.0.tgz#dee9ccae586239e7e0708b261f7ffa5bc7e00fb7" + integrity sha512-csccKXmW2z7EkZ0I3yAoW/offQt+JECdTIV/KrnRoZyM7wCSsQWODpwod8ZhYy7iOyamcHApR9uCh0oD1M+0/A== dependencies: - "@azure/msal-common" "14.4.0" + "@azure/msal-common" "14.12.0" -"@azure/msal-common@14.4.0": - version "14.4.0" - resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.4.0.tgz#f938c1d96bb73d65baab985c96faaa273c97cfd5" - integrity sha512-ffCymScQuMKVj+YVfwNI52A5Tu+uiZO2eTf+c+3TXxdAssks4nokJhtr+uOOMxH0zDi6d1OjFKFKeXODK0YLSg== +"@azure/msal-common@14.12.0": + version "14.12.0" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.12.0.tgz#844abe269b071f8fa8949dadc2a7b65bbb147588" + integrity sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw== -"@azure/msal-node@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.5.1.tgz#d180a1ba5fdc611a318a8f018a2db3453e2e2898" - integrity sha512-PsPRISqCG253HQk1cAS7eJW7NWTbnBGpG+vcGGz5z4JYRdnM2EIXlj1aBpXCdozenEPtXEVvHn2ELleW1w82nQ== +"@azure/msal-node@^2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.9.2.tgz#e6d3c1661012c1bd0ef68e328f73a2fdede52931" + integrity sha512-8tvi6Cos3m+0KmRbPjgkySXi+UQU/QiuVRFnrxIwt5xZlEEFa69O04RTaNESGgImyBBlYbo2mfE8/U8Bbdk1WQ== dependencies: - "@azure/msal-common" "14.4.0" + "@azure/msal-common" "14.12.0" jsonwebtoken "^9.0.0" uuid "^8.3.0" @@ -223,6 +199,15 @@ events "^3.0.0" tslib "^2.2.0" +"@electron/asar@^3.2.7": + version "3.2.10" + resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.10.tgz#615cf346b734b23cafa4e0603551010bd0e50aa8" + integrity sha512-mvBSwIBUeiRscrCeJE1LwctAriBj65eUDm0Pc11iE5gRwzkmsdbS7FnZ1XUWjpSeQWL1L5g12Fc/SchPM9DUOw== + dependencies: + commander "^5.0.0" + glob "^7.1.6" + minimatch "^3.0.4" + "@electron/get@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" @@ -238,125 +223,130 @@ optionalDependencies: global-agent "^3.0.0" -"@esbuild/aix-ppc64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz#509621cca4e67caf0d18561a0c56f8b70237472f" - integrity sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw== - -"@esbuild/android-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz#109a6fdc4a2783fc26193d2687827045d8fef5ab" - integrity sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q== - -"@esbuild/android-arm@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.0.tgz#1397a2c54c476c4799f9b9073550ede496c94ba5" - integrity sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g== - -"@esbuild/android-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.0.tgz#2b615abefb50dc0a70ac313971102f4ce2fdb3ca" - integrity sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ== - -"@esbuild/darwin-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz#5c122ed799eb0c35b9d571097f77254964c276a2" - integrity sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ== - -"@esbuild/darwin-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz#9561d277002ba8caf1524f209de2b22e93d170c1" - integrity sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw== - -"@esbuild/freebsd-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz#84178986a3138e8500d17cc380044868176dd821" - integrity sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ== - -"@esbuild/freebsd-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz#3f9ce53344af2f08d178551cd475629147324a83" - integrity sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ== - -"@esbuild/linux-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz#24efa685515689df4ecbc13031fa0a9dda910a11" - integrity sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw== - -"@esbuild/linux-arm@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz#6b586a488e02e9b073a75a957f2952b3b6e87b4c" - integrity sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg== - -"@esbuild/linux-ia32@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz#84ce7864f762708dcebc1b123898a397dea13624" - integrity sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w== - -"@esbuild/linux-loong64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz#1922f571f4cae1958e3ad29439c563f7d4fd9037" - integrity sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw== - -"@esbuild/linux-mips64el@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz#7ca1bd9df3f874d18dbf46af009aebdb881188fe" - integrity sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ== - -"@esbuild/linux-ppc64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz#8f95baf05f9486343bceeb683703875d698708a4" - integrity sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw== - -"@esbuild/linux-riscv64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz#ca63b921d5fe315e28610deb0c195e79b1a262ca" - integrity sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA== - -"@esbuild/linux-s390x@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz#cb3d069f47dc202f785c997175f2307531371ef8" - integrity sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ== - -"@esbuild/linux-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz#ac617e0dc14e9758d3d7efd70288c14122557dc7" - integrity sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg== - -"@esbuild/netbsd-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz#6cc778567f1513da6e08060e0aeb41f82eb0f53c" - integrity sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ== - -"@esbuild/openbsd-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz#76848bcf76b4372574fb4d06cd0ed1fb29ec0fbe" - integrity sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA== - -"@esbuild/sunos-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz#ea4cd0639bf294ad51bc08ffbb2dac297e9b4706" - integrity sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g== - -"@esbuild/win32-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz#a5c171e4a7f7e4e8be0e9947a65812c1535a7cf0" - integrity sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ== - -"@esbuild/win32-ia32@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz#f8ac5650c412d33ea62d7551e0caf82da52b7f85" - integrity sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg== - -"@esbuild/win32-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz#2efddf82828aac85e64cef62482af61c29561bee" - integrity sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg== - -"@malept/cross-spawn-promise@^1.1.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz#504af200af6b98e198bce768bc1730c6936ae01d" - integrity sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ== +"@esbuild/aix-ppc64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz#145b74d5e4a5223489cabdc238d8dad902df5259" + integrity sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ== + +"@esbuild/android-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz#453bbe079fc8d364d4c5545069e8260228559832" + integrity sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ== + +"@esbuild/android-arm@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.0.tgz#26c806853aa4a4f7e683e519cd9d68e201ebcf99" + integrity sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g== + +"@esbuild/android-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.0.tgz#1e51af9a6ac1f7143769f7ee58df5b274ed202e6" + integrity sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ== + +"@esbuild/darwin-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz#d996187a606c9534173ebd78c58098a44dd7ef9e" + integrity sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow== + +"@esbuild/darwin-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz#30c8f28a7ef4e32fe46501434ebe6b0912e9e86c" + integrity sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ== + +"@esbuild/freebsd-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz#30f4fcec8167c08a6e8af9fc14b66152232e7fb4" + integrity sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw== + +"@esbuild/freebsd-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz#1003a6668fe1f5d4439e6813e5b09a92981bc79d" + integrity sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ== + +"@esbuild/linux-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz#3b9a56abfb1410bb6c9138790f062587df3e6e3a" + integrity sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw== + +"@esbuild/linux-arm@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz#237a8548e3da2c48cd79ae339a588f03d1889aad" + integrity sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw== + +"@esbuild/linux-ia32@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz#4269cd19cb2de5de03a7ccfc8855dde3d284a238" + integrity sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA== + +"@esbuild/linux-loong64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz#82b568f5658a52580827cc891cb69d2cb4f86280" + integrity sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A== + +"@esbuild/linux-mips64el@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz#9a57386c926262ae9861c929a6023ed9d43f73e5" + integrity sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w== + +"@esbuild/linux-ppc64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz#f3a79fd636ba0c82285d227eb20ed8e31b4444f6" + integrity sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw== + +"@esbuild/linux-riscv64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz#f9d2ef8356ce6ce140f76029680558126b74c780" + integrity sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw== + +"@esbuild/linux-s390x@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz#45390f12e802201f38a0229e216a6aed4351dfe8" + integrity sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg== + +"@esbuild/linux-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz#c8409761996e3f6db29abcf9b05bee8d7d80e910" + integrity sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ== + +"@esbuild/netbsd-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz#ba70db0114380d5f6cfb9003f1d378ce989cd65c" + integrity sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw== + +"@esbuild/openbsd-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz#72fc55f0b189f7a882e3cf23f332370d69dfd5db" + integrity sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ== + +"@esbuild/openbsd-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz#b6ae7a0911c18fe30da3db1d6d17a497a550e5d8" + integrity sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg== + +"@esbuild/sunos-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz#58f0d5e55b9b21a086bfafaa29f62a3eb3470ad8" + integrity sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA== + +"@esbuild/win32-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz#b858b2432edfad62e945d5c7c9e5ddd0f528ca6d" + integrity sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ== + +"@esbuild/win32-ia32@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz#167ef6ca22a476c6c0c014a58b4f43ae4b80dec7" + integrity sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA== + +"@esbuild/win32-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz#db44a6a08520b5f25bbe409f34a59f2d4bcc7ced" + integrity sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g== + +"@malept/cross-spawn-promise@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz#d0772de1aa680a0bfb9ba2f32b4c828c7857cb9d" + integrity sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg== dependencies: cross-spawn "^7.0.1" @@ -493,6 +483,14 @@ dependencies: "@types/node" "*" +"@types/gulp-sort@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/gulp-sort/-/gulp-sort-2.0.4.tgz#60625bf608dbac8f03644c6785d25c616f1b7d8c" + integrity sha512-HUHxH+oMox1ct0SnxPqCXBni0MSws1ygcSAoLO4ISRmR/UuvNIz40rgNveZxwxQk+p78kw09z/qKQkgKJmNUOQ== + dependencies: + "@types/gulp-util" "*" + "@types/node" "*" + "@types/gulp-sourcemaps@^0.0.32": version "0.0.32" resolved "https://registry.yarnpkg.com/@types/gulp-sourcemaps/-/gulp-sourcemaps-0.0.32.tgz#e79ee617e0cb15729874be4533fe59c07793a175" @@ -500,6 +498,16 @@ dependencies: "@types/node" "*" +"@types/gulp-util@*": + version "3.0.41" + resolved "https://registry.yarnpkg.com/@types/gulp-util/-/gulp-util-3.0.41.tgz#52868a6f8b6af55a099fe48ea20736833c02bed4" + integrity sha512-BK0kJZ8euQNlISsmD6mBr/1RZkB0mljdtBsz2usv+QHPV10alH2AJw5p05S9LU6S+VdTjbFmGU0OxpH++2W9/Q== + dependencies: + "@types/node" "*" + "@types/through2" "*" + "@types/vinyl" "*" + chalk "^2.2.0" + "@types/gulp@^4.0.17": version "4.0.17" resolved "https://registry.yarnpkg.com/@types/gulp/-/gulp-4.0.17.tgz#b314c3762d08d8d69b7c0b86f78d069bafd65009" @@ -601,6 +609,13 @@ "@types/glob" "*" "@types/node" "*" +"@types/through2@*": + version "2.0.41" + resolved "https://registry.yarnpkg.com/@types/through2/-/through2-2.0.41.tgz#3e5e1720d71ffdfa03c22f2aad6593d12a47034f" + integrity sha512-ryQ0tidWkb1O1JuYvWKyMLYEtOWDqF5mHerJzKz/gQpoAaJq2l/dsMPBF0B5BNVT34rbARYJ5/tsZwLfUi2kwQ== + dependencies: + "@types/node" "*" + "@types/through2@^2.0.36": version "2.0.36" resolved "https://registry.yarnpkg.com/@types/through2/-/through2-2.0.36.tgz#35fda0db635827d44c0e08e2c94653e647574a00" @@ -710,6 +725,11 @@ optionalDependencies: keytar "^7.7.0" +"@xmldom/xmldom@^0.8.8": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" + integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -743,13 +763,6 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" @@ -786,18 +799,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -asar@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/asar/-/asar-3.0.3.tgz#1fef03c2d6d2de0cbad138788e4f7ae03b129c7b" - integrity sha512-k7zd+KoR+n8pl71PvgElcoKHrVNiSXtw7odKbyNpmgKe7EGRF9Pnu3uLOukD37EvavKwVFxOUpqXTIZC5B5Pmw== - dependencies: - chromium-pickle-js "^0.2.0" - commander "^5.0.0" - glob "^7.1.6" - minimatch "^3.0.4" - optionalDependencies: - "@types/glob" "^7.1.1" - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -818,11 +819,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - azure-devops-node-api@^11.0.1: version "11.2.0" resolved "https://registry.yarnpkg.com/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz#bf04edbef60313117a0507415eed4790a420ad6b" @@ -878,12 +874,19 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" buffer-alloc-unsafe@^1.1.0: version "1.1.0" @@ -908,11 +911,6 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= -buffer-equal@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" - integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= - buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" @@ -957,7 +955,7 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.2" -chalk@^2.4.2: +chalk@^2.2.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -966,14 +964,6 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - cheerio-select@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" @@ -1034,11 +1024,6 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -chromium-pickle-js@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" - integrity sha1-BKEGZywYsIWrd02YPfo+oTjyIgU= - clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" @@ -1077,33 +1062,16 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -colors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= - colors@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -1116,13 +1084,6 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q= - dependencies: - graceful-readlink ">= 1.0.0" - commander@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" @@ -1236,15 +1197,13 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -dir-compare@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-2.4.0.tgz#785c41dc5f645b34343a4eafc50b79bac7f11631" - integrity sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA== +dir-compare@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-4.2.0.tgz#d1d4999c14fbf55281071fdae4293b3b9ce86f19" + integrity sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ== dependencies: - buffer-equal "1.0.0" - colors "1.0.3" - commander "2.9.0" - minimatch "3.0.4" + minimatch "^3.0.5" + p-limit "^3.1.0 " dom-serializer@^2.0.0: version "2.0.0" @@ -1332,34 +1291,35 @@ es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild@0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.0.tgz#a7170b63447286cd2ff1f01579f09970e6965da4" - integrity sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA== +esbuild@0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.0.tgz#de06002d48424d9fdb7eb52dbe8e95927f852599" + integrity sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA== optionalDependencies: - "@esbuild/aix-ppc64" "0.20.0" - "@esbuild/android-arm" "0.20.0" - "@esbuild/android-arm64" "0.20.0" - "@esbuild/android-x64" "0.20.0" - "@esbuild/darwin-arm64" "0.20.0" - "@esbuild/darwin-x64" "0.20.0" - "@esbuild/freebsd-arm64" "0.20.0" - "@esbuild/freebsd-x64" "0.20.0" - "@esbuild/linux-arm" "0.20.0" - "@esbuild/linux-arm64" "0.20.0" - "@esbuild/linux-ia32" "0.20.0" - "@esbuild/linux-loong64" "0.20.0" - "@esbuild/linux-mips64el" "0.20.0" - "@esbuild/linux-ppc64" "0.20.0" - "@esbuild/linux-riscv64" "0.20.0" - "@esbuild/linux-s390x" "0.20.0" - "@esbuild/linux-x64" "0.20.0" - "@esbuild/netbsd-x64" "0.20.0" - "@esbuild/openbsd-x64" "0.20.0" - "@esbuild/sunos-x64" "0.20.0" - "@esbuild/win32-arm64" "0.20.0" - "@esbuild/win32-ia32" "0.20.0" - "@esbuild/win32-x64" "0.20.0" + "@esbuild/aix-ppc64" "0.23.0" + "@esbuild/android-arm" "0.23.0" + "@esbuild/android-arm64" "0.23.0" + "@esbuild/android-x64" "0.23.0" + "@esbuild/darwin-arm64" "0.23.0" + "@esbuild/darwin-x64" "0.23.0" + "@esbuild/freebsd-arm64" "0.23.0" + "@esbuild/freebsd-x64" "0.23.0" + "@esbuild/linux-arm" "0.23.0" + "@esbuild/linux-arm64" "0.23.0" + "@esbuild/linux-ia32" "0.23.0" + "@esbuild/linux-loong64" "0.23.0" + "@esbuild/linux-mips64el" "0.23.0" + "@esbuild/linux-ppc64" "0.23.0" + "@esbuild/linux-riscv64" "0.23.0" + "@esbuild/linux-s390x" "0.23.0" + "@esbuild/linux-x64" "0.23.0" + "@esbuild/netbsd-x64" "0.23.0" + "@esbuild/openbsd-arm64" "0.23.0" + "@esbuild/openbsd-x64" "0.23.0" + "@esbuild/sunos-x64" "0.23.0" + "@esbuild/win32-arm64" "0.23.0" + "@esbuild/win32-ia32" "0.23.0" + "@esbuild/win32-x64" "0.23.0" escape-string-regexp@^1.0.5: version "1.0.5" @@ -1422,10 +1382,10 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -1464,6 +1424,15 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-extra@^11.1.1: + version "11.2.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -1473,16 +1442,6 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1601,11 +1560,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= - gulp-merge-json@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/gulp-merge-json/-/gulp-merge-json-2.1.1.tgz#cfb1d066467577545b8c1c289a278e6ef4b4e0de" @@ -1617,28 +1571,18 @@ gulp-merge-json@^2.1.1: through "^2.3.8" vinyl "^2.1.0" -gulp-shell@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/gulp-shell/-/gulp-shell-0.8.0.tgz#0ed4980de1d0c67e5f6cce971d7201fd0be50555" - integrity sha512-wHNCgmqbWkk1c6Gc2dOL5SprcoeujQdeepICwfQRo91DIylTE7a794VEE+leq3cE2YDoiS5ulvRfKVIEMazcTQ== +gulp-sort@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/gulp-sort/-/gulp-sort-2.0.0.tgz#c6762a2f1f0de0a3fc595a21599d3fac8dba1aca" + integrity sha512-MyTel3FXOdh1qhw1yKhpimQrAmur9q1X0ZigLmCOxouQD+BD3za9/89O+HfbgBQvvh4igEbp0/PUWO+VqGYG1g== dependencies: - chalk "^3.0.0" - fancy-log "^1.3.3" - lodash.template "^4.5.0" - plugin-error "^1.0.1" - through2 "^3.0.1" - tslib "^1.10.0" + through2 "^2.0.1" has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -1917,31 +1861,11 @@ linkify-it@^3.0.1: dependencies: uc.micro "^1.0.1" -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - lodash.mergewith@^4.6.1: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== -lodash.template@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -2014,19 +1938,26 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@^3.0.3, minimatch@^3.0.5, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.3, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@^9.0.3: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" minimist@^1.2.0, minimist@^1.2.3: version "1.2.6" @@ -2150,6 +2081,13 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== +"p-limit@^3.1.0 ": + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + parse-node-version@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" @@ -2215,6 +2153,15 @@ plist@^3.0.1: base64-js "^1.5.1" xmlbuilder "^9.0.7" +plist@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" + integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== + dependencies: + "@xmldom/xmldom" "^0.8.8" + base64-js "^1.5.1" + xmlbuilder "^15.1.1" + plugin-error@1.0.1, plugin-error@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" @@ -2340,6 +2287,19 @@ readable-stream@^2.0.2, readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdirp@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" @@ -2559,13 +2519,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - tar-fs@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -2597,6 +2550,14 @@ ternary-stream@^3.0.0: merge-stream "^2.0.0" through2 "^3.0.1" +through2@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + through2@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" @@ -2657,20 +2618,10 @@ tree-sitter@^0.20.5, tree-sitter@^0.20.6: nan "^2.18.0" prebuild-install "^7.1.1" -tslib@^1.10.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" - integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== - -tslib@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tslib@^2.0.0, tslib@^2.2.0, tslib@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== tunnel-agent@^0.6.0: version "0.6.0" @@ -2783,16 +2734,18 @@ vscode-gulp-watch@^5.0.3: vinyl "^2.2.0" vinyl-file "^3.0.0" -vscode-universal-bundler@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/vscode-universal-bundler/-/vscode-universal-bundler-0.0.2.tgz#2c988dac681d3ffe6baec6defac0995cb833c55a" - integrity sha512-FPJcvKnQGBqFzy6M6Nm2yvAczNLUeXsfYM6GwCex/pUOkvIM2icIHmiSvtMJINlLW1iG+oEwE3/LVbABmcjEmQ== +vscode-universal-bundler@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/vscode-universal-bundler/-/vscode-universal-bundler-0.1.3.tgz#bd223e61ca0ed1d8ebb1c023d5ad99aab92c3d35" + integrity sha512-USXJqgD+ySROqgjl+KrGjlF7MvNnqsI6my1kUOsAXARmNqBjXk38w8g2BF6PEXVOYOQ+npRcTA7athXNU4Jjog== dependencies: - "@malept/cross-spawn-promise" "^1.1.0" - asar "^3.0.3" + "@electron/asar" "^3.2.7" + "@malept/cross-spawn-promise" "^2.0.0" debug "^4.3.1" - dir-compare "^2.4.0" - fs-extra "^9.0.1" + dir-compare "^4.2.0" + fs-extra "^11.1.1" + minimatch "^9.0.3" + plist "^3.1.0" webidl-conversions@^3.0.0: version "3.0.1" @@ -2832,6 +2785,11 @@ xml2js@^0.4.19, xml2js@^0.4.23: sax ">=0.6.0" xmlbuilder "~11.0.0" +xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== + xmlbuilder@^9.0.7: version "9.0.7" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" @@ -2842,6 +2800,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" @@ -2861,3 +2824,8 @@ yazl@^2.2.2: integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== dependencies: buffer-crc32 "~0.2.3" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/patched-vscode/cglicenses.json b/patched-vscode/cglicenses.json index d90d55d7..1919c9d2 100644 --- a/patched-vscode/cglicenses.json +++ b/patched-vscode/cglicenses.json @@ -40,12 +40,6 @@ "Copyright (c) Microsoft Corporation. All rights reserved." ] }, - { - "name": "big-integer", - "prependLicenseText": [ - "Copyright released to public domain" - ] - }, { // Reason: The license cannot be found by the tool due to access controls on the repository "name": "vscode-tas-client", @@ -567,6 +561,32 @@ "SOFTWARE" ] }, + { + "name":"vscode-markdown-languageserver", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) Microsoft Corporation.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE" + ] + }, { // Reason: mono-repo where the individual packages are also dual-licensed under MIT and Apache-2.0 "name": "system-configuration", diff --git a/patched-vscode/cgmanifest.json b/patched-vscode/cgmanifest.json index 61747342..dcb4791c 100644 --- a/patched-vscode/cgmanifest.json +++ b/patched-vscode/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "f1a45d7ded05d64ca8136cc142ddc0c271b1dd43" + "commitHash": "7fa4f6e14e0707c0e604cf7c1da33566e78169ce" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "122.0.6261.156" + "version": "124.0.6367.243" }, { "component": { @@ -48,7 +48,7 @@ "git": { "name": "ffmpeg", "repositoryUrl": "https://chromium.googlesource.com/chromium/third_party/ffmpeg", - "commitHash": "17525de887d54b970ffdd421a0879c1db1952307" + "commitHash": "52d8ef3799b2f16b66351dd0972bb0bcee1648ac" } }, "isOnlyProductionDependency": true, @@ -516,11 +516,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "9b1bf44ea9e7785e38c93b7d22d32dbca262df6c" + "commitHash": "a407d1f0b3669cc82c755700f0d500fb27cc39ea" } }, "isOnlyProductionDependency": true, - "version": "20.11.1" + "version": "20.15.1" }, { "component": { @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "f9ed0eaee4b172733872c2f84e5061882dd08e5c" + "commitHash": "ff3d3e69443c1c8939c9ba2d10d40cb65f3ff278" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "29.4.0" + "version": "30.4.0" }, { "component": { @@ -732,6 +732,39 @@ "SOFTWARE." ], "license": "MIT" + }, + { + "component": { + "type": "npm", + "npm": { + "name": "cacheable-request", + "version": "7.0.4" + } + }, + "licenseDetail": [ + "Copyright (c) cacheable-request authors", + "", + "MIT License", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to", + "deal in the Software without restriction, including without limitation the", + "rights to use, copy, modify, merge, publish, distribute, sublicense, and/or", + "sell copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING", + "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER", + "DEALINGS IN THE SOFTWARE." + ], + "license": "MIT" } ], "version": 1 diff --git a/patched-vscode/cli/Cargo.lock b/patched-vscode/cli/Cargo.lock index 3be3815a..27fe7989 100644 --- a/patched-vscode/cli/Cargo.lock +++ b/patched-vscode/cli/Cargo.lock @@ -1571,9 +1571,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.5.0", "cfg-if", @@ -1603,9 +1603,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", diff --git a/patched-vscode/cli/ThirdPartyNotices.txt b/patched-vscode/cli/ThirdPartyNotices.txt index 7a74fb50..4a58801f 100644 --- a/patched-vscode/cli/ThirdPartyNotices.txt +++ b/patched-vscode/cli/ThirdPartyNotices.txt @@ -22,31 +22,16 @@ required to debug changes to any libraries licensed under the GNU Lesser General addr2line 0.21.0 - Apache-2.0 OR MIT https://github.com/gimli-rs/addr2line -Copyright (c) 2016-2018 The gimli Developers +Licensed under either of -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: + * Apache License, Version 2.0 ([`LICENSE-APACHE`](./LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([`LICENSE-MIT`](./LICENSE-MIT) or https://opensource.org/licenses/MIT) -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. +at your option. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. --------------------------------------------------------- --------------------------------------------------------- @@ -292,27 +277,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. async-broadcast 0.5.1 - MIT OR Apache-2.0 https://github.com/smol-rs/async-broadcast -The MIT License (MIT) - -Copyright (c) 2020 Yoshua Wuyts - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + +Licensed under either of Apache License, Version +2.0 (LICENSE-APACHE) or MIT license (LICENSE-MIT) at your option. + -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + --------------------------------------------------------- --------------------------------------------------------- @@ -320,29 +296,18 @@ SOFTWARE. async-channel 2.3.1 - Apache-2.0 OR MIT https://github.com/smol-rs/async-channel -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: +Licensed under either of -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +at your option. + +#### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. --------------------------------------------------------- --------------------------------------------------------- @@ -382,29 +347,18 @@ async-lock 2.8.0 - Apache-2.0 OR MIT async-lock 3.3.0 - Apache-2.0 OR MIT https://github.com/smol-rs/async-lock -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: +Licensed under either of -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +at your option. + +#### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. --------------------------------------------------------- --------------------------------------------------------- @@ -412,29 +366,18 @@ DEALINGS IN THE SOFTWARE. async-process 1.8.1 - Apache-2.0 OR MIT https://github.com/smol-rs/async-process -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: +Licensed under either of -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +at your option. + +#### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. --------------------------------------------------------- --------------------------------------------------------- @@ -442,29 +385,13 @@ DEALINGS IN THE SOFTWARE. async-recursion 1.1.1 - MIT OR Apache-2.0 https://github.com/dcchut/async-recursion -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. +Licensed under either of + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or ) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or ) -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +at your option. --------------------------------------------------------- --------------------------------------------------------- @@ -502,29 +429,18 @@ DEALINGS IN THE SOFTWARE. async-task 4.7.1 - Apache-2.0 OR MIT https://github.com/smol-rs/async-task -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: +Licensed under either of -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +at your option. + +#### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. --------------------------------------------------------- --------------------------------------------------------- @@ -1341,7 +1257,7 @@ SOFTWARE. --------------------------------------------------------- clap_derive 4.5.4 - MIT OR Apache-2.0 -https://github.com/clap-rs/clap/tree/master/clap_derive +https://github.com/clap-rs/clap Copyright (c) Individual contributors @@ -1367,7 +1283,7 @@ SOFTWARE. --------------------------------------------------------- clap_lex 0.7.0 - MIT OR Apache-2.0 -https://github.com/clap-rs/clap/tree/master/clap_lex +https://github.com/clap-rs/clap Copyright (c) Individual contributors @@ -3396,7 +3312,7 @@ https://github.com/hyperium/http-body The MIT License (MIT) -Copyright (c) 2019 Hyper Contributors +Copyright (c) 2019-2024 Sean McArthur & Hyper Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -3428,7 +3344,7 @@ DEALINGS IN THE SOFTWARE. httparse 1.8.0 - MIT/Apache-2.0 https://github.com/seanmonstar/httparse -Copyright (c) 2015-2021 Sean McArthur +Copyright (c) 2015-2024 Sean McArthur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -4896,7 +4812,7 @@ OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -openssl 0.10.64 - Apache-2.0 +openssl 0.10.66 - Apache-2.0 https://github.com/sfackler/rust-openssl Copyright 2011-2017 Google Inc. @@ -4975,7 +4891,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -openssl-sys 0.9.102 - MIT +openssl-sys 0.9.103 - MIT https://github.com/sfackler/rust-openssl The MIT License (MIT) @@ -8507,6 +8423,7 @@ subtle 2.5.0 - BSD-3-Clause https://github.com/dalek-cryptography/subtle Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved. +Copyright (c) 2016-2024 Isis Agora Lovecruft. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10790,33 +10707,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI zbus 3.15.2 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10824,33 +10715,7 @@ DEALINGS IN THE SOFTWARE. zbus_macros 3.15.2 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10858,33 +10723,7 @@ DEALINGS IN THE SOFTWARE. zbus_names 2.6.1 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -10977,33 +10816,7 @@ licences; see files named LICENSE.*.txt for details. zvariant 3.15.2 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -11011,33 +10824,7 @@ DEALINGS IN THE SOFTWARE. zvariant_derive 3.15.2 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -11045,31 +10832,5 @@ DEALINGS IN THE SOFTWARE. zvariant_utils 1.0.1 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- \ No newline at end of file diff --git a/patched-vscode/cli/src/async_pipe.rs b/patched-vscode/cli/src/async_pipe.rs index e9b710c1..78aed6fe 100644 --- a/patched-vscode/cli/src/async_pipe.rs +++ b/patched-vscode/cli/src/async_pipe.rs @@ -227,7 +227,7 @@ impl hyper::server::accept::Accept for PollableAsyncListener { } } -/// Gets a random name for a pipe/socket on the paltform +/// Gets a random name for a pipe/socket on the platform pub fn get_socket_name() -> PathBuf { cfg_if::cfg_if! { if #[cfg(unix)] { diff --git a/patched-vscode/cli/src/auth.rs b/patched-vscode/cli/src/auth.rs index 67f1bfa6..2d9162c5 100644 --- a/patched-vscode/cli/src/auth.rs +++ b/patched-vscode/cli/src/auth.rs @@ -287,7 +287,7 @@ impl StorageImplementation for ThreadKeyringStorage { #[derive(Default)] struct KeyringStorage { - // keywring storage can be split into multiple entries due to entry length limits + // keyring storage can be split into multiple entries due to entry length limits // on Windows https://github.com/microsoft/vscode-cli/issues/358 entries: Vec, } diff --git a/patched-vscode/cli/src/commands/args.rs b/patched-vscode/cli/src/commands/args.rs index 79c4d376..101f1eac 100644 --- a/patched-vscode/cli/src/commands/args.rs +++ b/patched-vscode/cli/src/commands/args.rs @@ -64,7 +64,7 @@ pub struct IntegratedCli { pub core: CliCore, } -/// Common CLI shared between intergated and standalone interfaces. +/// Common CLI shared between integrated and standalone interfaces. #[derive(Args, Debug, Default, Clone)] pub struct CliCore { /// One or more files, folders, or URIs to open. @@ -230,8 +230,8 @@ pub struct CommandShellArgs { #[clap(long)] pub on_socket: bool, /// Listen on a host/port instead of stdin/stdout. - #[clap(long, num_args = 0..=1, default_missing_value = "0")] - pub on_port: Option, + #[clap(long, num_args = 0..=2, default_missing_value = "0")] + pub on_port: Vec, /// Listen on a host/port instead of stdin/stdout. #[clap[long]] pub on_host: Option, @@ -619,7 +619,7 @@ pub enum OutputFormat { #[derive(Args, Clone, Debug, Default)] pub struct ExistingTunnelArgs { /// Name you'd like to assign preexisting tunnel to use to connect the tunnel - /// Old option, new code sohuld just use `--name`. + /// Old option, new code should just use `--name`. #[clap(long, hide = true)] pub tunnel_name: Option, diff --git a/patched-vscode/cli/src/commands/serve_web.rs b/patched-vscode/cli/src/commands/serve_web.rs index fba92723..d8d2a49b 100644 --- a/patched-vscode/cli/src/commands/serve_web.rs +++ b/patched-vscode/cli/src/commands/serve_web.rs @@ -12,7 +12,6 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; -use const_format::concatcp; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Request, Response, Server}; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -56,16 +55,9 @@ const RELEASE_CACHE_SECS: u64 = 60 * 60; /// Number of bytes for the secret keys. See workbench.ts for their usage. const SECRET_KEY_BYTES: usize = 32; /// Path to mint the key combining server and client parts. -const SECRET_KEY_MINT_PATH: &str = "/_vscode-cli/mint-key"; +const SECRET_KEY_MINT_PATH: &str = "_vscode-cli/mint-key"; /// Cookie set to the `SECRET_KEY_MINT_PATH` const PATH_COOKIE_NAME: &str = "vscode-secret-key-path"; -/// Cookie set to the `SECRET_KEY_MINT_PATH` -const PATH_COOKIE_VALUE: &str = concatcp!( - PATH_COOKIE_NAME, - "=", - SECRET_KEY_MINT_PATH, - "; SameSite=Strict; Path=/" -); /// HTTP-only cookie where the client's secret half is stored. const SECRET_KEY_COOKIE_NAME: &str = "vscode-cli-secret-half"; @@ -79,13 +71,19 @@ pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result) -> Result, Infallible> { let client_key_half = get_client_key_half(&req); - let mut res = match req.uri().path() { - SECRET_KEY_MINT_PATH => handle_secret_mint(ctx, req), - _ => handle_proxied(ctx, req).await, + let path = req.uri().path(); + + let mut res = if path.starts_with(&ctx.cm.base_path) + && path.get(ctx.cm.base_path.len()..).unwrap_or_default() == SECRET_KEY_MINT_PATH + { + handle_secret_mint(&ctx, req) + } else { + handle_proxied(&ctx, req).await }; - append_secret_headers(&mut res, &client_key_half); + append_secret_headers(&ctx.cm.base_path, &mut res, &client_key_half); Ok(res) } -async fn handle_proxied(ctx: HandleContext, req: Request) -> Response { +async fn handle_proxied(ctx: &HandleContext, req: Request) -> Response { let release = if let Some((r, _)) = get_release_from_path(req.uri().path(), ctx.cm.platform) { r } else { @@ -194,7 +197,7 @@ async fn handle_proxied(ctx: HandleContext, req: Request) -> Response) -> Response { +fn handle_secret_mint(ctx: &HandleContext, req: Request) -> Response { use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); @@ -208,11 +211,20 @@ fn handle_secret_mint(ctx: HandleContext, req: Request) -> Response /// Appends headers to response to maintain the secret storage of the workbench: /// sets the `PATH_COOKIE_VALUE` so workbench.ts knows about the 'mint' endpoint, /// and maintains the http-only cookie the client will use for cookies. -fn append_secret_headers(res: &mut Response, client_key_half: &SecretKeyPart) { +fn append_secret_headers( + base_path: &str, + res: &mut Response, + client_key_half: &SecretKeyPart, +) { let headers = res.headers_mut(); headers.append( hyper::header::SET_COOKIE, - PATH_COOKIE_VALUE.parse().unwrap(), + format!( + "{}={}{}; SameSite=Strict; Path=/", + PATH_COOKIE_NAME, base_path, SECRET_KEY_MINT_PATH, + ) + .parse() + .unwrap(), ); headers.append( hyper::header::SET_COOKIE, @@ -496,6 +508,8 @@ struct ConnectionManager { pub platform: Platform, pub log: log::Logger, args: ServeWebArgs, + /// Server base path, ending in `/` + base_path: String, /// Cache where servers are stored cache: DownloadCache, /// Mapping of (Quality, Commit) to the state each server is in @@ -510,11 +524,24 @@ fn key_for_release(release: &Release) -> (Quality, String) { (release.quality, release.commit.clone()) } +fn normalize_base_path(p: &str) -> String { + let p = p.trim_matches('/'); + + if p.is_empty() { + return "/".to_string(); + } + + format!("/{}/", p.trim_matches('/')) +} + impl ConnectionManager { pub fn new(ctx: &CommandContext, platform: Platform, args: ServeWebArgs) -> Arc { + let base_path = normalize_base_path(args.server_base_path.as_deref().unwrap_or_default()); + Arc::new(Self { platform, args, + base_path, log: ctx.log.clone(), cache: DownloadCache::new(ctx.paths.web_server_storage()), update_service: UpdateService::new( diff --git a/patched-vscode/cli/src/commands/tunnels.rs b/patched-vscode/cli/src/commands/tunnels.rs index 688f603f..2d0014bc 100644 --- a/patched-vscode/cli/src/commands/tunnels.rs +++ b/patched-vscode/cli/src/commands/tunnels.rs @@ -155,43 +155,52 @@ pub async fn command_shell(ctx: CommandContext, args: CommandShellArgs) -> Resul code_server_args: (&ctx.args).into(), }; - let mut listener: Box = match (args.on_port, &args.on_host, args.on_socket) - { - (_, _, true) => { - let socket = get_socket_name(); - let listener = listen_socket_rw_stream(&socket) - .await - .map_err(|e| wrap(e, "error listening on socket"))?; - - params - .log - .result(format!("Listening on {}", socket.display())); - - Box::new(listener) - } - (Some(_), _, _) | (_, Some(_), _) => { - let addr = SocketAddr::new( - args.on_host + let mut listener: Box = + match (args.on_port.first(), &args.on_host, args.on_socket) { + (_, _, true) => { + let socket = get_socket_name(); + let listener = listen_socket_rw_stream(&socket) + .await + .map_err(|e| wrap(e, "error listening on socket"))?; + + params + .log + .result(format!("Listening on {}", socket.display())); + + Box::new(listener) + } + (Some(_), _, _) | (_, Some(_), _) => { + let host = args + .on_host .as_ref() .map(|h| h.parse().map_err(CodeError::InvalidHostAddress)) - .unwrap_or(Ok(IpAddr::V4(Ipv4Addr::LOCALHOST)))?, - args.on_port.unwrap_or_default(), - ); - let listener = tokio::net::TcpListener::bind(addr) - .await - .map_err(|e| wrap(e, "error listening on port"))?; - - params - .log - .result(format!("Listening on {}", listener.local_addr().unwrap())); - - Box::new(listener) - } - _ => { - serve_stream(tokio::io::stdin(), tokio::io::stderr(), params).await; - return Ok(0); - } - }; + .unwrap_or(Ok(IpAddr::V4(Ipv4Addr::LOCALHOST)))?; + + let lower_port = args.on_port.first().copied().unwrap_or_default(); + let port_no = if let Some(upper) = args.on_port.get(1) { + find_unused_port(&host, lower_port, *upper) + .await + .unwrap_or_default() + } else { + lower_port + }; + + let addr = SocketAddr::new(host, port_no); + let listener = tokio::net::TcpListener::bind(addr) + .await + .map_err(|e| wrap(e, "error listening on port"))?; + + params + .log + .result(format!("Listening on {}", listener.local_addr().unwrap())); + + Box::new(listener) + } + _ => { + serve_stream(tokio::io::stdin(), tokio::io::stderr(), params).await; + return Ok(0); + } + }; let mut servers = FuturesUnordered::new(); @@ -216,6 +225,21 @@ pub async fn command_shell(ctx: CommandContext, args: CommandShellArgs) -> Resul } } +async fn find_unused_port(host: &IpAddr, start_port: u16, end_port: u16) -> Option { + for port in start_port..=end_port { + if is_port_available(*host, port).await { + return Some(port); + } + } + None +} + +async fn is_port_available(host: IpAddr, port: u16) -> bool { + tokio::net::TcpListener::bind(SocketAddr::new(host, port)) + .await + .is_ok() +} + pub async fn service( ctx: CommandContext, service_args: TunnelServiceSubCommands, diff --git a/patched-vscode/cli/src/constants.rs b/patched-vscode/cli/src/constants.rs index 6f604e88..1e277a89 100644 --- a/patched-vscode/cli/src/constants.rs +++ b/patched-vscode/cli/src/constants.rs @@ -13,7 +13,7 @@ use crate::options::Quality; pub const CONTROL_PORT: u16 = 31545; -/// Protocol version sent to clients. This can be used to indiciate new or +/// Protocol version sent to clients. This can be used to indicate new or /// changed capabilities that clients may wish to leverage. /// 1 - Initial protocol version /// 2 - Addition of `serve.compressed` property to control whether servermsg's diff --git a/patched-vscode/cli/src/tunnels/control_server.rs b/patched-vscode/cli/src/tunnels/control_server.rs index f42984cf..dfb5e381 100644 --- a/patched-vscode/cli/src/tunnels/control_server.rs +++ b/patched-vscode/cli/src/tunnels/control_server.rs @@ -920,9 +920,14 @@ async fn handle_update( info!(log, "Updating CLI to {}", latest_release); - updater + let r = updater .do_update(&latest_release, SilentCopyProgress()) - .await?; + .await; + + if let Err(e) = r { + did_update.store(false, Ordering::SeqCst); + return Err(e); + } Ok(UpdateResult { up_to_date: true, diff --git a/patched-vscode/cli/src/tunnels/dev_tunnels.rs b/patched-vscode/cli/src/tunnels/dev_tunnels.rs index 19ee3c2b..a964b446 100644 --- a/patched-vscode/cli/src/tunnels/dev_tunnels.rs +++ b/patched-vscode/cli/src/tunnels/dev_tunnels.rs @@ -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. *--------------------------------------------------------------------------------------------*/ -use super::protocol::{self, PortPrivacy}; +use super::protocol::{self, PortPrivacy, PortProtocol}; use crate::auth; use crate::constants::{IS_INTERACTIVE_CLI, PROTOCOL_VERSION_TAG, TUNNEL_SERVICE_USER_AGENT}; use crate::state::{LauncherPaths, PersistedState}; @@ -221,8 +221,11 @@ impl ActiveTunnel { &self, port_number: u16, privacy: PortPrivacy, + protocol: PortProtocol, ) -> Result<(), AnyError> { - self.manager.add_port_tcp(port_number, privacy).await?; + self.manager + .add_port_tcp(port_number, privacy, protocol) + .await?; Ok(()) } @@ -972,13 +975,14 @@ impl ActiveTunnelManager { &self, port_number: u16, privacy: PortPrivacy, + protocol: PortProtocol, ) -> Result<(), WrappedError> { self.relay .lock() .await .add_port(&TunnelPort { port_number, - protocol: Some(TUNNEL_PROTOCOL_AUTO.to_owned()), + protocol: Some(protocol.to_contract_str().to_string()), access_control: Some(privacy_to_tunnel_acl(privacy)), ..Default::default() }) diff --git a/patched-vscode/cli/src/tunnels/local_forwarding.rs b/patched-vscode/cli/src/tunnels/local_forwarding.rs index e6410860..93c2d244 100644 --- a/patched-vscode/cli/src/tunnels/local_forwarding.rs +++ b/patched-vscode/cli/src/tunnels/local_forwarding.rs @@ -27,7 +27,7 @@ use super::{ protocol::{ self, forward_singleton::{PortList, SetPortsResponse}, - PortPrivacy, + PortPrivacy, PortProtocol, }, shutdown_signal::ShutdownSignal, }; @@ -71,8 +71,13 @@ impl PortCount { } } } +#[derive(Clone)] +struct PortMapRec { + count: PortCount, + protocol: PortProtocol, +} -type PortMap = HashMap; +type PortMap = HashMap; /// The PortForwardingHandle is given out to multiple consumers to allow /// them to set_ports that they want to be forwarded. @@ -99,8 +104,8 @@ impl PortForwardingSender { for p in current.iter() { if !ports.contains(p) { let n = v.get_mut(&p.number).expect("expected port in map"); - n[p.privacy] -= 1; - if n.is_empty() { + n.count[p.privacy] -= 1; + if n.count.is_empty() { v.remove(&p.number); } } @@ -110,12 +115,19 @@ impl PortForwardingSender { if !current.contains(p) { match v.get_mut(&p.number) { Some(n) => { - n[p.privacy] += 1; + n.count[p.privacy] += 1; + n.protocol = p.protocol; } None => { - let mut pc = PortCount::default(); - pc[p.privacy] += 1; - v.insert(p.number, pc); + let mut count = PortCount::default(); + count[p.privacy] += 1; + v.insert( + p.number, + PortMapRec { + count, + protocol: p.protocol, + }, + ); } }; } @@ -164,22 +176,34 @@ impl PortForwardingReceiver { while self.receiver.changed().await.is_ok() { let next = self.receiver.borrow().clone(); - for (port, count) in current.iter() { - let privacy = count.primary_privacy(); - if !matches!(next.get(port), Some(n) if n.primary_privacy() == privacy) { + for (port, rec) in current.iter() { + let privacy = rec.count.primary_privacy(); + if !matches!(next.get(port), Some(n) if n.count.primary_privacy() == privacy) { match tunnel.remove_port(*port).await { - Ok(_) => info!(log, "stopped forwarding port {} at {:?}", *port, privacy), - Err(e) => error!(log, "failed to stop forwarding port {}: {}", port, e), + Ok(_) => info!( + log, + "stopped forwarding {} port {} at {:?}", rec.protocol, *port, privacy + ), + Err(e) => error!( + log, + "failed to stop forwarding {} port {}: {}", rec.protocol, port, e + ), } } } - for (port, count) in next.iter() { - let privacy = count.primary_privacy(); - if !matches!(current.get(port), Some(n) if n.primary_privacy() == privacy) { - match tunnel.add_port_tcp(*port, privacy).await { - Ok(_) => info!(log, "forwarding port {} at {:?}", port, privacy), - Err(e) => error!(log, "failed to forward port {}: {}", port, e), + for (port, rec) in next.iter() { + let privacy = rec.count.primary_privacy(); + if !matches!(current.get(port), Some(n) if n.count.primary_privacy() == privacy) { + match tunnel.add_port_tcp(*port, privacy, rec.protocol).await { + Ok(_) => info!( + log, + "forwarding {} port {} at {:?}", rec.protocol, port, privacy + ), + Err(e) => error!( + log, + "failed to forward {} port {}: {}", rec.protocol, port, e + ), } } } diff --git a/patched-vscode/cli/src/tunnels/port_forwarder.rs b/patched-vscode/cli/src/tunnels/port_forwarder.rs index 30267e8b..b05ae95a 100644 --- a/patched-vscode/cli/src/tunnels/port_forwarder.rs +++ b/patched-vscode/cli/src/tunnels/port_forwarder.rs @@ -12,7 +12,10 @@ use crate::{ util::errors::{AnyError, CannotForwardControlPort, ServerHasClosed}, }; -use super::{dev_tunnels::ActiveTunnel, protocol::PortPrivacy}; +use super::{ + dev_tunnels::ActiveTunnel, + protocol::{PortPrivacy, PortProtocol}, +}; pub enum PortForwardingRec { Forward(u16, PortPrivacy, oneshot::Sender>), @@ -89,7 +92,9 @@ impl PortForwardingProcessor { } if !self.forwarded.contains(&port) { - tunnel.add_port_tcp(port, privacy).await?; + tunnel + .add_port_tcp(port, privacy, PortProtocol::Auto) + .await?; self.forwarded.insert(port); } diff --git a/patched-vscode/cli/src/tunnels/protocol.rs b/patched-vscode/cli/src/tunnels/protocol.rs index d26ea978..3654826c 100644 --- a/patched-vscode/cli/src/tunnels/protocol.rs +++ b/patched-vscode/cli/src/tunnels/protocol.rs @@ -299,10 +299,40 @@ pub enum PortPrivacy { Private, } +#[derive(Serialize, Deserialize, PartialEq, Copy, Eq, Clone, Debug)] +#[serde(rename_all = "lowercase")] +pub enum PortProtocol { + Auto, + Http, + Https, +} + +impl std::fmt::Display for PortProtocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_contract_str()) + } +} + +impl Default for PortProtocol { + fn default() -> Self { + Self::Auto + } +} + +impl PortProtocol { + pub fn to_contract_str(&self) -> &'static str { + match *self { + Self::Auto => tunnels::contracts::TUNNEL_PROTOCOL_AUTO, + Self::Http => tunnels::contracts::TUNNEL_PROTOCOL_HTTP, + Self::Https => tunnels::contracts::TUNNEL_PROTOCOL_HTTPS, + } + } +} + pub mod forward_singleton { use serde::{Deserialize, Serialize}; - use super::PortPrivacy; + use super::{PortPrivacy, PortProtocol}; pub const METHOD_SET_PORTS: &str = "set_ports"; @@ -310,6 +340,7 @@ pub mod forward_singleton { pub struct PortRec { pub number: u16, pub privacy: PortPrivacy, + pub protocol: PortProtocol, } pub type PortList = Vec; diff --git a/patched-vscode/cli/src/update_service.rs b/patched-vscode/cli/src/update_service.rs index 4bec13d6..90339148 100644 --- a/patched-vscode/cli/src/update_service.rs +++ b/patched-vscode/cli/src/update_service.rs @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use std::{ffi::OsStr, fmt, path::Path}; +use std::{fmt, path::Path}; use serde::{Deserialize, Serialize}; @@ -11,10 +11,11 @@ use crate::{ constants::VSCODE_CLI_UPDATE_ENDPOINT, debug, log, options, spanf, util::{ - errors::{AnyError, CodeError, WrappedError}, + errors::{wrap, AnyError, CodeError, WrappedError}, http::{BoxedHttp, SimpleResponse}, io::ReportCopyProgress, - tar, zipper, + tar::{self, has_gzip_header}, + zipper, }, }; @@ -178,10 +179,10 @@ pub fn unzip_downloaded_release( where T: ReportCopyProgress, { - if compressed_file.extension() == Some(OsStr::new("zip")) { - zipper::unzip_file(compressed_file, target_dir, reporter) - } else { - tar::decompress_tarball(compressed_file, target_dir, reporter) + match has_gzip_header(compressed_file) { + Ok((f, true)) => tar::decompress_tarball(f, target_dir, reporter), + Ok((f, false)) => zipper::unzip_file(f, target_dir, reporter), + Err(e) => Err(wrap(e, "error checking for gzip header")), } } @@ -249,7 +250,7 @@ impl Platform { Platform::DarwinARM64 => "server-darwin-arm64", Platform::WindowsX64 => "server-win32-x64", Platform::WindowsX86 => "server-win32", - Platform::WindowsARM64 => "server-win32-x64", // we don't publish an arm64 server build yet + Platform::WindowsARM64 => "server-win32-arm64", } .to_owned() } diff --git a/patched-vscode/cli/src/util/errors.rs b/patched-vscode/cli/src/util/errors.rs index 7d28ce9f..67519d54 100644 --- a/patched-vscode/cli/src/util/errors.rs +++ b/patched-vscode/cli/src/util/errors.rs @@ -512,6 +512,8 @@ pub enum CodeError { // todo: can be specialized when update service is moved to CodeErrors #[error("Could not check for update: {0}")] UpdateCheckFailed(String), + #[error("Could not read connection token file: {0}")] + CouldNotReadConnectionTokenFile(std::io::Error), #[error("Could not write connection token file: {0}")] CouldNotCreateConnectionTokenFile(std::io::Error), #[error("A tunnel with the name {0} exists and is in-use. Please pick a different name or stop the existing tunnel.")] diff --git a/patched-vscode/cli/src/util/prereqs.rs b/patched-vscode/cli/src/util/prereqs.rs index 5d4953ac..0f49ab20 100644 --- a/patched-vscode/cli/src/util/prereqs.rs +++ b/patched-vscode/cli/src/util/prereqs.rs @@ -19,12 +19,22 @@ lazy_static! { static ref GENERIC_VERSION_RE: Regex = Regex::new(r"^([0-9]+)\.([0-9]+)$").unwrap(); static ref LIBSTD_CXX_VERSION_RE: BinRegex = BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap(); - static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 25); - static ref MIN_LEGACY_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 19); static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 28, 0); static ref MIN_LEGACY_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 17, 0); } +#[cfg(target_arch = "arm")] +lazy_static! { + static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 26); + static ref MIN_LEGACY_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 22); +} + +#[cfg(not(target_arch = "arm"))] +lazy_static! { + static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 25); + static ref MIN_LEGACY_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 19); +} + const NIXOS_TEST_PATH: &str = "/etc/NIXOS"; pub struct PreReqChecker {} diff --git a/patched-vscode/cli/src/util/tar.rs b/patched-vscode/cli/src/util/tar.rs index fe4d4269..10577140 100644 --- a/patched-vscode/cli/src/util/tar.rs +++ b/patched-vscode/cli/src/util/tar.rs @@ -5,8 +5,8 @@ use crate::util::errors::{wrap, WrappedError}; use flate2::read::GzDecoder; -use std::fs; -use std::io::Seek; +use std::fs::{self, File}; +use std::io::{Read, Seek}; use std::path::{Path, PathBuf}; use tar::Archive; @@ -57,16 +57,13 @@ fn should_skip_first_segment(file: &fs::File) -> Result<(bool, u64), WrappedErro } pub fn decompress_tarball( - path: &Path, + mut tar_gz: File, parent_path: &Path, mut reporter: T, ) -> Result<(), WrappedError> where T: ReportCopyProgress, { - let mut tar_gz = fs::File::open(path) - .map_err(|e| wrap(e, format!("error opening file {}", path.display())))?; - let (skip_first, num_entries) = should_skip_first_segment(&tar_gz)?; let report_progress_every = num_entries / 20; let mut entries_so_far = 0; @@ -81,7 +78,7 @@ where let mut archive = Archive::new(tar); archive .entries() - .map_err(|e| wrap(e, format!("error opening archive {}", path.display())))? + .map_err(|e| wrap(e, "error opening archive"))? .filter_map(|e| e.ok()) .try_for_each::<_, Result<_, WrappedError>>(|mut entry| { // approximate progress based on where we are in the archive: @@ -118,3 +115,13 @@ where Ok(()) } + +pub fn has_gzip_header(path: &Path) -> std::io::Result<(File, bool)> { + let mut file = fs::File::open(path)?; + let mut header = [0; 2]; + let _ = file.read_exact(&mut header); + + file.rewind()?; + + Ok((file, header[0] == 0x1f && header[1] == 0x8b)) +} diff --git a/patched-vscode/cli/src/util/zipper.rs b/patched-vscode/cli/src/util/zipper.rs index 69bcf2d2..5521fa42 100644 --- a/patched-vscode/cli/src/util/zipper.rs +++ b/patched-vscode/cli/src/util/zipper.rs @@ -44,15 +44,12 @@ fn should_skip_first_segment(archive: &mut ZipArchive) -> bool { archive.len() > 1 // prefix removal is invalid if there's only a single file } -pub fn unzip_file(path: &Path, parent_path: &Path, mut reporter: T) -> Result<(), WrappedError> +pub fn unzip_file(file: File, parent_path: &Path, mut reporter: T) -> Result<(), WrappedError> where T: ReportCopyProgress, { - let file = fs::File::open(path) - .map_err(|e| wrap(e, format!("unable to open file {}", path.display())))?; - - let mut archive = zip::ZipArchive::new(file) - .map_err(|e| wrap(e, format!("failed to open zip archive {}", path.display())))?; + let mut archive = + zip::ZipArchive::new(file).map_err(|e| wrap(e, "failed to open zip archive"))?; let skip_segments_no = usize::from(should_skip_first_segment(&mut archive)); let report_progress_every = archive.len() / 20; diff --git a/patched-vscode/extensions/configuration-editing/package.json b/patched-vscode/extensions/configuration-editing/package.json index 8a00fda4..f6bfd751 100644 --- a/patched-vscode/extensions/configuration-editing/package.json +++ b/patched-vscode/extensions/configuration-editing/package.json @@ -11,7 +11,9 @@ "icon": "images/icon.png", "activationEvents": [ "onProfile", - "onProfile:github" + "onProfile:github", + "onLanguage:json", + "onLanguage:jsonc" ], "enabledApiProposals": [ "profileContentHandlers" diff --git a/patched-vscode/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json b/patched-vscode/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json index 681ca610..3f8400a7 100644 --- a/patched-vscode/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json +++ b/patched-vscode/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json @@ -180,6 +180,11 @@ "items": { "type": "string" } + }, + "disableAutomaticConfiguration": { + "type": "boolean", + "description": "Disables the setup that is automatically run in a codespace if no `postCreateCommand` is specified.", + "default": false } } } diff --git a/patched-vscode/extensions/cpp/package.json b/patched-vscode/extensions/cpp/package.json index c1d3f488..9f3c890a 100644 --- a/patched-vscode/extensions/cpp/package.json +++ b/patched-vscode/extensions/cpp/package.json @@ -30,9 +30,13 @@ "id": "cpp", "extensions": [ ".cpp", + ".cppm", ".cc", + ".ccm", ".cxx", + ".cxxm", ".c++", + ".c++m", ".hpp", ".hh", ".hxx", diff --git a/patched-vscode/extensions/csharp/cgmanifest.json b/patched-vscode/extensions/csharp/cgmanifest.json index 1c88bd17..58a7408d 100644 --- a/patched-vscode/extensions/csharp/cgmanifest.json +++ b/patched-vscode/extensions/csharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dotnet/csharp-tmLanguage", "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "7a7482ffc72a6677a87eb1ed76005593a4f7f131" + "commitHash": "d63e2661d4e0c83b6c7810eb1d0eedc5da843b04" } }, "license": "MIT", diff --git a/patched-vscode/extensions/csharp/syntaxes/csharp.tmLanguage.json b/patched-vscode/extensions/csharp/syntaxes/csharp.tmLanguage.json index 96dbe044..4a2497a0 100644 --- a/patched-vscode/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/patched-vscode/extensions/csharp/syntaxes/csharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dotnet/csharp-tmLanguage/commit/7a7482ffc72a6677a87eb1ed76005593a4f7f131", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/d63e2661d4e0c83b6c7810eb1d0eedc5da843b04", "name": "C#", "scopeName": "source.cs", "patterns": [ @@ -512,6 +512,9 @@ { "include": "#type-name" }, + { + "include": "#type-arguments" + }, { "include": "#attribute-arguments" } diff --git a/patched-vscode/extensions/css-language-features/client/src/browser/cssClientMain.ts b/patched-vscode/extensions/css-language-features/client/src/browser/cssClientMain.ts index 6522c786..1d4153d9 100644 --- a/patched-vscode/extensions/css-language-features/client/src/browser/cssClientMain.ts +++ b/patched-vscode/extensions/css-language-features/client/src/browser/cssClientMain.ts @@ -7,13 +7,7 @@ import { ExtensionContext, Uri, l10n } from 'vscode'; import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor } from '../cssClient'; import { LanguageClient } from 'vscode-languageclient/browser'; - -declare const Worker: { - new(stringUrl: string): any; -}; -declare const TextDecoder: { - new(encoding?: string): { decode(buffer: ArrayBuffer): string }; -}; +import { registerDropOrPasteResourceSupport } from '../dropOrPaste/dropOrPasteResource'; let client: BaseLanguageClient | undefined; @@ -25,11 +19,12 @@ export async function activate(context: ExtensionContext) { worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { - return new LanguageClient(id, name, clientOptions, worker); + return new LanguageClient(id, name, worker, clientOptions); }; client = await startClient(context, newLanguageClient, { TextDecoder }); + context.subscriptions.push(registerDropOrPasteResourceSupport({ language: 'css', scheme: '*' })); } catch (e) { console.log(e); } diff --git a/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/dropOrPasteResource.ts b/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/dropOrPasteResource.ts new file mode 100644 index 00000000..6a4c38d2 --- /dev/null +++ b/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/dropOrPasteResource.ts @@ -0,0 +1,152 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as vscode from 'vscode'; +import { getDocumentDir, Mimes, Schemes } from './shared'; +import { UriList } from './uriList'; + +class DropOrPasteResourceProvider implements vscode.DocumentDropEditProvider, vscode.DocumentPasteEditProvider { + readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('css', 'url'); + + async provideDocumentDropEdits( + document: vscode.TextDocument, + position: vscode.Position, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise { + const uriList = await this.getUriList(dataTransfer); + if (!uriList.entries.length || token.isCancellationRequested) { + return; + } + + const snippet = await this.createUriListSnippet(document.uri, uriList); + if (!snippet || token.isCancellationRequested) { + return; + } + + return { + kind: this.kind, + title: snippet.label, + insertText: snippet.snippet.value, + yieldTo: this.pasteAsCssUrlByDefault(document, position) ? [] : [vscode.DocumentDropOrPasteEditKind.Empty.append('uri')] + }; + } + + async provideDocumentPasteEdits( + document: vscode.TextDocument, + ranges: readonly vscode.Range[], + dataTransfer: vscode.DataTransfer, + _context: vscode.DocumentPasteEditContext, + token: vscode.CancellationToken + ): Promise { + const uriList = await this.getUriList(dataTransfer); + if (!uriList.entries.length || token.isCancellationRequested) { + return; + } + + const snippet = await this.createUriListSnippet(document.uri, uriList); + if (!snippet || token.isCancellationRequested) { + return; + } + + return [{ + kind: this.kind, + title: snippet.label, + insertText: snippet.snippet.value, + yieldTo: this.pasteAsCssUrlByDefault(document, ranges[0].start) ? [] : [vscode.DocumentDropOrPasteEditKind.Empty.append('uri')] + }]; + } + + private async getUriList(dataTransfer: vscode.DataTransfer): Promise { + const urlList = await dataTransfer.get(Mimes.uriList)?.asString(); + if (urlList) { + return UriList.from(urlList); + } + + // Find file entries + const uris: vscode.Uri[] = []; + for (const [_, entry] of dataTransfer) { + const file = entry.asFile(); + if (file?.uri) { + uris.push(file.uri); + } + } + + return new UriList(uris.map(uri => ({ uri, str: uri.toString(true) }))); + } + + private async createUriListSnippet(docUri: vscode.Uri, uriList: UriList): Promise<{ readonly snippet: vscode.SnippetString; readonly label: string } | undefined> { + if (!uriList.entries.length) { + return; + } + + const snippet = new vscode.SnippetString(); + for (let i = 0; i < uriList.entries.length; i++) { + const uri = uriList.entries[i]; + const relativePath = getRelativePath(getDocumentDir(docUri), uri.uri); + const urlText = relativePath ?? uri.str; + + snippet.appendText(`url(${urlText})`); + if (i !== uriList.entries.length - 1) { + snippet.appendText(' '); + } + } + + return { + snippet, + label: uriList.entries.length > 1 + ? vscode.l10n.t('Insert url() Functions') + : vscode.l10n.t('Insert url() Function') + }; + } + + private pasteAsCssUrlByDefault(document: vscode.TextDocument, position: vscode.Position): boolean { + const regex = /url\(.+?\)/gi; + for (const match of Array.from(document.lineAt(position.line).text.matchAll(regex))) { + if (position.character > match.index && position.character < match.index + match[0].length) { + return false; + } + } + return true; + } +} + +function getRelativePath(fromFile: vscode.Uri | undefined, toFile: vscode.Uri): string | undefined { + if (fromFile && fromFile.scheme === toFile.scheme && fromFile.authority === toFile.authority) { + if (toFile.scheme === Schemes.file) { + // On windows, we must use the native `path.relative` to generate the relative path + // so that drive-letters are resolved cast insensitively. However we then want to + // convert back to a posix path to insert in to the document + const relativePath = path.relative(fromFile.fsPath, toFile.fsPath); + return path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep)); + } + + return path.posix.relative(fromFile.path, toFile.path); + } + + return undefined; +} + +export function registerDropOrPasteResourceSupport(selector: vscode.DocumentSelector): vscode.Disposable { + const provider = new DropOrPasteResourceProvider(); + + return vscode.Disposable.from( + vscode.languages.registerDocumentDropEditProvider(selector, provider, { + providedDropEditKinds: [provider.kind], + dropMimeTypes: [ + Mimes.uriList, + 'files' + ] + }), + vscode.languages.registerDocumentPasteEditProvider(selector, provider, { + providedPasteEditKinds: [provider.kind], + pasteMimeTypes: [ + Mimes.uriList, + 'files' + ] + }) + ); +} diff --git a/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/shared.ts b/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/shared.ts new file mode 100644 index 00000000..548bccfe --- /dev/null +++ b/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/shared.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Utils } from 'vscode-uri'; + +export const Schemes = Object.freeze({ + file: 'file', + notebookCell: 'vscode-notebook-cell', + untitled: 'untitled', +}); + +export const Mimes = Object.freeze({ + plain: 'text/plain', + uriList: 'text/uri-list', +}); + + +export function getDocumentDir(uri: vscode.Uri): vscode.Uri | undefined { + const docUri = getParentDocumentUri(uri); + if (docUri.scheme === Schemes.untitled) { + return vscode.workspace.workspaceFolders?.[0]?.uri; + } + return Utils.dirname(docUri); +} + +function getParentDocumentUri(uri: vscode.Uri): vscode.Uri { + if (uri.scheme === Schemes.notebookCell) { + // is notebook documents necessary? + for (const notebook of vscode.workspace.notebookDocuments) { + for (const cell of notebook.getCells()) { + if (cell.document.uri.toString() === uri.toString()) { + return notebook.uri; + } + } + } + } + + return uri; +} diff --git a/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/uriList.ts b/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/uriList.ts new file mode 100644 index 00000000..ed20b1ee --- /dev/null +++ b/patched-vscode/extensions/css-language-features/client/src/dropOrPaste/uriList.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +function splitUriList(str: string): string[] { + return str.split('\r\n'); +} + +function parseUriList(str: string): string[] { + return splitUriList(str) + .filter(value => !value.startsWith('#')) // Remove comments + .map(value => value.trim()); +} + +export class UriList { + + static from(str: string): UriList { + return new UriList(coalesce(parseUriList(str).map(line => { + try { + return { uri: vscode.Uri.parse(line), str: line }; + } catch { + // Uri parse failure + return undefined; + } + }))); + } + + constructor( + public readonly entries: ReadonlyArray<{ readonly uri: vscode.Uri; readonly str: string }> + ) { } +} + +function coalesce(array: ReadonlyArray): T[] { + return array.filter(e => !!e); +} diff --git a/patched-vscode/extensions/css-language-features/client/src/node/cssClientMain.ts b/patched-vscode/extensions/css-language-features/client/src/node/cssClientMain.ts index 802b58ab..96926979 100644 --- a/patched-vscode/extensions/css-language-features/client/src/node/cssClientMain.ts +++ b/patched-vscode/extensions/css-language-features/client/src/node/cssClientMain.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getNodeFSRequestService } from './nodeFs'; -import { ExtensionContext, extensions, l10n } from 'vscode'; -import { startClient, LanguageClientConstructor } from '../cssClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; import { TextDecoder } from 'util'; - +import { ExtensionContext, extensions, l10n } from 'vscode'; +import { BaseLanguageClient, LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; +import { LanguageClientConstructor, startClient } from '../cssClient'; +import { getNodeFSRequestService } from './nodeFs'; +import { registerDropOrPasteResourceSupport } from '../dropOrPaste/dropOrPasteResource'; let client: BaseLanguageClient | undefined; @@ -37,6 +37,8 @@ export async function activate(context: ExtensionContext) { process.env['VSCODE_L10N_BUNDLE_LOCATION'] = l10n.uri?.toString() ?? ''; client = await startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder }); + + context.subscriptions.push(registerDropOrPasteResourceSupport({ language: 'css', scheme: '*' })); } export async function deactivate(): Promise { @@ -45,3 +47,4 @@ export async function deactivate(): Promise { client = undefined; } } + diff --git a/patched-vscode/extensions/css-language-features/client/tsconfig.json b/patched-vscode/extensions/css-language-features/client/tsconfig.json index 573b24b4..17bf7e96 100644 --- a/patched-vscode/extensions/css-language-features/client/tsconfig.json +++ b/patched-vscode/extensions/css-language-features/client/tsconfig.json @@ -1,10 +1,14 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "lib": [ + "webworker" + ] }, "include": [ "src/**/*", - "../../../src/vscode-dts/vscode.d.ts" + "../../../src/vscode-dts/vscode.d.ts", + "../../../src/vscode-dts/vscode.proposed.documentPaste.d.ts" ] } diff --git a/patched-vscode/extensions/css-language-features/package.json b/patched-vscode/extensions/css-language-features/package.json index f4f6adfb..10cbd954 100644 --- a/patched-vscode/extensions/css-language-features/package.json +++ b/patched-vscode/extensions/css-language-features/package.json @@ -23,6 +23,9 @@ "supported": true } }, + "enabledApiProposals": [ + "documentPaste" + ], "scripts": { "compile": "npx gulp compile-extension:css-language-features-client compile-extension:css-language-features-server", "watch": "npx gulp watch-extension:css-language-features-client watch-extension:css-language-features-server", @@ -994,7 +997,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^10.0.0-next.5", + "vscode-languageclient": "10.0.0-next.8", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/patched-vscode/extensions/css-language-features/schemas/package.schema.json b/patched-vscode/extensions/css-language-features/schemas/package.schema.json index 831149ca..6c4b91fa 100644 --- a/patched-vscode/extensions/css-language-features/schemas/package.schema.json +++ b/patched-vscode/extensions/css-language-features/schemas/package.schema.json @@ -1,6 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CSS contributions to package.json", "type": "object", "properties": { "contributes": { diff --git a/patched-vscode/extensions/css-language-features/server/package.json b/patched-vscode/extensions/css-language-features/server/package.json index 0f1750e8..329fe57f 100644 --- a/patched-vscode/extensions/css-language-features/server/package.json +++ b/patched-vscode/extensions/css-language-features/server/package.json @@ -11,8 +11,8 @@ "browser": "./dist/browser/cssServerMain", "dependencies": { "@vscode/l10n": "^0.0.18", - "vscode-css-languageservice": "^6.2.14", - "vscode-languageserver": "^10.0.0-next.3", + "vscode-css-languageservice": "^6.3.1", + "vscode-languageserver": "10.0.0-next.6", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/patched-vscode/extensions/css-language-features/server/yarn.lock b/patched-vscode/extensions/css-language-features/server/yarn.lock index 8d4c46d6..f59aed24 100644 --- a/patched-vscode/extensions/css-language-features/server/yarn.lock +++ b/patched-vscode/extensions/css-language-features/server/yarn.lock @@ -24,50 +24,50 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -vscode-css-languageservice@^6.2.14: - version "6.2.14" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.14.tgz#d44fe75c03942d865a9c1a5ff5fb4e8dec1f89d0" - integrity sha512-5UPQ9Y1sUTnuMyaMBpO7LrBkqjhEJb5eAwdUlDp+Uez8lry+Tspnk3+3p2qWS4LlNsr4p3v9WkZxUf1ltgFpgw== +vscode-css-languageservice@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.3.1.tgz#56733c90686db56855ccc156a534a68b8c1f2187" + integrity sha512-1BzTBuJfwMc3A0uX4JBdJgoxp74cjj4q2mDJdp49yD/GuAq4X0k5WtK6fNcMYr+FfJ9nqgR6lpfCSZDkARJ5qQ== dependencies: "@vscode/l10n" "^0.0.18" - vscode-languageserver-textdocument "^1.0.11" + vscode-languageserver-textdocument "^1.0.12" vscode-languageserver-types "3.17.5" vscode-uri "^3.0.8" -vscode-jsonrpc@9.0.0-next.2: - version "9.0.0-next.2" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.2.tgz#29e9741c742c80329bba1c60ce38fd014651ba80" - integrity sha512-meIaXAgChCHzWy45QGU8YpCNyqnZQ/sYeCj32OLDDbUYsCF7AvgpdXx3nnZn9yzr8ed0Od9bW+NGphEmXsqvIQ== +vscode-jsonrpc@9.0.0-next.4: + version "9.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz#ba403ddb3b82ca578179963dbe08e120a935f50d" + integrity sha512-zSVIr58lJSMYKIsZ5P7GtBbv1eEx25eNyOf0NmEzxmn1GhUNJAVAb5hkA1poKUwj1FRMwN6CeyWxZypmr8SsQQ== -vscode-languageserver-protocol@3.17.6-next.4: - version "3.17.6-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.4.tgz#3c56f6eb588bb42fccc0ac54a0d5daf2d02f0a1b" - integrity sha512-/2bleKBxZLyRObS4mkpaWlVI9xGiUqMVmh/ztZ2vL4uP2XyIpraT45JBpn9AtXr0alqKJPKLuKr+/qcYULvm/w== +vscode-languageserver-protocol@3.17.6-next.6: + version "3.17.6-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.6.tgz#8863a4dc8b395a8c31106ffdc945a00f9163b68b" + integrity sha512-naxM9kc/phpl0kAFNVPejMUWUtzFXdPYY/BtQTYtfbBbHf8sceHOrKkmf6yynZRu1A4oFtRZNqV3wyFRTWqUHw== dependencies: - vscode-jsonrpc "9.0.0-next.2" - vscode-languageserver-types "3.17.6-next.3" + vscode-jsonrpc "9.0.0-next.4" + vscode-languageserver-types "3.17.6-next.4" -vscode-languageserver-textdocument@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf" - integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA== +vscode-languageserver-textdocument@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631" + integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA== vscode-languageserver-types@3.17.5: version "3.17.5" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== -vscode-languageserver-types@3.17.6-next.3: - version "3.17.6-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.3.tgz#f71d6c57f18d921346cfe0c227aabd72eb8cd2f0" - integrity sha512-l5kNFXFRQGuzriXpuBqFpRmkf6f6A4VoU3h95OsVkqIOoi1k7KbwSo600cIdsKSJWrPg/+vX+QMPcMw1oI7ItA== +vscode-languageserver-types@3.17.6-next.4: + version "3.17.6-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz#6670939eb98f00aa7b05021dc3dd7fe9aa4453ea" + integrity sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q== -vscode-languageserver@^10.0.0-next.3: - version "10.0.0-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-10.0.0-next.3.tgz#a63c5ea9fab1be93d7732ab0fdc18c9b37956e07" - integrity sha512-4x1qHImf6ePji4+8PX43lnBCBfBNdi2jneGX2k5FswJhx/cxaYYmusShmmtO/clyL1iurxJacrQoXfw9+ikhvg== +vscode-languageserver@10.0.0-next.6: + version "10.0.0-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-10.0.0-next.6.tgz#0db118a93fe010c6b40cd04e91a15d09e7b60b60" + integrity sha512-0Lh1nhQfSxo5Ob+ayYO1QTIsDix2/Lc72Urm1KZrCFxK5zIFYaEh3QFeM9oZih4Rzs0ZkQPXXnoHtpvs5GT+Zw== dependencies: - vscode-languageserver-protocol "3.17.6-next.4" + vscode-languageserver-protocol "3.17.6-next.6" vscode-uri@^3.0.8: version "3.0.8" diff --git a/patched-vscode/extensions/css-language-features/yarn.lock b/patched-vscode/extensions/css-language-features/yarn.lock index 25a22d07..28dc1c05 100644 --- a/patched-vscode/extensions/css-language-features/yarn.lock +++ b/patched-vscode/extensions/css-language-features/yarn.lock @@ -47,32 +47,32 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -vscode-jsonrpc@9.0.0-next.2: - version "9.0.0-next.2" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.2.tgz#29e9741c742c80329bba1c60ce38fd014651ba80" - integrity sha512-meIaXAgChCHzWy45QGU8YpCNyqnZQ/sYeCj32OLDDbUYsCF7AvgpdXx3nnZn9yzr8ed0Od9bW+NGphEmXsqvIQ== +vscode-jsonrpc@9.0.0-next.4: + version "9.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz#ba403ddb3b82ca578179963dbe08e120a935f50d" + integrity sha512-zSVIr58lJSMYKIsZ5P7GtBbv1eEx25eNyOf0NmEzxmn1GhUNJAVAb5hkA1poKUwj1FRMwN6CeyWxZypmr8SsQQ== -vscode-languageclient@^10.0.0-next.5: - version "10.0.0-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-10.0.0-next.5.tgz#7431d88255a5fd99e9423659ac484b1f968200f3" - integrity sha512-JIf1WE7fvV0RElFM062bAummI433vcxuFwqoYAp+1zTVhta/jznxkTz1zs3Hbj2tiDfclf0TZ0qCxflAP1mY2Q== +vscode-languageclient@10.0.0-next.8: + version "10.0.0-next.8" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-10.0.0-next.8.tgz#5afa0ced3b2ac68d31cc1c48edc4f289744542a0" + integrity sha512-D9inIHgqKayO9Tv0MeLb3XIL76yTuWmKdHqcGZKzjtQrMGJgASJDYWTapu+yAjEpDp0gmVOaCYyIlLB86ncDoQ== dependencies: minimatch "^9.0.3" semver "^7.6.0" - vscode-languageserver-protocol "3.17.6-next.4" + vscode-languageserver-protocol "3.17.6-next.6" -vscode-languageserver-protocol@3.17.6-next.4: - version "3.17.6-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.4.tgz#3c56f6eb588bb42fccc0ac54a0d5daf2d02f0a1b" - integrity sha512-/2bleKBxZLyRObS4mkpaWlVI9xGiUqMVmh/ztZ2vL4uP2XyIpraT45JBpn9AtXr0alqKJPKLuKr+/qcYULvm/w== +vscode-languageserver-protocol@3.17.6-next.6: + version "3.17.6-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.6.tgz#8863a4dc8b395a8c31106ffdc945a00f9163b68b" + integrity sha512-naxM9kc/phpl0kAFNVPejMUWUtzFXdPYY/BtQTYtfbBbHf8sceHOrKkmf6yynZRu1A4oFtRZNqV3wyFRTWqUHw== dependencies: - vscode-jsonrpc "9.0.0-next.2" - vscode-languageserver-types "3.17.6-next.3" + vscode-jsonrpc "9.0.0-next.4" + vscode-languageserver-types "3.17.6-next.4" -vscode-languageserver-types@3.17.6-next.3: - version "3.17.6-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.3.tgz#f71d6c57f18d921346cfe0c227aabd72eb8cd2f0" - integrity sha512-l5kNFXFRQGuzriXpuBqFpRmkf6f6A4VoU3h95OsVkqIOoi1k7KbwSo600cIdsKSJWrPg/+vX+QMPcMw1oI7ItA== +vscode-languageserver-types@3.17.6-next.4: + version "3.17.6-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz#6670939eb98f00aa7b05021dc3dd7fe9aa4453ea" + integrity sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q== vscode-uri@^3.0.8: version "3.0.8" diff --git a/patched-vscode/extensions/extension-editing/src/extensionLinter.ts b/patched-vscode/extensions/extension-editing/src/extensionLinter.ts index dd1727ed..b69dac0e 100644 --- a/patched-vscode/extensions/extension-editing/src/extensionLinter.ts +++ b/patched-vscode/extensions/extension-editing/src/extensionLinter.ts @@ -149,7 +149,8 @@ export class ExtensionLinter { const effectiveProposalNames = extensionEnabledApiProposals[extensionId]; if (Array.isArray(effectiveProposalNames) && enabledApiProposals.children) { for (const child of enabledApiProposals.children) { - if (child.type === 'string' && !effectiveProposalNames.includes(getNodeValue(child))) { + const proposalName = child.type === 'string' ? getNodeValue(child) : undefined; + if (typeof proposalName === 'string' && !effectiveProposalNames.includes(proposalName.split('@')[0])) { const start = document.positionAt(child.offset); const end = document.positionAt(child.offset + child.length); diagnostics.push(new Diagnostic(new Range(start, end), apiProposalNotListed, DiagnosticSeverity.Error)); diff --git a/patched-vscode/extensions/fsharp/cgmanifest.json b/patched-vscode/extensions/fsharp/cgmanifest.json index 524b3fa0..f10f9ca6 100644 --- a/patched-vscode/extensions/fsharp/cgmanifest.json +++ b/patched-vscode/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "7d029a46f17637228b2ee85dd02e511c3e8039b3" + "commitHash": "7d1b695da917dc4c7a0f7fb4683f42da208f87a2" } }, "license": "MIT", diff --git a/patched-vscode/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/patched-vscode/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index 5063f1c5..41436335 100644 --- a/patched-vscode/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/patched-vscode/extensions/fsharp/syntaxes/fsharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/ionide/ionide-fsgrammar/commit/7d029a46f17637228b2ee85dd02e511c3e8039b3", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/7d1b695da917dc4c7a0f7fb4683f42da208f87a2", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -617,7 +617,7 @@ }, { "name": "constant.numeric.float.fsharp", - "match": "\\b-?[0-9][0-9_]*((\\.([0-9][0-9_]*([eE][+-]??[0-9][0-9_]*)?)?)|([eE][+-]??[0-9][0-9_]*))" + "match": "\\b-?[0-9][0-9_]*((\\.(?!\\.)([0-9][0-9_]*([eE][+-]??[0-9][0-9_]*)?)?)|([eE][+-]??[0-9][0-9_]*))" }, { "name": "constant.numeric.integer.nativeint.fsharp", @@ -635,7 +635,7 @@ }, "abstract_definition": { "name": "abstract.definition.fsharp", - "begin": "\\b(abstract)\\s+(member)?(\\s+\\[\\<.*\\>\\])?\\s*([_[:alpha:]0-9,\\._`\\s]+)(<)?", + "begin": "\\b(static)?\\s+(abstract)\\s+(member)?(\\s+\\[\\<.*\\>\\])?\\s*([_[:alpha:]0-9,\\._`\\s]+)(<)?", "end": "\\s*(with)\\b|=|$", "beginCaptures": { "1": { @@ -645,6 +645,9 @@ "name": "keyword.fsharp" }, "3": { + "name": "keyword.fsharp" + }, + "4": { "name": "support.function.attribute.fsharp" }, "5": { @@ -838,7 +841,7 @@ "name": "keyword.symbol.fsharp" } }, - "end": "(\\)\\s*(([?[:alpha:]0-9'`^._ ]+))+)", + "end": "(\\)\\s*(([?[:alpha:]0-9'`^._ ]+))*)", "endCaptures": { "1": { "name": "keyword.symbol.fsharp" @@ -933,7 +936,7 @@ "patterns": [ { "name": "binding.fsharp", - "begin": "\\b(let mutable|static let mutable|static let|let inline|let|and|member val|static member inline|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", + "begin": "\\b(let mutable|static let mutable|static let|let inline|let|and|member val|member inline|static member inline|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", "end": "\\s*((with\\b)|(=|\\n+=|(?<=\\=)))", "beginCaptures": { "1": { @@ -1008,7 +1011,7 @@ }, { "name": "binding.fsharp", - "begin": "\\b(static val mutable|val mutable|val)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9,\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9,\\._`\\s]+|(?<=,)\\s)*)?", + "begin": "\\b(static val mutable|val mutable|val inline|val)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9,\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9,\\._`\\s]+|(?<=,)\\s)*)?", "end": "\\n$", "beginCaptures": { "1": { diff --git a/patched-vscode/extensions/git/package.json b/patched-vscode/extensions/git/package.json index dfbb2928..6bf280b9 100644 --- a/patched-vscode/extensions/git/package.json +++ b/patched-vscode/extensions/git/package.json @@ -16,14 +16,17 @@ "contribMergeEditorMenus", "contribMultiDiffEditorMenus", "contribDiffEditorGutterToolBarMenus", + "contribSourceControlHistoryItemChangesMenu", "contribSourceControlHistoryItemGroupMenu", "contribSourceControlHistoryItemMenu", + "contribSourceControlHistoryTitleMenu", "contribSourceControlInputBoxMenu", "contribSourceControlTitleMenu", "contribViewsWelcome", "diffCommand", "editSessionIdentityProvider", "quickDiffProvider", + "quickInputButtonLocation", "quickPickSortByLabel", "scmActionButton", "scmHistoryProvider", @@ -891,6 +894,16 @@ "icon": "$(diff-multiple)", "category": "Git", "enablement": "!operationInProgress" + }, + { + "command": "git.copyCommitId", + "title": "%command.timelineCopyCommitId%", + "category": "Git" + }, + { + "command": "git.copyCommitMessage", + "title": "%command.timelineCopyCommitMessage%", + "category": "Git" } ], "continueEditSession": [ @@ -1426,6 +1439,14 @@ { "command": "git.pushRef", "when": "false" + }, + { + "command": "git.copyCommitId", + "when": "false" + }, + { + "command": "git.copyCommitMessage", + "when": "false" } ], "scm/title": [ @@ -1915,6 +1936,62 @@ "group": "1_modification@3" } ], + "scm/history/title": [ + { + "command": "git.fetchRef", + "group": "navigation@1", + "when": "scmProvider == git && scmHistoryItemGroupHasRemote" + }, + { + "command": "git.pullRef", + "group": "navigation@2", + "when": "scmProvider == git && scmHistoryItemGroupHasRemote" + }, + { + "command": "git.pushRef", + "when": "scmProvider == git && scmHistoryItemGroupHasRemote", + "group": "navigation@3" + }, + { + "command": "git.publish", + "when": "scmProvider == git && !scmHistoryItemGroupHasRemote", + "group": "navigation@3" + } + ], + "scm/historyItemChanges/title": [ + { + "command": "git.fetchRef", + "group": "navigation@1", + "when": "scmProvider == git && scmHistoryItemGroupHasRemote" + }, + { + "command": "git.pullRef", + "group": "navigation@2", + "when": "scmProvider == git && scmHistoryItemGroupHasRemote" + }, + { + "command": "git.pushRef", + "when": "scmProvider == git && scmHistoryItemGroupHasRemote", + "group": "navigation@3" + }, + { + "command": "git.publish", + "when": "scmProvider == git && !scmHistoryItemGroupHasRemote", + "group": "navigation@3" + } + ], + "scm/historyItem/context": [ + { + "command": "git.copyCommitId", + "when": "scmProvider == git && !listMultiSelection", + "group": "1_copy@1" + }, + { + "command": "git.copyCommitMessage", + "when": "scmProvider == git && !listMultiSelection", + "group": "1_copy@2" + } + ], "scm/incomingChanges": [ { "command": "git.fetchRef", @@ -1967,23 +2044,23 @@ { "command": "git.pushRef", "group": "navigation", - "when": "scmProvider == git && scmHistoryItemGroupHasUpstream" + "when": "scmProvider == git && scmHistoryItemGroupHasRemote" }, { "command": "git.publish", "group": "navigation", - "when": "scmProvider == git && !scmHistoryItemGroupHasUpstream" + "when": "scmProvider == git && !scmHistoryItemGroupHasRemote" } ], "scm/outgoingChanges/context": [ { "command": "git.pushRef", - "when": "scmProvider == git && scmHistoryItemGroupHasUpstream", + "when": "scmProvider == git && scmHistoryItemGroupHasRemote", "group": "1_modification@1" }, { "command": "git.publish", - "when": "scmProvider == git && !scmHistoryItemGroupHasUpstream", + "when": "scmProvider == git && !scmHistoryItemGroupHasRemote", "group": "1_modification@1" } ], @@ -3401,7 +3478,7 @@ "@vscode/iconv-lite-umd": "0.7.0", "byline": "^5.0.0", "file-type": "16.5.4", - "jschardet": "3.0.0", + "jschardet": "3.1.3", "picomatch": "2.3.1", "vscode-uri": "^2.0.0", "which": "4.0.0" diff --git a/patched-vscode/extensions/git/package.nls.json b/patched-vscode/extensions/git/package.nls.json index dfa60b96..c2f7c3d6 100644 --- a/patched-vscode/extensions/git/package.nls.json +++ b/patched-vscode/extensions/git/package.nls.json @@ -217,7 +217,7 @@ "message": "List of git commands (ex: commit, push) that would have their `stdout` logged to the [git output](command:git.showOutput). If the git command has a client-side hook configured, the client-side hook's `stdout` will also be logged to the [git output](command:git.showOutput).", "comment": [ "{Locked='](command:git.showOutput'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -245,8 +245,8 @@ "config.untrackedChanges.hidden": "Untracked changes are hidden and excluded from several actions.", "config.requireGitUserConfig": "Controls whether to require explicit Git user configuration or allow Git to guess if missing.", "config.showCommitInput": "Controls whether to show the commit input in the Git source control panel.", - "config.terminalAuthentication": "Controls whether to enable Code-OSS to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", - "config.terminalGitEditor": "Controls whether to enable Code-OSS to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", + "config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", + "config.terminalGitEditor": "Controls whether to enable VS Code to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", "config.timeline.showAuthor": "Controls whether to show the commit author in the Timeline view.", "config.timeline.showUncommitted": "Controls whether to show uncommitted changes in the Timeline view.", "config.timeline.date": "Controls which date to use for items in the Timeline view.", @@ -301,7 +301,7 @@ "message": "[Download Git for Windows](https://git-scm.com/download/win)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -309,7 +309,7 @@ "message": "[Download Git for macOS](https://git-scm.com/download/mac)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -317,48 +317,48 @@ "message": "Source control depends on Git being installed.\n[Download Git for Linux](https://git-scm.com/download/linux)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.missing": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).", "view.workbench.scm.disabled": { - "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", + "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:workbench.action.openSettings?%5B%22git.enabled%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.empty": { - "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", + "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:vscode.openFolder'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.folder": { - "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", + "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.init?%5Btrue%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.workspace": { - "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", + "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.init'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.emptyWorkspace": { - "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", + "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:workbench.action.addRootFolder'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -373,7 +373,7 @@ "comment": [ "{Locked='](command:git.openRepositoriesInParentFolders'}", "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -382,7 +382,7 @@ "comment": [ "{Locked='](command:git.openRepositoriesInParentFolders'}", "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -390,7 +390,7 @@ "message": "The detected Git repository is potentially unsafe as the folder is owned by someone other than the current user.\n[Manage Unsafe Repositories](command:git.manageUnsafeRepositories)\nTo learn more about unsafe repositories [read our docs](https://aka.ms/vscode-git-unsafe-repository).", "comment": [ "{Locked='](command:git.manageUnsafeRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -398,23 +398,23 @@ "message": "The detected Git repositories are potentially unsafe as the folders are owned by someone other than the current user.\n[Manage Unsafe Repositories](command:git.manageUnsafeRepositories)\nTo learn more about unsafe repositories [read our docs](https://aka.ms/vscode-git-unsafe-repository).", "comment": [ "{Locked='](command:git.manageUnsafeRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.closedRepository": { - "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", + "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.reopenClosedRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.closedRepositories": { - "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", + "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.reopenClosedRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -422,9 +422,9 @@ "message": "You can clone a repository locally.\n[Clone Repository](command:git.clone 'Clone a repository once the Git extension has activated')", "comment": [ "{Locked='](command:git.clone'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.learnMore": "To learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm)." + "view.workbench.learnMore": "To learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." } diff --git a/patched-vscode/extensions/git/src/api/api1.ts b/patched-vscode/extensions/git/src/api/api1.ts index f049939c..332c328b 100644 --- a/patched-vscode/extensions/git/src/api/api1.ts +++ b/patched-vscode/extensions/git/src/api/api1.ts @@ -44,6 +44,7 @@ export class ApiRepositoryState implements RepositoryState { get mergeChanges(): Change[] { return this._repository.mergeGroup.resourceStates.map(r => new ApiChange(r)); } get indexChanges(): Change[] { return this._repository.indexGroup.resourceStates.map(r => new ApiChange(r)); } get workingTreeChanges(): Change[] { return this._repository.workingTreeGroup.resourceStates.map(r => new ApiChange(r)); } + get untrackedChanges(): Change[] { return this._repository.untrackedGroup.resourceStates.map(r => new ApiChange(r)); } readonly onDidChange: Event = this._repository.onDidRunGitStatus; @@ -321,6 +322,10 @@ export class ApiImpl implements API { } async openRepository(root: Uri): Promise { + if (root.scheme !== 'file') { + return null; + } + await this._model.openRepository(root.fsPath); return this.getRepository(root) || null; } diff --git a/patched-vscode/extensions/git/src/api/git.d.ts b/patched-vscode/extensions/git/src/api/git.d.ts index 685b5413..17dd192e 100644 --- a/patched-vscode/extensions/git/src/api/git.d.ts +++ b/patched-vscode/extensions/git/src/api/git.d.ts @@ -122,6 +122,7 @@ export interface RepositoryState { readonly mergeChanges: Change[]; readonly indexChanges: Change[]; readonly workingTreeChanges: Change[]; + readonly untrackedChanges: Change[]; readonly onDidChange: Event; } @@ -144,6 +145,9 @@ export interface LogOptions { readonly sortByAuthorDate?: boolean; readonly shortStats?: boolean; readonly author?: string; + readonly refNames?: string[]; + readonly maxParents?: number; + readonly skip?: number; } export interface CommitOptions { diff --git a/patched-vscode/extensions/git/src/commands.ts b/patched-vscode/extensions/git/src/commands.ts index 48f67f39..cb13cca6 100644 --- a/patched-vscode/extensions/git/src/commands.ts +++ b/patched-vscode/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; @@ -1339,14 +1339,14 @@ export class CommandCenter { @command('git.stage') async stage(...resourceStates: SourceControlResourceState[]): Promise { - this.logger.debug(`git.stage ${resourceStates.length} `); + this.logger.debug(`[CommandCenter][stage] git.stage ${resourceStates.length} `); resourceStates = resourceStates.filter(s => !!s); if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) { const resource = this.getSCMResource(); - this.logger.debug(`git.stage.getSCMResource ${resource ? resource.resourceUri.toString() : null} `); + this.logger.debug(`[CommandCenter][stage] git.stage.getSCMResource ${resource ? resource.resourceUri.toString() : null} `); if (!resource) { return; @@ -1389,7 +1389,7 @@ export class CommandCenter { const untracked = selection.filter(s => s.resourceGroupType === ResourceGroupType.Untracked); const scmResources = [...workingTree, ...untracked, ...resolved, ...unresolved]; - this.logger.debug(`git.stage.scmResources ${scmResources.length} `); + this.logger.debug(`[CommandCenter][stage] git.stage.scmResources ${scmResources.length} `); if (!scmResources.length) { return; } @@ -2063,74 +2063,79 @@ export class CommandCenter { promptToSaveFilesBeforeCommit = 'never'; } - const enableSmartCommit = config.get('enableSmartCommit') === true; + let enableSmartCommit = config.get('enableSmartCommit') === true; const enableCommitSigning = config.get('enableCommitSigning') === true; let noStagedChanges = repository.indexGroup.resourceStates.length === 0; let noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; - if (promptToSaveFilesBeforeCommit !== 'never') { - let documents = workspace.textDocuments - .filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath)); - - if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) { - documents = documents - .filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath))); - } - - if (documents.length > 0) { - const message = documents.length === 1 - ? l10n.t('The following file has unsaved changes which won\'t be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?', path.basename(documents[0].uri.fsPath)) - : l10n.t('There are {0} unsaved files.\n\nWould you like to save them before committing?', documents.length); - const saveAndCommit = l10n.t('Save All & Commit Changes'); - const commit = l10n.t('Commit Changes'); - const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit); - - if (pick === saveAndCommit) { - await Promise.all(documents.map(d => d.save())); + if (!opts.empty) { + if (promptToSaveFilesBeforeCommit !== 'never') { + let documents = workspace.textDocuments + .filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath)); - // After saving the dirty documents, if there are any documents that are part of the - // index group we have to add them back in order for the saved changes to be committed + if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) { documents = documents .filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath))); - await repository.add(documents.map(d => d.uri)); + } - noStagedChanges = repository.indexGroup.resourceStates.length === 0; - noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; - } else if (pick !== commit) { - return; // do not commit on cancel + if (documents.length > 0) { + const message = documents.length === 1 + ? l10n.t('The following file has unsaved changes which won\'t be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?', path.basename(documents[0].uri.fsPath)) + : l10n.t('There are {0} unsaved files.\n\nWould you like to save them before committing?', documents.length); + const saveAndCommit = l10n.t('Save All & Commit Changes'); + const commit = l10n.t('Commit Changes'); + const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit); + + if (pick === saveAndCommit) { + await Promise.all(documents.map(d => d.save())); + + // After saving the dirty documents, if there are any documents that are part of the + // index group we have to add them back in order for the saved changes to be committed + documents = documents + .filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath))); + await repository.add(documents.map(d => d.uri)); + + noStagedChanges = repository.indexGroup.resourceStates.length === 0; + noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; + } else if (pick !== commit) { + return; // do not commit on cancel + } } } - } - // no changes, and the user has not configured to commit all in this case - if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty && !opts.all) { - const suggestSmartCommit = config.get('suggestSmartCommit') === true; + // no changes, and the user has not configured to commit all in this case + if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.all && !opts.amend) { + const suggestSmartCommit = config.get('suggestSmartCommit') === true; - if (!suggestSmartCommit) { - return; - } + if (!suggestSmartCommit) { + return; + } - // prompt the user if we want to commit all or not - const message = l10n.t('There are no staged changes to commit.\n\nWould you like to stage all your changes and commit them directly?'); - const yes = l10n.t('Yes'); - const always = l10n.t('Always'); - const never = l10n.t('Never'); - const pick = await window.showWarningMessage(message, { modal: true }, yes, always, never); - - if (pick === always) { - config.update('enableSmartCommit', true, true); - } else if (pick === never) { - config.update('suggestSmartCommit', false, true); - return; - } else if (pick !== yes) { - return; // do not commit on cancel + // prompt the user if we want to commit all or not + const message = l10n.t('There are no staged changes to commit.\n\nWould you like to stage all your changes and commit them directly?'); + const yes = l10n.t('Yes'); + const always = l10n.t('Always'); + const never = l10n.t('Never'); + const pick = await window.showWarningMessage(message, { modal: true }, yes, always, never); + + if (pick === always) { + enableSmartCommit = true; + config.update('enableSmartCommit', true, true); + } else if (pick === never) { + config.update('suggestSmartCommit', false, true); + return; + } else if (pick === yes) { + enableSmartCommit = true; + } else { + // Cancel + return; + } } - } - if (opts.all === undefined) { - opts = { ...opts, all: noStagedChanges }; - } else if (!opts.all && noStagedChanges && !opts.empty) { - opts = { ...opts, all: true }; + // smart commit + if (enableSmartCommit && !opts.all) { + opts = { ...opts, all: noStagedChanges }; + } } // enable signing of commits if configured @@ -2515,9 +2520,26 @@ export class CommandCenter { : l10n.t('Select a branch or tag to checkout'); quickPick.show(); - picks.push(... await createCheckoutItems(repository, opts?.detached)); - quickPick.items = [...commands, ...picks]; + + const setQuickPickItems = () => { + switch (true) { + case quickPick.value === '': + quickPick.items = [...commands, ...picks]; + break; + case commands.length === 0: + quickPick.items = picks; + break; + case picks.length === 0: + quickPick.items = commands; + break; + default: + quickPick.items = [...picks, { label: '', kind: QuickPickItemKind.Separator }, ...commands]; + break; + } + }; + + setQuickPickItems(); quickPick.busy = false; const choice = await new Promise(c => { @@ -2532,22 +2554,7 @@ export class CommandCenter { c(undefined); }))); - disposables.push(quickPick.onDidChangeValue(value => { - switch (true) { - case value === '': - quickPick.items = [...commands, ...picks]; - break; - case commands.length === 0: - quickPick.items = picks; - break; - case picks.length === 0: - quickPick.items = commands; - break; - default: - quickPick.items = [...picks, { label: '', kind: QuickPickItemKind.Separator }, ...commands]; - break; - } - })); + disposables.push(quickPick.onDidChangeValue(() => setQuickPickItems())); }); dispose(disposables); @@ -2696,6 +2703,7 @@ export class CommandCenter { { iconPath: new ThemeIcon('refresh'), tooltip: l10n.t('Regenerate Branch Name'), + location: QuickInputButtonLocation.Inline } ] : []; @@ -3044,7 +3052,8 @@ export class CommandCenter { } @command('git.fetchRef', { repository: true }) - async fetchRef(repository: Repository, ref: string): Promise { + async fetchRef(repository: Repository, ref?: string): Promise { + ref = ref ?? repository?.historyProvider.currentHistoryItemGroup?.remote?.id; if (!repository || !ref) { return; } @@ -3116,7 +3125,8 @@ export class CommandCenter { } @command('git.pullRef', { repository: true }) - async pullRef(repository: Repository, ref: string): Promise { + async pullRef(repository: Repository, ref?: string): Promise { + ref = ref ?? repository?.historyProvider.currentHistoryItemGroup?.remote?.id; if (!repository || !ref) { return; } @@ -3263,8 +3273,8 @@ export class CommandCenter { } @command('git.pushRef', { repository: true }) - async pushRef(repository: Repository, ref: string): Promise { - if (!repository || !ref) { + async pushRef(repository: Repository): Promise { + if (!repository) { return; } @@ -4182,17 +4192,36 @@ export class CommandCenter { } @command('git.viewCommit', { repository: true }) - async viewCommit(repository: Repository, historyItem: SourceControlHistoryItem): Promise { - if (!repository || !historyItem) { + async viewCommit(repository: Repository, historyItem1: SourceControlHistoryItem, historyItem2?: SourceControlHistoryItem): Promise { + if (!repository || !historyItem1) { return; } - const commit = await repository.getCommit(historyItem.id); - const title = `${historyItem.id.substring(0, 8)} - ${commit.message}`; + if (historyItem2) { + const mergeBase = await repository.getMergeBase(historyItem1.id, historyItem2.id); + if (!mergeBase || (mergeBase !== historyItem1.id && mergeBase !== historyItem2.id)) { + return; + } + } + + let title: string | undefined; + let historyItemParentId: string | undefined; + + // If historyItem2 is not provided, we are viewing a single commit. If historyItem2 is + // provided, we are viewing a range and we have to include both start and end commits. + // TODO@lszomoru - handle the case when historyItem2 is the first commit in the repository + if (!historyItem2) { + const commit = await repository.getCommit(historyItem1.id); + title = `${historyItem1.id.substring(0, 8)} - ${commit.message}`; + historyItemParentId = historyItem1.parentIds.length > 0 ? historyItem1.parentIds[0] : `${historyItem1.id}^`; + } else { + title = l10n.t('All Changes ({0} ↔ {1})', historyItem2.id.substring(0, 8), historyItem1.id.substring(0, 8)); + historyItemParentId = historyItem2.parentIds.length > 0 ? historyItem2.parentIds[0] : `${historyItem2.id}^`; + } - const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-commit' }); + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `${historyItemParentId}..${historyItem1.id}`, { scheme: 'git-commit', }); - await this._viewChanges(repository, historyItem, multiDiffSourceUri, title); + await this._viewChanges(repository, historyItem1.id, historyItemParentId, multiDiffSourceUri, title); } @command('git.viewAllChanges', { repository: true }) @@ -4207,20 +4236,32 @@ export class CommandCenter { const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-changes' }); - await this._viewChanges(repository, historyItem, multiDiffSourceUri, title); + await this._viewChanges(repository, modifiedShortRef, originalShortRef, multiDiffSourceUri, title); } - async _viewChanges(repository: Repository, historyItem: SourceControlHistoryItem, multiDiffSourceUri: Uri, title: string): Promise { - const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : `${historyItem.id}^`; - const changes = await repository.diffBetween(historyItemParentId, historyItem.id); + async _viewChanges(repository: Repository, historyItemId: string, historyItemParentId: string, multiDiffSourceUri: Uri, title: string): Promise { + const changes = await repository.diffBetween(historyItemParentId, historyItemId); + const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItemId)); + + await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); + } - if (changes.length === 0) { + @command('git.copyCommitId', { repository: true }) + async copyCommitId(repository: Repository, historyItem: SourceControlHistoryItem): Promise { + if (!repository || !historyItem) { return; } - const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItem.id)); + env.clipboard.writeText(historyItem.id); + } - await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); + @command('git.copyCommitMessage', { repository: true }) + async copyCommitMessage(repository: Repository, historyItem: SourceControlHistoryItem): Promise { + if (!repository || !historyItem) { + return; + } + + env.clipboard.writeText(historyItem.message); } private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { @@ -4400,10 +4441,10 @@ export class CommandCenter { private getSCMResource(uri?: Uri): Resource | undefined { uri = uri ? uri : (window.activeTextEditor && window.activeTextEditor.document.uri); - this.logger.debug(`git.getSCMResource.uri ${uri && uri.toString()}`); + this.logger.debug(`[CommandCenter][getSCMResource] git.getSCMResource.uri: ${uri && uri.toString()}`); for (const r of this.model.repositories.map(r => r.root)) { - this.logger.debug(`repo root ${r}`); + this.logger.debug(`[CommandCenter][getSCMResource] repo root: ${r}`); } if (!uri) { diff --git a/patched-vscode/extensions/git/src/decorationProvider.ts b/patched-vscode/extensions/git/src/decorationProvider.ts index 3f855326..ace68c22 100644 --- a/patched-vscode/extensions/git/src/decorationProvider.ts +++ b/patched-vscode/extensions/git/src/decorationProvider.ts @@ -220,16 +220,16 @@ class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider const historyProvider = this.repository.historyProvider; const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; - if (!currentHistoryItemGroup?.base) { + if (!currentHistoryItemGroup?.remote) { return []; } - const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); + const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.remote.id); if (!ancestor) { return []; } - const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); + const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.remote.id); return changes; } catch (err) { return []; diff --git a/patched-vscode/extensions/git/src/encoding.ts b/patched-vscode/extensions/git/src/encoding.ts index a283f628..c80fb6ee 100644 --- a/patched-vscode/extensions/git/src/encoding.ts +++ b/patched-vscode/extensions/git/src/encoding.ts @@ -49,15 +49,38 @@ const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = { 'big5': 'cp950' }; -export function detectEncoding(buffer: Buffer): string | null { +const MAP_CANDIDATE_GUESS_ENCODING_TO_JSCHARDET: { [key: string]: string } = { + utf8: 'UTF-8', + utf16le: 'UTF-16LE', + utf16be: 'UTF-16BE', + windows1252: 'windows-1252', + windows1250: 'windows-1250', + iso88592: 'ISO-8859-2', + windows1251: 'windows-1251', + cp866: 'IBM866', + iso88595: 'ISO-8859-5', + koi8r: 'KOI8-R', + windows1253: 'windows-1253', + iso88597: 'ISO-8859-7', + windows1255: 'windows-1255', + iso88598: 'ISO-8859-8', + cp950: 'Big5', + shiftjis: 'SHIFT_JIS', + eucjp: 'EUC-JP', + euckr: 'EUC-KR', + gb2312: 'GB2312' +}; + +export function detectEncoding(buffer: Buffer, candidateGuessEncodings: string[]): string | null { const result = detectEncodingByBOM(buffer); if (result) { return result; } - const detected = jschardet.detect(buffer); + candidateGuessEncodings = candidateGuessEncodings.map(e => MAP_CANDIDATE_GUESS_ENCODING_TO_JSCHARDET[e]).filter(e => !!e); + const detected = jschardet.detect(buffer, candidateGuessEncodings.length > 0 ? { detectEncodings: candidateGuessEncodings } : undefined); if (!detected || !detected.encoding) { return null; } diff --git a/patched-vscode/extensions/git/src/git.ts b/patched-vscode/extensions/git/src/git.ts index 697e7781..9940ae53 100644 --- a/patched-vscode/extensions/git/src/git.ts +++ b/patched-vscode/extensions/git/src/git.ts @@ -1062,6 +1062,7 @@ export interface PullOptions { } export class Repository { + private _isUsingRefTable = false; constructor( private _git: Git, @@ -1113,7 +1114,7 @@ export class Repository { return result.stdout.trim(); } catch (err) { - this.logger.warn(`git config failed: ${err.message}`); + this.logger.warn(`[Git][config] git config failed: ${err.message}`); return ''; } } @@ -1165,6 +1166,20 @@ export class Repository { args.push(`--author="${options.author}"`); } + if (typeof options?.maxParents === 'number') { + args.push(`--max-parents=${options.maxParents}`); + } + + if (typeof options?.skip === 'number') { + args.push(`--skip=${options.skip}`); + } + + if (options?.refNames) { + args.push('--topo-order'); + args.push('--decorate=full'); + args.push(...options.refNames); + } + if (options?.path) { args.push('--', options.path); } @@ -1233,11 +1248,11 @@ export class Repository { .filter(entry => !!entry); } - async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise { + async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false, candidateGuessEncodings: string[] = []): Promise { const stdout = await this.buffer(object); if (autoGuessEncoding) { - encoding = detectEncoding(stdout) || encoding; + encoding = detectEncoding(stdout, candidateGuessEncodings) || encoding; } encoding = iconv.encodingExists(encoding) ? encoding : 'utf8'; @@ -1496,9 +1511,31 @@ export class Repository { return parseGitChanges(this.repositoryRoot, gitResult.stdout); } - async getMergeBase(ref1: string, ref2: string): Promise { + async diffTrees(treeish1: string, treeish2?: string): Promise { + const args = ['diff-tree', '-r', '--name-status', '-z', '--diff-filter=ADMR', treeish1]; + + if (treeish2) { + args.push(treeish2); + } + + const gitResult = await this.exec(args); + if (gitResult.exitCode) { + return []; + } + + return parseGitChanges(this.repositoryRoot, gitResult.stdout); + } + + async getMergeBase(ref1: string, ref2: string, ...refs: string[]): Promise { try { - const args = ['merge-base', ref1, ref2]; + const args = ['merge-base']; + if (refs.length !== 0) { + args.push('--octopus'); + args.push(...refs); + } + + args.push(ref1, ref2); + const result = await this.exec(args); return result.stdout.trim(); @@ -2298,13 +2335,25 @@ export class Repository { } async getHEAD(): Promise { - try { - // Attempt to parse the HEAD file - const result = await this.getHEADFS(); - return result; - } - catch (err) { - this.logger.warn(err.message); + if (!this._isUsingRefTable) { + try { + // Attempt to parse the HEAD file + const result = await this.getHEADFS(); + + // Git 2.45 adds support for a new reference storage backend called "reftable", promising + // faster lookups, reads, and writes for repositories with any number of references. For + // backwards compatibility the `.git/HEAD` file contains `ref: refs/heads/.invalid`. More + // details are available at https://git-scm.com/docs/reftable + if (result.name === '.invalid') { + this._isUsingRefTable = true; + this.logger.warn(`[Git][getHEAD] Failed to parse HEAD file: Repository is using reftable format.`); + } else { + return result; + } + } + catch (err) { + this.logger.warn(`[Git][getHEAD] Failed to parse HEAD file: ${err.message}`); + } } try { @@ -2452,11 +2501,11 @@ export class Repository { remotes.push(...await this.getRemotesFS()); if (remotes.length === 0) { - this.logger.info('No remotes found in the git config file.'); + this.logger.info('[Git][getRemotes] No remotes found in the git config file'); } } catch (err) { - this.logger.warn(`getRemotes() - ${err.message}`); + this.logger.warn(`[Git][getRemotes] Error: ${err.message}`); // Fallback to using git to get the remotes remotes.push(...await this.getRemotesGit()); @@ -2592,7 +2641,7 @@ export class Repository { return branch; } - this.logger.warn(`No such branch: ${name}.`); + this.logger.warn(`[Git][getBranch] No such branch: ${name}`); return Promise.reject(new Error(`No such branch: ${name}.`)); } @@ -2657,7 +2706,7 @@ export class Repository { } async getCommit(ref: string): Promise { - const result = await this.exec(['show', '-s', `--format=${COMMIT_FORMAT}`, '-z', ref]); + const result = await this.exec(['show', '-s', '--decorate=full', '--shortstat', `--format=${COMMIT_FORMAT}`, '-z', ref]); const commits = parseGitCommits(result.stdout); if (commits.length === 0) { return Promise.reject('bad commit format'); @@ -2688,7 +2737,7 @@ export class Repository { const result = await fs.readFile(path.join(this.dotGit.path, ref), 'utf8'); return result.trim(); } catch (err) { - this.logger.warn(err.message); + this.logger.warn(`[Git][revParse] Unable to read file: ${err.message}`); } try { diff --git a/patched-vscode/extensions/git/src/historyProvider.ts b/patched-vscode/extensions/git/src/historyProvider.ts index f238010e..d19311b3 100644 --- a/patched-vscode/extensions/git/src/historyProvider.ts +++ b/patched-vscode/extensions/git/src/historyProvider.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ -import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel } from 'vscode'; +import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemLabel } from 'vscode'; import { Repository, Resource } from './repository'; -import { IDisposable, dispose, filterEvent } from './util'; +import { IDisposable, dispose } from './util'; import { toGitUri } from './uri'; -import { Branch, RefType, UpstreamRef } from './api/git'; +import { Branch, LogOptions, RefType, UpstreamRef } from './api/git'; import { emojify, ensureEmojis } from './emoji'; -import { Operation } from './operation'; +import { Commit } from './git'; export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable { @@ -21,6 +21,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; private _HEAD: Branch | undefined; + private _HEADMergeBase: Branch | undefined; + private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined; get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { @@ -29,36 +31,46 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } private historyItemDecorations = new Map(); + private historyItemLabels = new Map([ + ['HEAD -> refs/heads/', 'target'], + ['refs/heads/', 'git-branch'], + ['refs/remotes/', 'cloud'], + ['refs/tags/', 'tag'] + ]); private disposables: Disposable[] = []; constructor(protected readonly repository: Repository, private readonly logger: LogOutputChannel) { this.disposables.push(repository.onDidRunGitStatus(() => this.onDidRunGitStatus(), this)); - this.disposables.push(filterEvent(repository.onDidRunOperation, e => e.operation === Operation.Refresh)(() => this.onDidRunGitStatus(true), this)); - this.disposables.push(window.registerFileDecorationProvider(this)); } - private async onDidRunGitStatus(force = false): Promise { - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD:', JSON.stringify(this._HEAD)); - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - repository.HEAD:', JSON.stringify(this.repository.HEAD)); + private async onDidRunGitStatus(): Promise { + this.logger.trace('[GitHistoryProvider][onDidRunGitStatus] HEAD:', JSON.stringify(this._HEAD)); + this.logger.trace('[GitHistoryProvider][onDidRunGitStatus] repository.HEAD:', JSON.stringify(this.repository.HEAD)); + + // Get the merge base of the current history item group + const mergeBase = await this.resolveHEADMergeBase(); // Check if HEAD has changed - if (!force && - this._HEAD?.name === this.repository.HEAD?.name && + if (this._HEAD?.name === this.repository.HEAD?.name && this._HEAD?.commit === this.repository.HEAD?.commit && this._HEAD?.upstream?.name === this.repository.HEAD?.upstream?.name && this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote && - this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD has not changed'); + this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit && + this._HEADMergeBase?.name === mergeBase?.name && + this._HEADMergeBase?.remote === mergeBase?.remote && + this._HEADMergeBase?.commit === mergeBase?.commit) { + this.logger.trace('[GitHistoryProvider][onDidRunGitStatus] HEAD has not changed'); return; } this._HEAD = this.repository.HEAD; + this._HEADMergeBase = mergeBase; // Check if HEAD does not support incoming/outgoing (detached commit, tag) if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) { - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD does not support incoming/outgoing'); + this.logger.trace('[GitHistoryProvider][onDidRunGitStatus] HEAD does not support incoming/outgoing'); this.currentHistoryItemGroup = undefined; return; @@ -67,14 +79,22 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec this.currentHistoryItemGroup = { id: `refs/heads/${this.repository.HEAD.name ?? ''}`, name: this.repository.HEAD.name ?? '', - base: this.repository.HEAD.upstream ? - { - id: `refs/remotes/${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`, - name: `${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`, - } : undefined + revision: this.repository.HEAD.commit, + remote: this.repository.HEAD.upstream ? { + id: `refs/remotes/${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`, + name: `${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`, + revision: this.repository.HEAD.upstream.commit + } : undefined, + base: mergeBase && + (mergeBase.remote !== this.repository.HEAD.upstream?.remote || + mergeBase.name !== this.repository.HEAD.upstream?.name) ? { + id: `refs/remotes/${mergeBase.remote}/${mergeBase.name}`, + name: `${mergeBase.remote}/${mergeBase.name}`, + revision: mergeBase.commit + } : undefined }; - this.logger.trace(`GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup (${force}): ${JSON.stringify(this.currentHistoryItemGroup)}`); + this.logger.trace(`[GitHistoryProvider][onDidRunGitStatus] currentHistoryItemGroup: ${JSON.stringify(this.currentHistoryItemGroup)}`); } async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise { @@ -112,25 +132,71 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItems; } - async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise { - if (!historyItemParentId) { - const commit = await this.repository.getCommit(historyItemId); - historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : `${historyItemId}^`; + async provideHistoryItems2(options: SourceControlHistoryOptions): Promise { + if (!this.currentHistoryItemGroup || !options.historyItemGroupIds) { + return []; + } + + // Deduplicate refNames + const refNames = Array.from(new Set(options.historyItemGroupIds)); + + let logOptions: LogOptions = { refNames, shortStats: true }; + + try { + if (options.limit === undefined || typeof options.limit === 'number') { + logOptions = { ...logOptions, maxEntries: options.limit ?? 50 }; + } else if (typeof options.limit.id === 'string') { + // Get the common ancestor commit, and commits + const commit = await this.repository.getCommit(options.limit.id); + const commitParentId = commit.parents.length > 0 ? commit.parents[0] : await this.repository.getEmptyTree(); + + logOptions = { ...logOptions, range: `${commitParentId}..` }; + } + + if (typeof options.skip === 'number') { + logOptions = { ...logOptions, skip: options.skip }; + } + + const commits = await this.repository.log({ ...logOptions, silent: true }); + + await ensureEmojis(); + + return commits.map(commit => { + const newLineIndex = commit.message.indexOf('\n'); + const subject = newLineIndex !== -1 ? commit.message.substring(0, newLineIndex) : commit.message; + + const labels = this.resolveHistoryItemLabels(commit); + + return { + id: commit.hash, + parentIds: commit.parents, + message: emojify(subject), + author: commit.authorName, + icon: new ThemeIcon('git-commit'), + timestamp: commit.authorDate?.getTime(), + statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 }, + labels: labels.length !== 0 ? labels : undefined + }; + }); + } catch (err) { + this.logger.error(`[GitHistoryProvider][provideHistoryItems2] Failed to get history items with options '${JSON.stringify(options)}': ${err}`); + return []; } + } + async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise { + historyItemParentId = historyItemParentId ?? await this.repository.getEmptyTree(); const allChanges = await this.repository.diffBetweenShortStat(historyItemParentId, historyItemId); + return { id: historyItemId, parentIds: [historyItemParentId], message: '', statistics: allChanges }; } async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise { - if (!historyItemParentId) { - const commit = await this.repository.getCommit(historyItemId); - historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : `${historyItemId}^`; - } + historyItemParentId = historyItemParentId ?? await this.repository.getEmptyTree(); const historyItemChangesUri: Uri[] = []; const historyItemChanges: SourceControlHistoryItemChange[] = []; - const changes = await this.repository.diffBetween(historyItemParentId, historyItemId); + const changes = await this.repository.diffTrees(historyItemParentId, historyItemId); for (const change of changes) { const historyItemUri = change.uri.with({ @@ -161,9 +227,9 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec async resolveHistoryItemGroupCommonAncestor(historyItemId1: string, historyItemId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> { if (!historyItemId2) { - const upstreamRef = await this.resolveHistoryItemGroupBase(historyItemId1); + const upstreamRef = await this.resolveHistoryItemGroupMergeBase(historyItemId1); if (!upstreamRef) { - this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to resolve history item group base for '${historyItemId1}'`); + this.logger.info(`[GitHistoryProvider][resolveHistoryItemGroupCommonAncestor] Failed to resolve history item group base for '${historyItemId1}'`); return undefined; } @@ -172,16 +238,51 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const ancestor = await this.repository.getMergeBase(historyItemId1, historyItemId2); if (!ancestor) { - this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to resolve common ancestor for '${historyItemId1}' and '${historyItemId2}'`); + this.logger.info(`[GitHistoryProvider][resolveHistoryItemGroupCommonAncestor] Failed to resolve common ancestor for '${historyItemId1}' and '${historyItemId2}'`); return undefined; } try { const commitCount = await this.repository.getCommitCount(`${historyItemId1}...${historyItemId2}`); - this.logger.trace(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Resolved common ancestor for '${historyItemId1}' and '${historyItemId2}': ${JSON.stringify({ id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind })}`); + this.logger.trace(`[GitHistoryProvider][resolveHistoryItemGroupCommonAncestor] Resolved common ancestor for '${historyItemId1}' and '${historyItemId2}': ${JSON.stringify({ id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind })}`); return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind }; } catch (err) { - this.logger.error(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to get ahead/behind for '${historyItemId1}...${historyItemId2}': ${err.message}`); + this.logger.error(`[GitHistoryProvider][resolveHistoryItemGroupCommonAncestor] Failed to get ahead/behind for '${historyItemId1}...${historyItemId2}': ${err.message}`); + } + + return undefined; + } + + async resolveHistoryItemGroupCommonAncestor2(historyItemGroupIds: string[]): Promise { + try { + if (historyItemGroupIds.length === 0) { + // TODO@lszomoru - log + return undefined; + } else if (historyItemGroupIds.length === 1 && historyItemGroupIds[0] === this.currentHistoryItemGroup?.id) { + // Remote + if (this.currentHistoryItemGroup.remote) { + const ancestor = await this.repository.getMergeBase(historyItemGroupIds[0], this.currentHistoryItemGroup.remote.id); + return ancestor; + } + + // Base + if (this.currentHistoryItemGroup.base) { + const ancestor = await this.repository.getMergeBase(historyItemGroupIds[0], this.currentHistoryItemGroup.base.id); + return ancestor; + } + + // First commit + const commits = await this.repository.log({ maxParents: 0, refNames: ['HEAD'] }); + if (commits.length > 0) { + return commits[0].hash; + } + } else if (historyItemGroupIds.length > 1) { + const ancestor = await this.repository.getMergeBase(historyItemGroupIds[0], historyItemGroupIds[1], ...historyItemGroupIds.slice(2)); + return ancestor; + } + } + catch (err) { + this.logger.error(`[GitHistoryProvider][resolveHistoryItemGroupCommonAncestor2] Failed to resolve common ancestor for ${historyItemGroupIds.join(',')}: ${err}`); } return undefined; @@ -191,7 +292,25 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return this.historyItemDecorations.get(uri.toString()); } - private async resolveHistoryItemGroupBase(historyItemId: string): Promise { + private resolveHistoryItemLabels(commit: Commit): SourceControlHistoryItemLabel[] { + const labels: SourceControlHistoryItemLabel[] = []; + + for (const label of commit.refNames) { + for (const [key, value] of this.historyItemLabels) { + if (label.startsWith(key)) { + labels.push({ + title: label.substring(key.length), + icon: new ThemeIcon(value) + }); + break; + } + } + } + + return labels; + } + + private async resolveHistoryItemGroupMergeBase(historyItemId: string): Promise { try { // Upstream const branch = await this.repository.getBranch(historyItemId); @@ -202,7 +321,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // Base (config -> reflog -> default) const remoteBranch = await this.repository.getBranchBase(historyItemId); if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { - this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to resolve history item group base for '${historyItemId}'`); + this.logger.info(`[GitHistoryProvider][resolveHistoryItemGroupUpstreamOrBase] Failed to resolve history item group base for '${historyItemId}'`); return undefined; } @@ -213,12 +332,21 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec }; } catch (err) { - this.logger.error(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to get branch base for '${historyItemId}': ${err.message}`); + this.logger.error(`[GitHistoryProvider][resolveHistoryItemGroupUpstreamOrBase] Failed to get branch base for '${historyItemId}': ${err.message}`); } return undefined; } + private async resolveHEADMergeBase(): Promise { + if (this.repository.HEAD?.type !== RefType.Head || !this.repository.HEAD?.name) { + return undefined; + } + + const mergeBase = await this.repository.getBranchBase(this.repository.HEAD.name); + return mergeBase; + } + dispose(): void { dispose(this.disposables); } diff --git a/patched-vscode/extensions/git/src/main.ts b/patched-vscode/extensions/git/src/main.ts index c2d9b974..aa4d98ad 100644 --- a/patched-vscode/extensions/git/src/main.ts +++ b/patched-vscode/extensions/git/src/main.ts @@ -20,7 +20,7 @@ import * as fs from 'fs'; import * as os from 'os'; import { GitTimelineProvider } from './timelineProvider'; import { registerAPICommands } from './api/api1'; -import { TerminalEnvironmentManager } from './terminal'; +import { TerminalEnvironmentManager, TerminalShellExecutionManager } from './terminal'; import { createIPCServer, IPCServer } from './ipc/ipcServer'; import { GitEditor } from './gitEditor'; import { GitPostCommitCommandsProvider } from './postCommitCommands'; @@ -48,7 +48,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, } const info = await findGit(pathHints, gitPath => { - logger.info(l10n.t('Validating found git in: "{0}"', gitPath)); + logger.info(l10n.t('[main] Validating found git in: "{0}"', gitPath)); if (excludes.length === 0) { return true; } @@ -56,7 +56,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const normalized = path.normalize(gitPath).replace(/[\r\n]+$/, ''); const skip = excludes.some(e => normalized.startsWith(e)); if (skip) { - logger.info(l10n.t('Skipped found git in: "{0}"', gitPath)); + logger.info(l10n.t('[main] Skipped found git in: "{0}"', gitPath)); } return !skip; }, logger); @@ -66,7 +66,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, try { ipcServer = await createIPCServer(context.storagePath); } catch (err) { - logger.error(`Failed to create git IPC: ${err}`); + logger.error(`[main] Failed to create git IPC: ${err}`); } const askpass = new Askpass(ipcServer); @@ -79,7 +79,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const terminalEnvironmentManager = new TerminalEnvironmentManager(context, [askpass, gitEditor, ipcServer]); disposables.push(terminalEnvironmentManager); - logger.info(l10n.t('Using git "{0}" from "{1}"', info.version, info.path)); + logger.info(l10n.t('[main] Using git "{0}" from "{1}"', info.version, info.path)); const git = new Git({ gitPath: info.path, @@ -113,7 +113,8 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, new GitFileSystemProvider(model), new GitDecorations(model), new GitTimelineProvider(model, cc), - new GitEditSessionIdentityProvider(model) + new GitEditSessionIdentityProvider(model), + new TerminalShellExecutionManager(model, logger) ); const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); @@ -187,7 +188,7 @@ export async function _activate(context: ExtensionContext): Promise { - logger.appendLine(l10n.t('Log level: {0}', LogLevel[logLevel])); + logger.appendLine(l10n.t('[main] Log level: {0}', LogLevel[logLevel])); }; disposables.push(logger.onDidChangeLogLevel(onDidChangeLogLevel)); onDidChangeLogLevel(logger.logLevel); @@ -212,13 +213,13 @@ export async function _activate(context: ExtensionContext): Promise { + this.logger.info('[Model][doInitialScan] Initial repository scan started'); + const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt'); + this.logger.trace(`[Model][doInitialScan] Settings: autoRepositoryDetection=${autoRepositoryDetection}, openRepositoryInParentFolders=${parentRepositoryConfig}`); + // Initial repository scan function const initialScanFn = () => Promise.all([ this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }), @@ -321,6 +325,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } */ this.telemetryReporter.sendTelemetryEvent('git.repositoryInitialScan', { autoRepositoryDetection: String(autoRepositoryDetection) }, { repositoryCount: this.openRepositories.length }); + this.logger.info(`[Model][doInitialScan] Initial repository scan completed - repositories (${this.repositories.length}), closed repositories (${this.closedRepositories.length}), parent repositories (${this.parentRepositories.length}), unsafe repositories (${this.unsafeRepositories.length})`); } /** @@ -329,47 +334,51 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi * the git.repositoryScanMaxDepth setting. */ private async scanWorkspaceFolders(): Promise { - const config = workspace.getConfiguration('git'); - const autoRepositoryDetection = config.get('autoRepositoryDetection'); - this.logger.trace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`); + try { + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); - if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') { - return; - } + if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') { + return; + } - await Promise.all((workspace.workspaceFolders || []).map(async folder => { - const root = folder.uri.fsPath; - this.logger.trace(`[swsf] Workspace folder: ${root}`); + await Promise.all((workspace.workspaceFolders || []).map(async folder => { + const root = folder.uri.fsPath; + this.logger.trace(`[Model][scanWorkspaceFolders] Workspace folder: ${root}`); - // Workspace folder children - const repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1); - const repositoryScanIgnoredFolders = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanIgnoredFolders', []); + // Workspace folder children + const repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1); + const repositoryScanIgnoredFolders = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanIgnoredFolders', []); - const subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth, repositoryScanIgnoredFolders)); + const subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth, repositoryScanIgnoredFolders)); - // Repository scan folders - const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || []; - this.logger.trace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`); + // Repository scan folders + const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || []; + this.logger.trace(`[Model][scanWorkspaceFolders] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`); - for (const scanPath of scanPaths) { - if (scanPath === '.git') { - this.logger.trace('[swsf] \'.git\' not supported in \'git.scanRepositories\' setting.'); - continue; - } + for (const scanPath of scanPaths) { + if (scanPath === '.git') { + this.logger.trace('[Model][scanWorkspaceFolders] \'.git\' not supported in \'git.scanRepositories\' setting.'); + continue; + } - if (path.isAbsolute(scanPath)) { - const notSupportedMessage = l10n.t('Absolute paths not supported in "git.scanRepositories" setting.'); - this.logger.warn(notSupportedMessage); - console.warn(notSupportedMessage); - continue; - } + if (path.isAbsolute(scanPath)) { + const notSupportedMessage = l10n.t('Absolute paths not supported in "git.scanRepositories" setting.'); + this.logger.warn(`[Model][scanWorkspaceFolders] ${notSupportedMessage}`); + console.warn(notSupportedMessage); + continue; + } - subfolders.add(path.join(root, scanPath)); - } + subfolders.add(path.join(root, scanPath)); + } - this.logger.trace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`); - await Promise.all([...subfolders].map(f => this.openRepository(f))); - })); + this.logger.trace(`[Model][scanWorkspaceFolders] Workspace scan sub folders: [${[...subfolders].join(', ')}]`); + await Promise.all([...subfolders].map(f => this.openRepository(f))); + })); + } + catch (err) { + this.logger.warn(`[Model][scanWorkspaceFolders] Error: ${err}`); + } } private async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number, repositoryScanIgnoredFolders: string[]): Promise { @@ -379,15 +388,26 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi while (foldersToTravers.length > 0) { const currentFolder = foldersToTravers.shift()!; + const children: fs.Dirent[] = []; + try { + children.push(...await fs.promises.readdir(currentFolder.path, { withFileTypes: true })); + + if (currentFolder.depth !== 0) { + result.push(currentFolder.path); + } + } + catch (err) { + this.logger.warn(`[Model][traverseWorkspaceFolder] Unable to read workspace folder '${currentFolder.path}': ${err}`); + continue; + } + if (currentFolder.depth < maxDepth || maxDepth === -1) { - const children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true }); const childrenFolders = children .filter(dirent => dirent.isDirectory() && dirent.name !== '.git' && !repositoryScanIgnoredFolders.find(f => pathEquals(dirent.name, f))) .map(dirent => path.join(currentFolder.path, dirent.name)); - result.push(...childrenFolders); foldersToTravers.push(...childrenFolders.map(folder => { return { path: folder, depth: currentFolder.depth + 1 }; })); @@ -423,23 +443,28 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } private async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise { - const possibleRepositoryFolders = added - .filter(folder => !this.getOpenRepository(folder.uri)); - - const activeRepositoriesList = window.visibleTextEditors - .map(editor => this.getRepository(editor.document.uri)) - .filter(repository => !!repository) as Repository[]; - - const activeRepositories = new Set(activeRepositoriesList); - const openRepositoriesToDispose = removed - .map(folder => this.getOpenRepository(folder.uri)) - .filter(r => !!r) - .filter(r => !activeRepositories.has(r!.repository)) - .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[]; - - openRepositoriesToDispose.forEach(r => r.dispose()); - this.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); - await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); + try { + const possibleRepositoryFolders = added + .filter(folder => !this.getOpenRepository(folder.uri)); + + const activeRepositoriesList = window.visibleTextEditors + .map(editor => this.getRepository(editor.document.uri)) + .filter(repository => !!repository) as Repository[]; + + const activeRepositories = new Set(activeRepositoriesList); + const openRepositoriesToDispose = removed + .map(folder => this.getOpenRepository(folder.uri)) + .filter(r => !!r) + .filter(r => !activeRepositories.has(r!.repository)) + .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[]; + + openRepositoriesToDispose.forEach(r => r.dispose()); + this.logger.trace(`[Model][onDidChangeWorkspaceFolders] Workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); + await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); + } + catch (err) { + this.logger.warn(`[Model][onDidChangeWorkspaceFolders] Error: ${err}`); + } } private onDidChangeConfiguration(): void { @@ -452,50 +477,54 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi .filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true) .map(({ repository }) => repository); - this.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); + this.logger.trace(`[Model][onDidChangeConfiguration] Workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); possibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath)); openRepositoriesToDispose.forEach(r => r.dispose()); } private async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise { - if (!workspace.isTrusted) { - this.logger.trace('[svte] Workspace is not trusted.'); - return; - } - - const config = workspace.getConfiguration('git'); - const autoRepositoryDetection = config.get('autoRepositoryDetection'); - this.logger.trace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`); - - if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') { - return; - } - - await Promise.all(editors.map(async editor => { - const uri = editor.document.uri; - - if (uri.scheme !== 'file') { + try { + if (!workspace.isTrusted) { + this.logger.trace('[Model][onDidChangeVisibleTextEditors] Workspace is not trusted.'); return; } - const repository = this.getRepository(uri); + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); - if (repository) { - this.logger.trace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`); + if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') { return; } - this.logger.trace(`[svte] Open repository for editor resource ${uri.fsPath}`); - await this.openRepository(path.dirname(uri.fsPath)); - })); + await Promise.all(editors.map(async editor => { + const uri = editor.document.uri; + + if (uri.scheme !== 'file') { + return; + } + + const repository = this.getRepository(uri); + + if (repository) { + this.logger.trace(`[Model][onDidChangeVisibleTextEditors] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`); + return; + } + + this.logger.trace(`[Model][onDidChangeVisibleTextEditors] Open repository for editor resource ${uri.fsPath}`); + await this.openRepository(path.dirname(uri.fsPath)); + })); + } + catch (err) { + this.logger.warn(`[Model][onDidChangeVisibleTextEditors] Error: ${err}`); + } } @sequentialize async openRepository(repoPath: string, openIfClosed = false): Promise { - this.logger.trace(`Opening repository: ${repoPath}`); + this.logger.trace(`[Model][openRepository] Repository: ${repoPath}`); const existingRepository = await this.getRepositoryExact(repoPath); if (existingRepository) { - this.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root}`); + this.logger.trace(`[Model][openRepository] Repository for path ${repoPath} already exists: ${existingRepository.root}`); return; } @@ -503,7 +532,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi const enabled = config.get('enabled') === true; if (!enabled) { - this.logger.trace('Git is not enabled'); + this.logger.trace('[Model][openRepository] Git is not enabled'); return; } @@ -513,7 +542,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi fs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK); const result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']); if (result.stderr.trim() === '' && result.stdout.trim() === '') { - this.logger.trace(`Bare repository: ${repoPath}`); + this.logger.trace(`[Model][openRepository] Bare repository: ${repoPath}`); return; } } catch { @@ -523,16 +552,16 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi try { const { repositoryRoot, unsafeRepositoryMatch } = await this.getRepositoryRoot(repoPath); - this.logger.trace(`Repository root for path ${repoPath} is: ${repositoryRoot}`); + this.logger.trace(`[Model][openRepository] Repository root for path ${repoPath} is: ${repositoryRoot}`); const existingRepository = await this.getRepositoryExact(repositoryRoot); if (existingRepository) { - this.logger.trace(`Repository for path ${repositoryRoot} already exists: ${existingRepository.root}`); + this.logger.trace(`[Model][openRepository] Repository for path ${repositoryRoot} already exists: ${existingRepository.root}`); return; } if (this.shouldRepositoryBeIgnored(repositoryRoot)) { - this.logger.trace(`Repository for path ${repositoryRoot} is ignored`); + this.logger.trace(`[Model][openRepository] Repository for path ${repositoryRoot} is ignored`); return; } @@ -541,7 +570,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi if (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) { const isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot); if (isRepositoryOutsideWorkspace) { - this.logger.trace(`Repository in parent folder: ${repositoryRoot}`); + this.logger.trace(`[Model][openRepository] Repository in parent folder: ${repositoryRoot}`); if (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) { // Show a notification if the parent repository is opened after the initial scan @@ -558,7 +587,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Handle unsafe repositories if (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) { - this.logger.trace(`Unsafe repository: ${repositoryRoot}`); + this.logger.trace(`[Model][openRepository] Unsafe repository: ${repositoryRoot}`); // Show a notification if the unsafe repository is opened after the initial scan if (this._state === 'initialized' && !this._unsafeRepositoriesManager.hasRepository(repositoryRoot)) { @@ -572,7 +601,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Handle repositories that were closed by the user if (!openIfClosed && this._closedRepositoriesManager.isRepositoryClosed(repositoryRoot)) { - this.logger.trace(`Repository for path ${repositoryRoot} is closed`); + this.logger.trace(`[Model][openRepository] Repository for path ${repositoryRoot} is closed`); return; } @@ -583,12 +612,14 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this.open(repository); this._closedRepositoriesManager.deleteRepository(repository.root); + this.logger.info(`[Model][openRepository] Opened repository: ${repository.root}`); + // Do not await this, we want SCM // to know about the repo asap repository.status(); } catch (err) { // noop - this.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`); + this.logger.trace(`[Model][openRepository] Opening repository for path='${repoPath}' failed. Error:${err}`); } } @@ -620,7 +651,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi const repositoryRootRealPath = await fs.promises.realpath(repositoryRoot); return !pathEquals(repositoryRoot, repositoryRootRealPath) ? repositoryRootRealPath : undefined; } catch (err) { - this.logger.warn(`Failed to get repository realpath for: "${repositoryRoot}". ${err}`); + this.logger.warn(`[Model][getRepositoryRootRealPath] Failed to get repository realpath for "${repositoryRoot}": ${err}`); return undefined; } } @@ -647,7 +678,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } private open(repository: Repository): void { - this.logger.info(`Open repository: ${repository.root}`); + this.logger.trace(`[Model][open] Repository: ${repository.root}`); const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed); const disappearListener = onDidDisappearRepository(() => dispose()); @@ -664,7 +695,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi const checkForSubmodules = () => { if (!shouldDetectSubmodules) { - this.logger.trace('Automatic detection of git submodules is not enabled.'); + this.logger.trace('[Model][open] Automatic detection of git submodules is not enabled.'); return; } @@ -677,7 +708,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi .slice(0, submodulesLimit) .map(r => path.join(repository.root, r.path)) .forEach(p => { - this.logger.trace(`Opening submodule: '${p}'`); + this.logger.trace(`[Model][open] Opening submodule: '${p}'`); this.eventuallyScanPossibleGitRepository(p); }); }; @@ -739,7 +770,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return; } - this.logger.info(`Close repository: ${repository.root}`); + this.logger.info(`[Model][close] Repository: ${repository.root}`); this._closedRepositoriesManager.addRepository(openRepository.repository.root); openRepository.dispose(); @@ -792,7 +823,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return openRepositoryRealPath?.repository; } catch (err) { - this.logger.warn(`Failed to get repository realpath for: "${repoPath}". ${err}`); + this.logger.warn(`[Model][getRepositoryExact] Failed to get repository realpath for: "${repoPath}". Error:${err}`); return undefined; } } @@ -978,7 +1009,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this._workspaceFolders.set(workspaceFolder.uri.fsPath, result); } catch (err) { // noop - Workspace folder does not exist - this.logger.trace(`Failed to resolve workspace folder: "${workspaceFolder.uri.fsPath}". ${err}`); + this.logger.trace(`[Model][getWorkspaceFolderRealPath] Failed to resolve workspace folder "${workspaceFolder.uri.fsPath}". Error:${err}`); } } diff --git a/patched-vscode/extensions/git/src/operation.ts b/patched-vscode/extensions/git/src/operation.ts index 223f1945..d960cedf 100644 --- a/patched-vscode/extensions/git/src/operation.ts +++ b/patched-vscode/extensions/git/src/operation.ts @@ -141,7 +141,7 @@ export const Operation = { CheckoutTracking: (refLabel: string) => ({ kind: OperationKind.CheckoutTracking, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true, refLabel } as CheckoutTrackingOperation), Clean: (showProgress: boolean) => ({ kind: OperationKind.Clean, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as CleanOperation), Commit: { kind: OperationKind.Commit, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true } as CommitOperation, - Config: (readOnly: boolean) => ({ kind: OperationKind.Config, blocking: false, readOnly, remote: false, retry: false, showProgress: true } as ConfigOperation), + Config: (readOnly: boolean) => ({ kind: OperationKind.Config, blocking: false, readOnly, remote: false, retry: false, showProgress: false } as ConfigOperation), DeleteBranch: { kind: OperationKind.DeleteBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, @@ -149,7 +149,7 @@ export const Operation = { Diff: { kind: OperationKind.Diff, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as DiffOperation, Fetch: (showProgress: boolean) => ({ kind: OperationKind.Fetch, blocking: false, readOnly: false, remote: true, retry: true, showProgress } as FetchOperation), FindTrackingBranches: { kind: OperationKind.FindTrackingBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as FindTrackingBranchesOperation, - GetBranch: { kind: OperationKind.GetBranch, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchOperation, + GetBranch: { kind: OperationKind.GetBranch, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetBranchOperation, GetBranches: { kind: OperationKind.GetBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchesOperation, GetCommitTemplate: { kind: OperationKind.GetCommitTemplate, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetCommitTemplateOperation, GetObjectDetails: { kind: OperationKind.GetObjectDetails, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectDetailsOperation, @@ -158,7 +158,7 @@ export const Operation = { GetRemoteRefs: { kind: OperationKind.GetRemoteRefs, blocking: false, readOnly: true, remote: true, retry: false, showProgress: false } as GetRemoteRefsOperation, HashObject: { kind: OperationKind.HashObject, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as HashObjectOperation, Ignore: { kind: OperationKind.Ignore, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as IgnoreOperation, - Log: { kind: OperationKind.Log, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as LogOperation, + Log: (showProgress: boolean) => ({ kind: OperationKind.Log, blocking: false, readOnly: true, remote: false, retry: false, showProgress }) as LogOperation, LogFile: { kind: OperationKind.LogFile, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as LogFileOperation, Merge: { kind: OperationKind.Merge, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as MergeOperation, MergeAbort: { kind: OperationKind.MergeAbort, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as MergeAbortOperation, @@ -214,7 +214,7 @@ export class OperationManager implements IOperationManager { this.operations.set(operation.kind, new Set([operation])); } - this.logger.trace(`Operation start: ${operation.kind} (blocking: ${operation.blocking}, readOnly: ${operation.readOnly}; retry: ${operation.retry}; showProgress: ${operation.showProgress})`); + this.logger.trace(`[OperationManager][start] ${operation.kind} (blocking: ${operation.blocking}, readOnly: ${operation.readOnly}; retry: ${operation.retry}; showProgress: ${operation.showProgress})`); } end(operation: Operation): void { @@ -226,7 +226,7 @@ export class OperationManager implements IOperationManager { } } - this.logger.trace(`Operation end: ${operation.kind} (blocking: ${operation.blocking}, readOnly: ${operation.readOnly}; retry: ${operation.retry}; showProgress: ${operation.showProgress})`); + this.logger.trace(`[OperationManager][end] ${operation.kind} (blocking: ${operation.blocking}, readOnly: ${operation.readOnly}; retry: ${operation.retry}; showProgress: ${operation.showProgress})`); } getOperations(operationKind: OperationKind): Operation[] { diff --git a/patched-vscode/extensions/git/src/protocolHandler.ts b/patched-vscode/extensions/git/src/protocolHandler.ts index dc73fe39..90491fec 100644 --- a/patched-vscode/extensions/git/src/protocolHandler.ts +++ b/patched-vscode/extensions/git/src/protocolHandler.ts @@ -22,7 +22,7 @@ export class GitProtocolHandler implements UriHandler { } handleUri(uri: Uri): void { - this.logger.info(`GitProtocolHandler.handleUri(${uri.toString()})`); + this.logger.info(`[GitProtocolHandler][handleUri] URI:(${uri.toString()})`); switch (uri.path) { case '/clone': this.clone(uri); @@ -34,17 +34,17 @@ export class GitProtocolHandler implements UriHandler { const ref = data.ref; if (!data.url) { - this.logger.warn('Failed to open URI:' + uri.toString()); + this.logger.warn('[GitProtocolHandler][clone] Failed to open URI:' + uri.toString()); return; } if (Array.isArray(data.url) && data.url.length === 0) { - this.logger.warn('Failed to open URI:' + uri.toString()); + this.logger.warn('[GitProtocolHandler][clone] Failed to open URI:' + uri.toString()); return; } if (ref !== undefined && typeof ref !== 'string') { - this.logger.warn('Failed to open URI due to multiple references:' + uri.toString()); + this.logger.warn('[GitProtocolHandler][clone] Failed to open URI due to multiple references:' + uri.toString()); return; } @@ -69,12 +69,12 @@ export class GitProtocolHandler implements UriHandler { } } catch (ex) { - this.logger.warn('Invalid URI:' + uri.toString()); + this.logger.warn('[GitProtocolHandler][clone] Invalid URI:' + uri.toString()); return; } if (!(await commands.getCommands(true)).includes('git.clone')) { - this.logger.error('Could not complete git clone operation as git installation was not found.'); + this.logger.error('[GitProtocolHandler][clone] Could not complete git clone operation as git installation was not found.'); const errorMessage = l10n.t('Could not clone your repository as Git is not installed.'); const downloadGit = l10n.t('Download Git'); @@ -86,7 +86,7 @@ export class GitProtocolHandler implements UriHandler { return; } else { const cloneTarget = cloneUri.toString(true); - this.logger.info(`Executing git.clone for ${cloneTarget}`); + this.logger.info(`[GitProtocolHandler][clone] Executing git.clone for ${cloneTarget}`); commands.executeCommand('git.clone', cloneTarget, undefined, { ref: ref }); } } diff --git a/patched-vscode/extensions/git/src/repository.ts b/patched-vscode/extensions/git/src/repository.ts index ed959765..8ba56b1a 100644 --- a/patched-vscode/extensions/git/src/repository.ts +++ b/patched-vscode/extensions/git/src/repository.ts @@ -426,8 +426,8 @@ class FileEventLogger { } this.eventDisposable = combinedDisposable([ - this.onWorkspaceWorkingTreeFileChange(uri => this.logger.debug(`[wt] Change: ${uri.fsPath}`)), - this.onDotGitFileChange(uri => this.logger.debug(`[.git] Change: ${uri.fsPath}`)) + this.onWorkspaceWorkingTreeFileChange(uri => this.logger.debug(`[FileEventLogger][onWorkspaceWorkingTreeFileChange] ${uri.fsPath}`)), + this.onDotGitFileChange(uri => this.logger.debug(`[FileEventLogger][onDotGitFileChange] ${uri.fsPath}`)) ]); } @@ -478,7 +478,7 @@ class DotGitWatcher implements IFileWatcher { this.transientDisposables.push(upstreamWatcher); upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables); } catch (err) { - this.logger.warn(`Failed to watch ref '${upstreamPath}', is most likely packed.`); + this.logger.warn(`[DotGitWatcher][updateTransientWatchers] Failed to watch ref '${upstreamPath}', is most likely packed.`); } } @@ -718,6 +718,8 @@ export class Repository implements Disposable { private _untrackedGroup: SourceControlResourceGroup; get untrackedGroup(): GitResourceGroup { return this._untrackedGroup as GitResourceGroup; } + private _EMPTY_TREE: string | undefined; + private _HEAD: Branch | undefined; get HEAD(): Branch | undefined { return this._HEAD; @@ -1046,8 +1048,9 @@ export class Repository implements Disposable { return this.run(Operation.Config(false), () => this.repository.config('local', key, value)); } - log(options?: LogOptions): Promise { - return this.run(Operation.Log, () => this.repository.log(options)); + log(options?: LogOptions & { silent?: boolean }): Promise { + const showProgress = !options || options.silent !== true; + return this.run(Operation.Log(showProgress), () => this.repository.log(options)); } logFile(uri: Uri, options?: LogFileOptions): Promise { @@ -1112,8 +1115,12 @@ export class Repository implements Disposable { return this.run(Operation.Diff, () => this.repository.diffBetweenShortStat(ref1, ref2)); } - getMergeBase(ref1: string, ref2: string): Promise { - return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2)); + diffTrees(treeish1: string, treeish2?: string): Promise { + return this.run(Operation.Diff, () => this.repository.diffTrees(treeish1, treeish2)); + } + + getMergeBase(ref1: string, ref2: string, ...refs: string[]): Promise { + return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2, ...refs)); } async hashObject(data: string): Promise { @@ -1523,7 +1530,7 @@ export class Repository implements Disposable { return upstreamBranch; } catch (err) { - this.logger.warn(`Failed to get branch details for 'refs/remotes/${branch.upstream.remote}/${branch.upstream.name}': ${err.message}.`); + this.logger.warn(`[Repository][getUpstreamBranch] Failed to get branch details for 'refs/remotes/${branch.upstream.remote}/${branch.upstream.name}': ${err.message}.`); return undefined; } } @@ -1602,6 +1609,15 @@ export class Repository implements Disposable { return await this.repository.getCommit(ref); } + async getEmptyTree(): Promise { + if (!this._EMPTY_TREE) { + const result = await this.repository.exec(['hash-object', '-t', 'tree', '/dev/null']); + this._EMPTY_TREE = result.stdout.trim(); + } + + return this._EMPTY_TREE; + } + async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> { return await this.run(Operation.RevList, () => this.repository.getCommitCount(range)); } @@ -1819,7 +1835,7 @@ export class Repository implements Disposable { return true; } - const maybeRebased = await this.run(Operation.Log, async () => { + const maybeRebased = await this.run(Operation.Log(true), async () => { try { const result = await this.repository.exec(['log', '--oneline', '--cherry', `${currentBranch ?? ''}...${currentBranch ?? ''}@{upstream}`, '--']); if (result.exitCode) { @@ -1865,13 +1881,14 @@ export class Repository implements Disposable { const configFiles = workspace.getConfiguration('files', Uri.file(filePath)); const defaultEncoding = configFiles.get('encoding'); const autoGuessEncoding = configFiles.get('autoGuessEncoding'); + const candidateGuessEncodings = configFiles.get('candidateGuessEncodings'); try { - return await this.repository.bufferString(`${ref}:${path}`, defaultEncoding, autoGuessEncoding); + return await this.repository.bufferString(`${ref}:${path}`, defaultEncoding, autoGuessEncoding, candidateGuessEncodings); } catch (err) { if (err.gitErrorCode === GitErrorCodes.WrongCase) { const gitRelativePath = await this.repository.getGitRelativePath(ref, path); - return await this.repository.bufferString(`${ref}:${gitRelativePath}`, defaultEncoding, autoGuessEncoding); + return await this.repository.bufferString(`${ref}:${gitRelativePath}`, defaultEncoding, autoGuessEncoding, candidateGuessEncodings); } throw err; @@ -1942,7 +1959,8 @@ export class Repository implements Disposable { return await this.run(Operation.Ignore, async () => { const ignoreFile = `${this.repository.root}${path.sep}.gitignore`; const textToAppend = files - .map(uri => relativePath(this.repository.root, uri.fsPath).replace(/\\/g, '/')) + .map(uri => relativePath(this.repository.root, uri.fsPath) + .replace(/\\|\[/g, match => match === '\\' ? '/' : `\\${match}`)) .join('\n'); const document = await new Promise(c => fs.exists(ignoreFile, c)) @@ -2408,17 +2426,17 @@ export class Repository implements Disposable { const autorefresh = config.get('autorefresh'); if (!autorefresh) { - this.logger.trace('Skip running git status because autorefresh setting is disabled.'); + this.logger.trace('[Repository][onFileChange] Skip running git status because autorefresh setting is disabled.'); return; } if (this.isRepositoryHuge) { - this.logger.trace('Skip running git status because repository is huge.'); + this.logger.trace('[Repository][onFileChange] Skip running git status because repository is huge.'); return; } if (!this.operations.isIdle()) { - this.logger.trace('Skip running git status because an operation is running.'); + this.logger.trace('[Repository][onFileChange] Skip running git status because an operation is running.'); return; } diff --git a/patched-vscode/extensions/git/src/terminal.ts b/patched-vscode/extensions/git/src/terminal.ts index 4f6d9548..05a4366d 100644 --- a/patched-vscode/extensions/git/src/terminal.ts +++ b/patched-vscode/extensions/git/src/terminal.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, l10n, workspace } from 'vscode'; -import { filterEvent, IDisposable } from './util'; +import { ExtensionContext, l10n, LogOutputChannel, TerminalShellExecutionEndEvent, window, workspace } from 'vscode'; +import { dispose, filterEvent, IDisposable } from './util'; +import { Model } from './model'; export interface ITerminalEnvironmentProvider { featureDescription?: string; @@ -50,3 +51,42 @@ export class TerminalEnvironmentManager { this.disposable.dispose(); } } + +export class TerminalShellExecutionManager { + private readonly subcommands = new Set([ + 'add', 'branch', 'checkout', 'cherry-pick', 'clean', 'commit', 'fetch', 'merge', + 'mv', 'rebase', 'reset', 'restore', 'revert', 'rm', 'pull', 'push', 'stash', 'switch']); + + private readonly disposables: IDisposable[] = []; + + constructor( + private readonly model: Model, + private readonly logger: LogOutputChannel + ) { + window.onDidEndTerminalShellExecution(this.onDidEndTerminalShellExecution, this, this.disposables); + } + + private onDidEndTerminalShellExecution(e: TerminalShellExecutionEndEvent): void { + const { execution, exitCode, shellIntegration } = e; + const [executable, subcommand] = execution.commandLine.value.split(/\s+/); + const cwd = execution.cwd ?? shellIntegration.cwd; + + if (executable.toLowerCase() !== 'git' || !this.subcommands.has(subcommand.toLowerCase()) || !cwd || exitCode !== 0) { + return; + } + + this.logger.trace(`[TerminalShellExecutionManager][onDidEndTerminalShellExecution] Matched git subcommand: ${subcommand}`); + + const repository = this.model.getRepository(cwd); + if (!repository) { + this.logger.trace(`[TerminalShellExecutionManager][onDidEndTerminalShellExecution] Unable to find repository for current working directory: ${cwd.toString()}`); + return; + } + + repository.status(); + } + + dispose(): void { + dispose(this.disposables); + } +} diff --git a/patched-vscode/extensions/git/tsconfig.json b/patched-vscode/extensions/git/tsconfig.json index 6ca99fec..75fb8217 100644 --- a/patched-vscode/extensions/git/tsconfig.json +++ b/patched-vscode/extensions/git/tsconfig.json @@ -21,6 +21,7 @@ "../../src/vscode-dts/vscode.proposed.tabInputMultiDiff.d.ts", "../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts", "../../src/vscode-dts/vscode.proposed.timeline.d.ts", + "../../src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts", "../types/lib.textEncoder.d.ts" ] } diff --git a/patched-vscode/extensions/git/yarn.lock b/patched-vscode/extensions/git/yarn.lock index 266157e9..a7e1a693 100644 --- a/patched-vscode/extensions/git/yarn.lock +++ b/patched-vscode/extensions/git/yarn.lock @@ -182,10 +182,10 @@ isexe@^3.1.1: resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== -jschardet@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882" - integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ== +jschardet@3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.1.3.tgz#10c2289fdae91a0aa9de8bba9c59055fd78898d3" + integrity sha512-Q1PKVMK/uu+yjdlobgWIYkUOCR1SqUmW9m/eUJNNj4zI2N12i25v8fYpVf+zCakQeaTdBdhnZTFbVIAVZIVVOg== peek-readable@^4.1.0: version "4.1.0" diff --git a/patched-vscode/extensions/github-authentication/package.json b/patched-vscode/extensions/github-authentication/package.json index 2d2bea56..7fcbc7f1 100644 --- a/patched-vscode/extensions/github-authentication/package.json +++ b/patched-vscode/extensions/github-authentication/package.json @@ -38,7 +38,7 @@ "id": "github-enterprise" } ], - "configuration": { + "configuration": [{ "title": "GitHub Enterprise Server Authentication Provider", "properties": { "github-enterprise.uri": { @@ -46,7 +46,17 @@ "description": "GitHub Enterprise Server URI" } } + }, + { + "title": "GitHub Authentication", + "properties": { + "github.experimental.multipleAccounts": { + "type": "boolean", + "description": "Experimental support for multiple GitHub accounts" + } + } } + ] }, "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", diff --git a/patched-vscode/extensions/github-authentication/src/flows.ts b/patched-vscode/extensions/github-authentication/src/flows.ts index 7498a2b2..a2497b2b 100644 --- a/patched-vscode/extensions/github-authentication/src/flows.ts +++ b/patched-vscode/extensions/github-authentication/src/flows.ts @@ -173,6 +173,8 @@ const allFlows: IFlow[] = [ ]); if (existingLogin) { searchParams.append('login', existingLogin); + } else { + searchParams.append('prompt', 'select_account'); } // The extra toString, parse is apparently needed for env.openExternal @@ -240,6 +242,8 @@ const allFlows: IFlow[] = [ ]); if (existingLogin) { searchParams.append('login', existingLogin); + } else { + searchParams.append('prompt', 'select_account'); } const loginUrl = baseUri.with({ diff --git a/patched-vscode/extensions/github-authentication/src/github.ts b/patched-vscode/extensions/github-authentication/src/github.ts index 15fe2ef0..ed584c65 100644 --- a/patched-vscode/extensions/github-authentication/src/github.ts +++ b/patched-vscode/extensions/github-authentication/src/github.ts @@ -97,6 +97,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid private readonly _keychain: Keychain; private readonly _accountsSeen = new Set(); private readonly _disposable: vscode.Disposable | undefined; + private _supportsMultipleAccounts = false; private _sessionsPromise: Promise; @@ -133,10 +134,24 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid return sessions; }); + this._supportsMultipleAccounts = vscode.workspace.getConfiguration('github.experimental').get('multipleAccounts', false); + this._disposable = vscode.Disposable.from( this._telemetryReporter, - vscode.authentication.registerAuthenticationProvider(type, this._githubServer.friendlyName, this, { supportsMultipleAccounts: false }), - this.context.secrets.onDidChange(() => this.checkForUpdates()) + vscode.authentication.registerAuthenticationProvider(type, this._githubServer.friendlyName, this, { supportsMultipleAccounts: this._supportsMultipleAccounts }), + this.context.secrets.onDidChange(() => this.checkForUpdates()), + vscode.workspace.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration('github.experimental.multipleAccounts')) { + const newValue = vscode.workspace.getConfiguration('github.experimental').get('multipleAccounts', false); + if (newValue === this._supportsMultipleAccounts) { + return; + } + const result = await vscode.window.showInformationMessage(vscode.l10n.t('Please reload the window to apply the new setting.'), { modal: true }, vscode.l10n.t('Reload Window')); + if (result) { + vscode.commands.executeCommand('workbench.action.reloadWindow'); + } + } + }) ); } @@ -148,14 +163,17 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid return this._sessionChangeEmitter.event; } - async getSessions(scopes?: string[]): Promise { + async getSessions(scopes: string[] | undefined, options?: vscode.AuthenticationProviderSessionOptions): Promise { // For GitHub scope list, order doesn't matter so we immediately sort the scopes const sortedScopes = scopes?.sort() || []; this._logger.info(`Getting sessions for ${sortedScopes.length ? sortedScopes.join(',') : 'all scopes'}...`); const sessions = await this._sessionsPromise; - const finalSessions = sortedScopes.length - ? sessions.filter(session => arrayEquals([...session.scopes].sort(), sortedScopes)) + const accountFilteredSessions = options?.account + ? sessions.filter(session => session.account.label === options.account?.label) : sessions; + const finalSessions = sortedScopes.length + ? accountFilteredSessions.filter(session => arrayEquals([...session.scopes].sort(), sortedScopes)) + : accountFilteredSessions; this._logger.info(`Got ${finalSessions.length} sessions for ${sortedScopes?.join(',') ?? 'all scopes'}...`); return finalSessions; @@ -226,7 +244,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid const sessionPromises = sessionData.map(async (session: SessionData) => { // For GitHub scope list, order doesn't matter so we immediately sort the scopes const scopesStr = [...session.scopes].sort().join(' '); - if (scopesSeen.has(scopesStr)) { + if (!this._supportsMultipleAccounts && scopesSeen.has(scopesStr)) { return undefined; } let userInfo: { id: string; accountName: string } | undefined; @@ -279,7 +297,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid this._logger.info(`Stored ${sessions.length} sessions!`); } - public async createSession(scopes: string[]): Promise { + public async createSession(scopes: string[], options?: vscode.AuthenticationProviderSessionOptions): Promise { try { // For GitHub scope list, order doesn't matter so we use a sorted scope to determine // if we've got a session already. @@ -298,14 +316,20 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid const sessions = await this._sessionsPromise; - const accounts = new Set(sessions.map(session => session.account.label)); - const existingLogin = accounts.size <= 1 ? sessions[0]?.account.label : await vscode.window.showQuickPick([...accounts], { placeHolder: 'Choose an account that you would like to log in to' }); + // First we use the account specified in the options, otherwise we use the first account we have to seed auth. + const loginWith = options?.account?.label ?? sessions[0]?.account.label; + this._logger.info(`Logging in with '${loginWith ? loginWith : 'any'}' account...`); + const scopeString = sortedScopes.join(' '); - const token = await this._githubServer.login(scopeString, existingLogin); + const token = await this._githubServer.login(scopeString, loginWith); const session = await this.tokenToSession(token, scopes); this.afterSessionLoad(session); - const sessionIndex = sessions.findIndex(s => s.id === session.id || arrayEquals([...s.scopes].sort(), sortedScopes)); + const sessionIndex = sessions.findIndex( + this._supportsMultipleAccounts + ? s => s.account.id === session.account.id && arrayEquals([...s.scopes].sort(), sortedScopes) + : s => s.id === session.id || arrayEquals([...s.scopes].sort(), sortedScopes) + ); const removed = new Array(); if (sessionIndex > -1) { removed.push(...sessions.splice(sessionIndex, 1, session)); diff --git a/patched-vscode/extensions/github-authentication/src/githubServer.ts b/patched-vscode/extensions/github-authentication/src/githubServer.ts index af2cf227..c9f0a8c0 100644 --- a/patched-vscode/extensions/github-authentication/src/githubServer.ts +++ b/patched-vscode/extensions/github-authentication/src/githubServer.ts @@ -197,7 +197,7 @@ export class GitHubServer implements IGitHubServer { throw new Error(`${result.status} ${result.statusText}`); } } catch (e) { - this._logger.warn('Failed to delete token from server.' + e.message ?? e); + this._logger.warn('Failed to delete token from server.' + (e.message ?? e)); } } diff --git a/patched-vscode/extensions/go/cgmanifest.json b/patched-vscode/extensions/go/cgmanifest.json index fc3c741c..bd8f2d61 100644 --- a/patched-vscode/extensions/go/cgmanifest.json +++ b/patched-vscode/extensions/go/cgmanifest.json @@ -6,12 +6,12 @@ "git": { "name": "go-syntax", "repositoryUrl": "https://github.com/worlpaker/go-syntax", - "commitHash": "254bd0f25182c86ffd2043824f8d003e11a34268" + "commitHash": "21f28840e04d4fa04682d19d6fe64de437f40b64" } }, "license": "MIT", "description": "The file syntaxes/go.tmLanguage.json is from https://github.com/worlpaker/go-syntax, which in turn was derived from https://github.com/jeff-hykin/better-go-syntax.", - "version": "0.6.6" + "version": "0.7.5" } ], "version": 1 diff --git a/patched-vscode/extensions/go/syntaxes/go.tmLanguage.json b/patched-vscode/extensions/go/syntaxes/go.tmLanguage.json index 083d4ffb..b8a6604d 100644 --- a/patched-vscode/extensions/go/syntaxes/go.tmLanguage.json +++ b/patched-vscode/extensions/go/syntaxes/go.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/worlpaker/go-syntax/commit/254bd0f25182c86ffd2043824f8d003e11a34268", + "version": "https://github.com/worlpaker/go-syntax/commit/21f28840e04d4fa04682d19d6fe64de437f40b64", "name": "Go", "scopeName": "source.go", "patterns": [ @@ -32,6 +32,9 @@ }, { "include": "#group-variables" + }, + { + "include": "#field_hover" } ] }, @@ -318,7 +321,7 @@ "name": "punctuation.definition.begin.bracket.square.go" } }, - "end": "(?:(\\])((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?!(?:[\\[\\]\\*]+)?\\b(?:func|struct|map)\\b)(?:[\\*\\[\\]]+)?(?:[\\w\\.]+))?)", + "end": "(?:(\\])((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?!(?:[\\[\\]\\*]+)?\\b(?:func|struct|map)\\b)(?:[\\*\\[\\]]+)?(?:[\\w\\.]+)(?:\\[(?:(?:[\\w\\.\\*\\[\\]\\{\\}]+)(?:(?:\\,\\s*(?:[\\w\\.\\*\\[\\]\\{\\}]+))*))?\\])?)?)", "endCaptures": { "1": { "name": "punctuation.definition.end.bracket.square.go" @@ -1859,7 +1862,7 @@ }, { "comment": "property variables and types", - "match": "(?:((?:(?:\\w+\\,\\s*)+)?(?:\\w+\\s+))([\\s\\S]+))", + "match": "(?:((?:(?:\\w+\\,\\s*)+)?(?:\\w+\\s+))([^\\`]+))", "captures": { "1": { "patterns": [ @@ -2004,6 +2007,29 @@ } ] }, + { + "comment": "one type only with multi line raw string", + "begin": "(?:((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?(?\\&\\|\\%\\*]+)?)+)?(\\))))", - "captures": { + "begin": "(?:(\\bmake\\b)(?:(\\()((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?:[\\[\\]\\*]+)?(?:(?!\\bmap\\b)(?:[\\w\\.]+))?(\\[(?:(?:[\\S]+)(?:(?:\\,\\s*(?:[\\S]+))*))?\\])?(?:\\,)?)?))", + "beginCaptures": { "1": { "name": "entity.name.function.support.builtin.go" }, @@ -2398,18 +2427,19 @@ "name": "entity.name.type.go" } ] - }, - "4": { - "patterns": [ - { - "include": "$self" - } - ] - }, - "5": { + } + }, + "end": "\\)", + "endCaptures": { + "0": { "name": "punctuation.definition.end.bracket.round.go" } - } + }, + "patterns": [ + { + "include": "$self" + } + ] } ] }, @@ -2435,7 +2465,7 @@ }, "switch_types": { "comment": "switch type assertions, only highlights types after case keyword", - "begin": "(?<=\\bswitch\\b)(?:\\s*)(?:(\\w+\\s*\\:\\=)?\\s*([\\w\\.\\*\\(\\)\\[\\]]+))(\\.\\(\\btype\\b\\)\\s*)(\\{)", + "begin": "(?<=\\bswitch\\b)(?:\\s*)(?:(\\w+\\s*\\:\\=)?\\s*([\\w\\.\\*\\(\\)\\[\\]\\+/\\-\\%\\<\\>\\|\\&]+))(\\.\\(\\btype\\b\\)\\s*)(\\{)", "beginCaptures": { "1": { "patterns": [ @@ -2766,7 +2796,7 @@ }, "slice_index_variables": { "comment": "slice index and capacity variables, to not scope them as property variables", - "match": "(?<=\\w\\[)((?:(?:\\b[\\w\\.\\*\\+/\\-\\*\\%\\<\\>\\|\\&]+\\:)|(?:\\:\\b[\\w\\.\\*\\+/\\-\\*\\%\\<\\>\\|\\&]+))(?:\\b[\\w\\.\\*\\+/\\-\\*\\%\\<\\>\\|\\&]+)?(?:\\:\\b[\\w\\.\\*\\+/\\-\\*\\%\\<\\>\\|\\&]+)?)(?=\\])", + "match": "(?<=\\w\\[)((?:(?:\\b[\\w\\.\\*\\+/\\-\\%\\<\\>\\|\\&]+\\:)|(?:\\:\\b[\\w\\.\\*\\+/\\-\\%\\<\\>\\|\\&]+))(?:\\b[\\w\\.\\*\\+/\\-\\%\\<\\>\\|\\&]+)?(?:\\:\\b[\\w\\.\\*\\+/\\-\\%\\<\\>\\|\\&]+)?)(?=\\])", "captures": { "1": { "patterns": [ @@ -2782,8 +2812,8 @@ } }, "property_variables": { - "comment": "Property variables in struct | parameter field in struct initialization", - "match": "(?:(?:((?:\\b[\\w\\.]+)(?:\\:(?!\\=))))(?:(?:\\s*([\\w\\.\\*\\&\\[\\]]+)(\\.\\w+)(?![\\w\\.\\*\\&\\[\\]]*(?:\\{|\\()))((?:\\s*(?:\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\|\\||\\&\\&|\\+|/|\\-|\\*|\\%|\\||\\&)\\s*(?:[\\w\\.\\*\\&\\[\\]]+)(?:\\.\\w+)(?![\\w\\.\\*\\&\\[\\]]*(?:\\{|\\()))*))?)", + "comment": "Property variables in struct", + "match": "((?:\\b[\\w\\.]+)(?:\\:(?!\\=)))", "captures": { "1": { "patterns": [ @@ -2795,68 +2825,6 @@ "name": "variable.other.property.go" } ] - }, - "2": { - "patterns": [ - { - "include": "#type-declarations" - }, - { - "match": "\\w+", - "name": "variable.other.go" - }, - { - "include": "$self" - } - ] - }, - "3": { - "patterns": [ - { - "include": "#type-declarations" - }, - { - "match": "\\w+", - "name": "variable.other.property.field.go" - }, - { - "include": "$self" - } - ] - }, - "4": { - "patterns": [ - { - "match": "([\\w\\.\\*\\&\\[\\]]+)(\\.\\w+)", - "captures": { - "1": { - "patterns": [ - { - "include": "#type-declarations" - }, - { - "match": "\\w+", - "name": "variable.other.go" - } - ] - }, - "2": { - "patterns": [ - { - "include": "#type-declarations" - }, - { - "match": "\\w+", - "name": "variable.other.property.field.go" - } - ] - } - } - }, - { - "include": "$self" - } - ] } } }, @@ -2910,6 +2878,41 @@ } } }, + "field_hover": { + "comment": "struct field property and types when hovering with the mouse", + "match": "(?:(?<=^\\bfield\\b)\\s+([\\w\\*\\.]+)\\s+([\\s\\S]+))", + "captures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.property.go" + } + ] + }, + "2": { + "patterns": [ + { + "match": "\\binvalid\\b\\s+\\btype\\b", + "name": "invalid.field.go" + }, + { + "include": "#type-declarations-without-brackets" + }, + { + "include": "#parameter-variable-types" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + } + } + }, "other_variables": { "comment": "all other variables", "match": "\\w+", diff --git a/patched-vscode/extensions/grunt/src/main.ts b/patched-vscode/extensions/grunt/src/main.ts index 886e7e27..fd99ba33 100644 --- a/patched-vscode/extensions/grunt/src/main.ts +++ b/patched-vscode/extensions/grunt/src/main.ts @@ -316,7 +316,7 @@ class TaskDetector { if (this.detectors.size === 0) { return Promise.resolve([]); } else if (this.detectors.size === 1) { - return this.detectors.values().next().value.getTasks(); + return this.detectors.values().next().value!.getTasks(); } else { const promises: Promise[] = []; for (const detector of this.detectors.values()) { @@ -338,7 +338,7 @@ class TaskDetector { if (this.detectors.size === 0) { return undefined; } else if (this.detectors.size === 1) { - return this.detectors.values().next().value.getTask(task); + return this.detectors.values().next().value!.getTask(task); } else { if ((task.scope === vscode.TaskScope.Workspace) || (task.scope === vscode.TaskScope.Global)) { return undefined; diff --git a/patched-vscode/extensions/gulp/src/main.ts b/patched-vscode/extensions/gulp/src/main.ts index 28417574..b0b85ca2 100644 --- a/patched-vscode/extensions/gulp/src/main.ts +++ b/patched-vscode/extensions/gulp/src/main.ts @@ -357,7 +357,7 @@ class TaskDetector { if (this.detectors.size === 0) { return Promise.resolve([]); } else if (this.detectors.size === 1) { - return this.detectors.values().next().value.getTasks(); + return this.detectors.values().next().value!.getTasks(); } else { const promises: Promise[] = []; for (const detector of this.detectors.values()) { @@ -379,7 +379,7 @@ class TaskDetector { if (this.detectors.size === 0) { return undefined; } else if (this.detectors.size === 1) { - return this.detectors.values().next().value.getTask(task); + return this.detectors.values().next().value!.getTask(task); } else { if ((task.scope === vscode.TaskScope.Workspace) || (task.scope === vscode.TaskScope.Global)) { // Not supported, we don't have enough info to create the task. diff --git a/patched-vscode/extensions/html-language-features/client/src/browser/htmlClientMain.ts b/patched-vscode/extensions/html-language-features/client/src/browser/htmlClientMain.ts index 3f10e6d1..06997d39 100644 --- a/patched-vscode/extensions/html-language-features/client/src/browser/htmlClientMain.ts +++ b/patched-vscode/extensions/html-language-features/client/src/browser/htmlClientMain.ts @@ -8,13 +8,6 @@ import { LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor, AsyncDisposable } from '../htmlClient'; import { LanguageClient } from 'vscode-languageclient/browser'; -declare const Worker: { - new(stringUrl: string): any; -}; -declare const TextDecoder: { - new(encoding?: string): { decode(buffer: ArrayBuffer): string }; -}; - let client: AsyncDisposable | undefined; // this method is called when vs code is activated @@ -25,7 +18,7 @@ export async function activate(context: ExtensionContext) { worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { - return new LanguageClient(id, name, clientOptions, worker); + return new LanguageClient(id, name, worker, clientOptions); }; const timer = { diff --git a/patched-vscode/extensions/html-language-features/client/tsconfig.json b/patched-vscode/extensions/html-language-features/client/tsconfig.json index 8f5cef74..349af163 100644 --- a/patched-vscode/extensions/html-language-features/client/tsconfig.json +++ b/patched-vscode/extensions/html-language-features/client/tsconfig.json @@ -1,7 +1,10 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "lib": [ + "webworker" + ] }, "include": [ "src/**/*", diff --git a/patched-vscode/extensions/html-language-features/package.json b/patched-vscode/extensions/html-language-features/package.json index 49489ff2..91382640 100644 --- a/patched-vscode/extensions/html-language-features/package.json +++ b/patched-vscode/extensions/html-language-features/package.json @@ -259,7 +259,7 @@ }, "dependencies": { "@vscode/extension-telemetry": "^0.9.0", - "vscode-languageclient": "^10.0.0-next.3", + "vscode-languageclient": "10.0.0-next.8", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/patched-vscode/extensions/html-language-features/schemas/package.schema.json b/patched-vscode/extensions/html-language-features/schemas/package.schema.json index ef717dbd..205143c3 100644 --- a/patched-vscode/extensions/html-language-features/schemas/package.schema.json +++ b/patched-vscode/extensions/html-language-features/schemas/package.schema.json @@ -1,6 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HTML contributions to package.json", "type": "object", "properties": { "contributes": { diff --git a/patched-vscode/extensions/html-language-features/server/package.json b/patched-vscode/extensions/html-language-features/server/package.json index 75bfa00d..3698bf8f 100644 --- a/patched-vscode/extensions/html-language-features/server/package.json +++ b/patched-vscode/extensions/html-language-features/server/package.json @@ -10,10 +10,10 @@ "main": "./out/node/htmlServerMain", "dependencies": { "@vscode/l10n": "^0.0.18", - "vscode-css-languageservice": "^6.2.13", - "vscode-html-languageservice": "^5.2.0", - "vscode-languageserver": "^10.0.0-next.2", - "vscode-languageserver-textdocument": "^1.0.11", + "vscode-css-languageservice": "^6.3.1", + "vscode-html-languageservice": "^5.3.1", + "vscode-languageserver": "10.0.0-next.6", + "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/patched-vscode/extensions/html-language-features/server/yarn.lock b/patched-vscode/extensions/html-language-features/server/yarn.lock index f327f1f3..1d915ccf 100644 --- a/patched-vscode/extensions/html-language-features/server/yarn.lock +++ b/patched-vscode/extensions/html-language-features/server/yarn.lock @@ -24,60 +24,60 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -vscode-css-languageservice@^6.2.13: - version "6.2.13" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.13.tgz#c7c2dc7a081a203048d60157c65536767d6d96f8" - integrity sha512-2rKWXfH++Kxd9Z4QuEgd1IF7WmblWWU7DScuyf1YumoGLkY9DW6wF/OTlhOyO2rN63sWHX2dehIpKBbho4ZwvA== +vscode-css-languageservice@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.3.1.tgz#56733c90686db56855ccc156a534a68b8c1f2187" + integrity sha512-1BzTBuJfwMc3A0uX4JBdJgoxp74cjj4q2mDJdp49yD/GuAq4X0k5WtK6fNcMYr+FfJ9nqgR6lpfCSZDkARJ5qQ== dependencies: "@vscode/l10n" "^0.0.18" - vscode-languageserver-textdocument "^1.0.11" + vscode-languageserver-textdocument "^1.0.12" vscode-languageserver-types "3.17.5" vscode-uri "^3.0.8" -vscode-html-languageservice@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.2.0.tgz#5b36f9131acc073cebaa2074dc8ff53e84c80f31" - integrity sha512-cdNMhyw57/SQzgUUGSIMQ66jikqEN6nBNyhx5YuOyj9310+eY9zw8Q0cXpiKzDX8aHYFewQEXRnigl06j/TVwQ== +vscode-html-languageservice@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.3.1.tgz#93cac1cebb42165b52a15220f02c47d1320fc43a" + integrity sha512-ysUh4hFeW/WOWz/TO9gm08xigiSsV/FOAZ+DolgJfeLftna54YdmZ4A+lIn46RbdO3/Qv5QHTn1ZGqmrXQhZyA== dependencies: "@vscode/l10n" "^0.0.18" - vscode-languageserver-textdocument "^1.0.11" + vscode-languageserver-textdocument "^1.0.12" vscode-languageserver-types "^3.17.5" vscode-uri "^3.0.8" -vscode-jsonrpc@9.0.0-next.2: - version "9.0.0-next.2" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.2.tgz#29e9741c742c80329bba1c60ce38fd014651ba80" - integrity sha512-meIaXAgChCHzWy45QGU8YpCNyqnZQ/sYeCj32OLDDbUYsCF7AvgpdXx3nnZn9yzr8ed0Od9bW+NGphEmXsqvIQ== +vscode-jsonrpc@9.0.0-next.4: + version "9.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz#ba403ddb3b82ca578179963dbe08e120a935f50d" + integrity sha512-zSVIr58lJSMYKIsZ5P7GtBbv1eEx25eNyOf0NmEzxmn1GhUNJAVAb5hkA1poKUwj1FRMwN6CeyWxZypmr8SsQQ== -vscode-languageserver-protocol@3.17.6-next.3: - version "3.17.6-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.3.tgz#09d3e28e9ad12270233d07fa0b69cf1d51d7dfe4" - integrity sha512-H8ATH5SAvc3JzttS+AL6g681PiBOZM/l34WP2JZk4akY3y7NqTP+f9cJ+MhrVBbD3aDS8bdAKewZgbFLW6M8Pg== +vscode-languageserver-protocol@3.17.6-next.6: + version "3.17.6-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.6.tgz#8863a4dc8b395a8c31106ffdc945a00f9163b68b" + integrity sha512-naxM9kc/phpl0kAFNVPejMUWUtzFXdPYY/BtQTYtfbBbHf8sceHOrKkmf6yynZRu1A4oFtRZNqV3wyFRTWqUHw== dependencies: - vscode-jsonrpc "9.0.0-next.2" - vscode-languageserver-types "3.17.6-next.3" + vscode-jsonrpc "9.0.0-next.4" + vscode-languageserver-types "3.17.6-next.4" -vscode-languageserver-textdocument@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf" - integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA== +vscode-languageserver-textdocument@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631" + integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA== vscode-languageserver-types@3.17.5, vscode-languageserver-types@^3.17.5: version "3.17.5" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== -vscode-languageserver-types@3.17.6-next.3: - version "3.17.6-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.3.tgz#f71d6c57f18d921346cfe0c227aabd72eb8cd2f0" - integrity sha512-l5kNFXFRQGuzriXpuBqFpRmkf6f6A4VoU3h95OsVkqIOoi1k7KbwSo600cIdsKSJWrPg/+vX+QMPcMw1oI7ItA== +vscode-languageserver-types@3.17.6-next.4: + version "3.17.6-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz#6670939eb98f00aa7b05021dc3dd7fe9aa4453ea" + integrity sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q== -vscode-languageserver@^10.0.0-next.2: - version "10.0.0-next.2" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-10.0.0-next.2.tgz#9a8ac58f72979961497c4fd7f6097561d4134d5f" - integrity sha512-WZdK/XO6EkNU6foYck49NpS35sahWhYFs4hwCGalH/6lhPmdUKABTnWioK/RLZKWqH8E5HdlAHQMfSBIxKBV9Q== +vscode-languageserver@10.0.0-next.6: + version "10.0.0-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-10.0.0-next.6.tgz#0db118a93fe010c6b40cd04e91a15d09e7b60b60" + integrity sha512-0Lh1nhQfSxo5Ob+ayYO1QTIsDix2/Lc72Urm1KZrCFxK5zIFYaEh3QFeM9oZih4Rzs0ZkQPXXnoHtpvs5GT+Zw== dependencies: - vscode-languageserver-protocol "3.17.6-next.3" + vscode-languageserver-protocol "3.17.6-next.6" vscode-uri@^3.0.8: version "3.0.8" diff --git a/patched-vscode/extensions/html-language-features/yarn.lock b/patched-vscode/extensions/html-language-features/yarn.lock index d1d73407..46688b66 100644 --- a/patched-vscode/extensions/html-language-features/yarn.lock +++ b/patched-vscode/extensions/html-language-features/yarn.lock @@ -149,32 +149,32 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -vscode-jsonrpc@9.0.0-next.2: - version "9.0.0-next.2" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.2.tgz#29e9741c742c80329bba1c60ce38fd014651ba80" - integrity sha512-meIaXAgChCHzWy45QGU8YpCNyqnZQ/sYeCj32OLDDbUYsCF7AvgpdXx3nnZn9yzr8ed0Od9bW+NGphEmXsqvIQ== +vscode-jsonrpc@9.0.0-next.4: + version "9.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz#ba403ddb3b82ca578179963dbe08e120a935f50d" + integrity sha512-zSVIr58lJSMYKIsZ5P7GtBbv1eEx25eNyOf0NmEzxmn1GhUNJAVAb5hkA1poKUwj1FRMwN6CeyWxZypmr8SsQQ== -vscode-languageclient@^10.0.0-next.3: - version "10.0.0-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-10.0.0-next.3.tgz#d7336bafafb37569ac1d8e931d20ba2a6385cc64" - integrity sha512-jJhPdZaiELpPRnCUt8kQcF2HJuvzLgeW4HOGc6dp8Je+p08ndueVT4fpSsbly6KiEHr/Ri73tNz0CSfsOye6MA== +vscode-languageclient@10.0.0-next.8: + version "10.0.0-next.8" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-10.0.0-next.8.tgz#5afa0ced3b2ac68d31cc1c48edc4f289744542a0" + integrity sha512-D9inIHgqKayO9Tv0MeLb3XIL76yTuWmKdHqcGZKzjtQrMGJgASJDYWTapu+yAjEpDp0gmVOaCYyIlLB86ncDoQ== dependencies: minimatch "^9.0.3" semver "^7.6.0" - vscode-languageserver-protocol "3.17.6-next.4" + vscode-languageserver-protocol "3.17.6-next.6" -vscode-languageserver-protocol@3.17.6-next.4: - version "3.17.6-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.4.tgz#3c56f6eb588bb42fccc0ac54a0d5daf2d02f0a1b" - integrity sha512-/2bleKBxZLyRObS4mkpaWlVI9xGiUqMVmh/ztZ2vL4uP2XyIpraT45JBpn9AtXr0alqKJPKLuKr+/qcYULvm/w== +vscode-languageserver-protocol@3.17.6-next.6: + version "3.17.6-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.6.tgz#8863a4dc8b395a8c31106ffdc945a00f9163b68b" + integrity sha512-naxM9kc/phpl0kAFNVPejMUWUtzFXdPYY/BtQTYtfbBbHf8sceHOrKkmf6yynZRu1A4oFtRZNqV3wyFRTWqUHw== dependencies: - vscode-jsonrpc "9.0.0-next.2" - vscode-languageserver-types "3.17.6-next.3" + vscode-jsonrpc "9.0.0-next.4" + vscode-languageserver-types "3.17.6-next.4" -vscode-languageserver-types@3.17.6-next.3: - version "3.17.6-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.3.tgz#f71d6c57f18d921346cfe0c227aabd72eb8cd2f0" - integrity sha512-l5kNFXFRQGuzriXpuBqFpRmkf6f6A4VoU3h95OsVkqIOoi1k7KbwSo600cIdsKSJWrPg/+vX+QMPcMw1oI7ItA== +vscode-languageserver-types@3.17.6-next.4: + version "3.17.6-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz#6670939eb98f00aa7b05021dc3dd7fe9aa4453ea" + integrity sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q== vscode-uri@^3.0.8: version "3.0.8" diff --git a/patched-vscode/extensions/ipynb/package.json b/patched-vscode/extensions/ipynb/package.json index d923904e..d881eb8c 100644 --- a/patched-vscode/extensions/ipynb/package.json +++ b/patched-vscode/extensions/ipynb/package.json @@ -15,7 +15,8 @@ ], "activationEvents": [ "onNotebook:jupyter-notebook", - "onNotebookSerializer:interactive" + "onNotebookSerializer:interactive", + "onNotebookSerializer:repl" ], "extensionKind": [ "workspace", @@ -61,6 +62,11 @@ "command": "notebook.cellOutput.copy", "title": "%copyCellOutput.title%", "category": "Notebook" + }, + { + "command": "notebook.cellOutput.openInTextEditor", + "title": "%openCellOutput.title%", + "category": "Notebook" } ], "notebooks": [ @@ -107,12 +113,24 @@ { "command": "notebook.cellOutput.copy", "when": "notebookCellHasOutputs" + }, + { + "command": "notebook.cellOutput.openInTextEditor", + "when": "false" } ], "webview/context": [ { "command": "notebook.cellOutput.copy", "when": "webviewId == 'notebook.output' && webviewSection == 'image'" + }, + { + "command": "notebook.cellOutput.copy", + "when": "webviewId == 'notebook.output' && webviewSection == 'text'" + }, + { + "command": "notebook.cellOutput.openInTextEditor", + "when": "webviewId == 'notebook.output' && webviewSection == 'text'" } ] } diff --git a/patched-vscode/extensions/ipynb/package.nls.json b/patched-vscode/extensions/ipynb/package.nls.json index af7d8f4a..7a3d9518 100644 --- a/patched-vscode/extensions/ipynb/package.nls.json +++ b/patched-vscode/extensions/ipynb/package.nls.json @@ -7,6 +7,7 @@ "openIpynbInNotebookEditor.title": "Open IPYNB File In Notebook Editor", "cleanInvalidImageAttachment.title": "Clean Invalid Image Attachment Reference", "copyCellOutput.title": "Copy Cell Output", + "openCellOutput.title": "Open Cell Output in Text Editor", "markdownAttachmentRenderer.displayName": { "message": "Markdown-It ipynb Cell Attachment renderer", "comment": [ diff --git a/patched-vscode/extensions/ipynb/src/ipynbMain.ts b/patched-vscode/extensions/ipynb/src/ipynbMain.ts index 889f4c07..6d73107e 100644 --- a/patched-vscode/extensions/ipynb/src/ipynbMain.ts +++ b/patched-vscode/extensions/ipynb/src/ipynbMain.ts @@ -117,13 +117,6 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(cleaner); } - // Update new file contribution - vscode.extensions.onDidChange(() => { - vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter')); - }); - vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter')); - - return { get dropCustomMetadata() { return !useCustomPropertyInMetadata(); diff --git a/patched-vscode/extensions/ipynb/src/serializers.ts b/patched-vscode/extensions/ipynb/src/serializers.ts index 3eb6e90e..eedad802 100644 --- a/patched-vscode/extensions/ipynb/src/serializers.ts +++ b/patched-vscode/extensions/ipynb/src/serializers.ts @@ -123,7 +123,10 @@ function createCodeCellFromNotebookCell(cell: NotebookCellData, preferredLanguag const codeCell: any = { cell_type: 'code', - execution_count: cell.executionSummary?.executionOrder ?? null, + // Possible the metadata was edited as part of diff view + // In diff view we display execution_count as part of metadata, hence when execution count changes in metadata, + // We need to change that here as well, i.e. give preference to any execution_count value in metadata. + execution_count: cellMetadata.execution_count ?? cell.executionSummary?.executionOrder ?? null, source: splitMultilineString(cell.value.replace(/\r\n/g, '\n')), outputs: (cell.outputs || []).map(translateCellDisplayOutput), metadata: cellMetadata.metadata diff --git a/patched-vscode/extensions/jake/src/main.ts b/patched-vscode/extensions/jake/src/main.ts index 33d39e28..a2511dc6 100644 --- a/patched-vscode/extensions/jake/src/main.ts +++ b/patched-vscode/extensions/jake/src/main.ts @@ -290,7 +290,7 @@ class TaskDetector { if (this.detectors.size === 0) { return Promise.resolve([]); } else if (this.detectors.size === 1) { - return this.detectors.values().next().value.getTasks(); + return this.detectors.values().next().value!.getTasks(); } else { const promises: Promise[] = []; for (const detector of this.detectors.values()) { @@ -312,7 +312,7 @@ class TaskDetector { if (this.detectors.size === 0) { return undefined; } else if (this.detectors.size === 1) { - return this.detectors.values().next().value.getTask(task); + return this.detectors.values().next().value!.getTask(task); } else { if ((task.scope === vscode.TaskScope.Workspace) || (task.scope === vscode.TaskScope.Global)) { // Not supported, we don't have enough info to create the task. diff --git a/patched-vscode/extensions/javascript/snippets/javascript.code-snippets b/patched-vscode/extensions/javascript/snippets/javascript.code-snippets index 9448fd14..5bf6aa5e 100644 --- a/patched-vscode/extensions/javascript/snippets/javascript.code-snippets +++ b/patched-vscode/extensions/javascript/snippets/javascript.code-snippets @@ -1,16 +1,79 @@ { - "define module": { - "prefix": "define", - "body": [ - "define([", - "\t'require',", - "\t'${1:dependency}'", - "], function(require, ${2:factory}) {", - "\t'use strict';", + "Constructor": { + "prefix": "ctor", + "body": [ + "/**", + " *", + " */", + "constructor() {", + "\tsuper();", "\t$0", - "});" + "}" + ], + "description": "Constructor" + }, + "Class Definition": { + "prefix": "class", + "isFileTemplate": true, + "body": [ + "class ${1:name} {", + "\tconstructor(${2:parameters}) {", + "\t\t$0", + "\t}", + "}" + ], + "description": "Class Definition" + }, + "Method Definition": { + "prefix": "method", + "body": [ + "/**", + " * ", + " */", + "${1:name}() {", + "\t$0", + "}" + ], + "description": "Method Definition" + }, + "Import Statement": { + "prefix": "import", + "body": [ + "import { $0 } from \"${1:module}\";" + ], + "description": "Import external module" + }, + "Log to the console": { + "prefix": "log", + "body": [ + "console.log($1);", + "$0" + ], + "description": "Log to the console" + }, + "Log warning to console": { + "prefix": "warn", + "body": [ + "console.warn($1);", + "$0" + ], + "description": "Log warning to the console" + }, + "Log error to console": { + "prefix": "error", + "body": [ + "console.error($1);", + "$0" + ], + "description": "Log error to the console" + }, + "Throw Exception": { + "prefix": "throw", + "body": [ + "throw new Error(\"$1\");", + "$0" ], - "description": "define module" + "description": "Throw Exception" }, "For Loop": { "prefix": "for", @@ -22,20 +85,20 @@ ], "description": "For Loop" }, - "For-Each Loop": { - "prefix": "foreach", + "For-Each Loop using =>": { + "prefix": "foreach =>", "body": [ "${1:array}.forEach(${2:element} => {", "\t$TM_SELECTED_TEXT$0", "});" ], - "description": "For-Each Loop" + "description": "For-Each Loop using =>" }, "For-In Loop": { "prefix": "forin", "body": [ "for (const ${1:key} in ${2:object}) {", - "\tif (Object.hasOwnProperty.call(${2:object}, ${1:key})) {", + "\tif (Object.prototype.hasOwnProperty.call(${2:object}, ${1:key})) {", "\t\tconst ${3:element} = ${2:object}[${1:key}];", "\t\t$TM_SELECTED_TEXT$0", "\t}", @@ -46,12 +109,21 @@ "For-Of Loop": { "prefix": "forof", "body": [ - "for (const ${1:iterator} of ${2:object}) {", + "for (const ${1:element} of ${2:object}) {", "\t$TM_SELECTED_TEXT$0", "}" ], "description": "For-Of Loop" }, + "For-Await-Of Loop": { + "prefix": "forawaitof", + "body": [ + "for await (const ${1:element} of ${2:object}) {", + "\t$TM_SELECTED_TEXT$0", + "}" + ], + "description": "For-Await-Of Loop" + }, "Function Statement": { "prefix": "function", "body": [ @@ -149,13 +221,6 @@ ], "description": "Set Interval Function" }, - "Import Statement": { - "prefix": "import", - "body": [ - "import { $0 } from \"${1:module}\";" - ], - "description": "Import external module" - }, "Region Start": { "prefix": "#region", "body": [ @@ -170,34 +235,31 @@ ], "description": "Folding Region End" }, - "Log to the console": { - "prefix": "log", - "body": [ - "console.log($1);" - ], - "description": "Log to the console" - }, - "Log warning to console": { - "prefix": "warn", + "new Promise": { + "prefix": "newpromise", "body": [ - "console.warn($1);" + "new Promise((resolve, reject) => {", + "\t$TM_SELECTED_TEXT$0", + "})" ], - "description": "Log warning to the console" + "description": "Create a new Promise" }, - "Log error to console": { - "prefix": "error", + "Async Function Statement": { + "prefix": "async function", "body": [ - "console.error($1);" + "async function ${1:name}(${2:params}) {", + "\t$TM_SELECTED_TEXT$0", + "}" ], - "description": "Log error to the console" + "description": "Async Function Statement" }, - "new Promise": { - "prefix": "newpromise", + "Async Function Expression": { + "prefix": "async arrow function", "body": [ - "new Promise((resolve, reject) => {", + "async (${1:params}) => {", "\t$TM_SELECTED_TEXT$0", - "})" + "}" ], - "description": "Create a new Promise" + "description": "Async Function Expression" } } diff --git a/patched-vscode/extensions/json-language-features/client/src/browser/jsonClientMain.ts b/patched-vscode/extensions/json-language-features/client/src/browser/jsonClientMain.ts index f78f494d..91ed937f 100644 --- a/patched-vscode/extensions/json-language-features/client/src/browser/jsonClientMain.ts +++ b/patched-vscode/extensions/json-language-features/client/src/browser/jsonClientMain.ts @@ -8,12 +8,6 @@ import { LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor, SchemaRequestService, AsyncDisposable, languageServerDescription } from '../jsonClient'; import { LanguageClient } from 'vscode-languageclient/browser'; -declare const Worker: { - new(stringUrl: string): any; -}; - -declare function fetch(uri: string, options: any): any; - let client: AsyncDisposable | undefined; // this method is called when vs code is activated @@ -24,7 +18,7 @@ export async function activate(context: ExtensionContext) { worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { - return new LanguageClient(id, name, clientOptions, worker); + return new LanguageClient(id, name, worker, clientOptions); }; const schemaRequests: SchemaRequestService = { diff --git a/patched-vscode/extensions/json-language-features/client/src/jsonClient.ts b/patched-vscode/extensions/json-language-features/client/src/jsonClient.ts index f892664d..90aafc89 100644 --- a/patched-vscode/extensions/json-language-features/client/src/jsonClient.ts +++ b/patched-vscode/extensions/json-language-features/client/src/jsonClient.ts @@ -8,7 +8,8 @@ export type JSONLanguageStatus = { schemas: string[] }; import { workspace, window, languages, commands, LogOutputChannel, ExtensionContext, extensions, Uri, ColorInformation, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, FoldingRange, - ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation, l10n + ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation, l10n, + RelativePattern } from 'vscode'; import { LanguageClientOptions, RequestType, NotificationType, FormattingOptions as LSPFormattingOptions, DocumentDiagnosticReportKind, @@ -360,18 +361,29 @@ async function startClientWithParticipants(context: ExtensionContext, languagePa const schemaDocuments: { [uri: string]: boolean } = {}; // handle content request - client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { + client.onRequest(VSCodeContentRequest.type, async (uriPath: string) => { const uri = Uri.parse(uriPath); + const uriString = uri.toString(); if (uri.scheme === 'untitled') { - return Promise.reject(new ResponseError(3, l10n.t('Unable to load {0}', uri.toString()))); + throw new ResponseError(3, l10n.t('Unable to load {0}', uriString)); } - if (uri.scheme !== 'http' && uri.scheme !== 'https') { - return workspace.openTextDocument(uri).then(doc => { - schemaDocuments[uri.toString()] = true; - return doc.getText(); - }, error => { - return Promise.reject(new ResponseError(2, error.toString())); - }); + if (uri.scheme === 'vscode') { + try { + runtime.logOutputChannel.info('read schema from vscode: ' + uriString); + ensureFilesystemWatcherInstalled(uri); + const content = await workspace.fs.readFile(uri); + return new TextDecoder().decode(content); + } catch (e) { + throw new ResponseError(5, e.toString(), e); + } + } else if (uri.scheme !== 'http' && uri.scheme !== 'https') { + try { + const document = await workspace.openTextDocument(uri); + schemaDocuments[uriString] = true; + return document.getText(); + } catch (e) { + throw new ResponseError(2, e.toString(), e); + } } else if (schemaDownloadEnabled) { if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { /* __GDPR__ @@ -381,13 +393,15 @@ async function startClientWithParticipants(context: ExtensionContext, languagePa "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The azure schema URL that was requested." } } */ - runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); + runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriString }); + } + try { + return await runtime.schemaRequests.getContent(uriString); + } catch (e) { + throw new ResponseError(4, e.toString()); } - return runtime.schemaRequests.getContent(uriPath).catch(e => { - return Promise.reject(new ResponseError(4, e.toString())); - }); } else { - return Promise.reject(new ResponseError(1, l10n.t('Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload))); + throw new ResponseError(1, l10n.t('Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload)); } }); @@ -415,15 +429,50 @@ async function startClientWithParticipants(context: ExtensionContext, languagePa schemaResolutionErrorStatusBarItem.hide(); } }; - - toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString()))); - toDispose.push(workspace.onDidCloseTextDocument(d => { - const uriString = d.uri.toString(); + const handleContentClosed = (uriString: string) => { if (handleContentChange(uriString)) { delete schemaDocuments[uriString]; } fileSchemaErrors.delete(uriString); + }; + + const watchers: Map = new Map(); + toDispose.push(new Disposable(() => { + for (const d of watchers.values()) { + d.dispose(); + } })); + + + const ensureFilesystemWatcherInstalled = (uri: Uri) => { + + const uriString = uri.toString(); + if (!watchers.has(uriString)) { + try { + const watcher = workspace.createFileSystemWatcher(new RelativePattern(uri, '*')); + const handleChange = (uri: Uri) => { + runtime.logOutputChannel.info('schema change detected ' + uri.toString()); + client.sendNotification(SchemaContentChangeNotification.type, uriString); + }; + const createListener = watcher.onDidCreate(handleChange); + const changeListener = watcher.onDidChange(handleChange); + const deleteListener = watcher.onDidDelete(() => { + const watcher = watchers.get(uriString); + if (watcher) { + watcher.dispose(); + watchers.delete(uriString); + } + }); + watchers.set(uriString, Disposable.from(watcher, createListener, changeListener, deleteListener)); + } catch { + runtime.logOutputChannel.info('Problem installing a file system watcher for ' + uriString); + } + } + }; + + toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString()))); + toDispose.push(workspace.onDidCloseTextDocument(d => handleContentClosed(d.uri.toString()))); + toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange)); const handleRetryResolveSchemaCommand = () => { diff --git a/patched-vscode/extensions/json-language-features/client/tsconfig.json b/patched-vscode/extensions/json-language-features/client/tsconfig.json index aa51e4d0..89e6a6c1 100644 --- a/patched-vscode/extensions/json-language-features/client/tsconfig.json +++ b/patched-vscode/extensions/json-language-features/client/tsconfig.json @@ -1,7 +1,10 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "lib": [ + "webworker" + ] }, "include": [ "src/**/*", diff --git a/patched-vscode/extensions/json-language-features/package.json b/patched-vscode/extensions/json-language-features/package.json index f8647042..dc9d4a27 100644 --- a/patched-vscode/extensions/json-language-features/package.json +++ b/patched-vscode/extensions/json-language-features/package.json @@ -162,8 +162,8 @@ }, "dependencies": { "@vscode/extension-telemetry": "^0.9.0", - "request-light": "^0.7.0", - "vscode-languageclient": "^10.0.0-next.5" + "request-light": "^0.8.0", + "vscode-languageclient": "10.0.0-next.8" }, "devDependencies": { "@types/node": "20.x" diff --git a/patched-vscode/extensions/json-language-features/server/package.json b/patched-vscode/extensions/json-language-features/server/package.json index 6134fb42..1ac5eb91 100644 --- a/patched-vscode/extensions/json-language-features/server/package.json +++ b/patched-vscode/extensions/json-language-features/server/package.json @@ -13,10 +13,10 @@ "main": "./out/node/jsonServerMain", "dependencies": { "@vscode/l10n": "^0.0.18", - "jsonc-parser": "^3.2.1", - "request-light": "^0.7.0", - "vscode-json-languageservice": "^5.3.11", - "vscode-languageserver": "^10.0.0-next.3", + "jsonc-parser": "^3.3.1", + "request-light": "^0.8.0", + "vscode-json-languageservice": "^5.4.1", + "vscode-languageserver": "10.0.0-next.6", "vscode-uri": "^3.0.8" }, "devDependencies": { diff --git a/patched-vscode/extensions/json-language-features/server/yarn.lock b/patched-vscode/extensions/json-language-features/server/yarn.lock index 669e8234..4c7fd819 100644 --- a/patched-vscode/extensions/json-language-features/server/yarn.lock +++ b/patched-vscode/extensions/json-language-features/server/yarn.lock @@ -19,66 +19,66 @@ resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.18.tgz#916d3a5e960dbab47c1c56f58a7cb5087b135c95" integrity sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ== -jsonc-parser@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" - integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== +jsonc-parser@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" + integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== -request-light@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.7.0.tgz#885628bb2f8040c26401ebf258ec51c4ae98ac2a" - integrity sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q== +request-light@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.8.0.tgz#6ec01e4b526638358c2c6353efa1c6aaf057dfa0" + integrity sha512-bH6E4PMmsEXYrLX6Kr1vu+xI3HproB1vECAwaPSJeroLE1kpWE3HR27uB4icx+6YORu1ajqBJXxuedv8ZQg5Lw== undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -vscode-json-languageservice@^5.3.11: - version "5.3.11" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.3.11.tgz#71dbc56e9b1d07a57aa6a3d5569c8b7f2c05ca05" - integrity sha512-WYS72Ymria3dn8ZbjtBbt5K71m05wY1Q6hpXV5JxUT0q75Ts0ljLmnZJAVpx8DjPgYbFD+Z8KHpWh2laKLUCtQ== +vscode-json-languageservice@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.4.1.tgz#dc9d3b96c714d8d4feb58a219747b1239d6dd5e6" + integrity sha512-5czFGNyVPxz3ZJYl8R3a3SuIj5gjhmGF4Wv05MRPvD4DEnHK6b8km4VbNMJNHBlTCh7A0aHzUbPVzo+0C18mCA== dependencies: "@vscode/l10n" "^0.0.18" - jsonc-parser "^3.2.1" - vscode-languageserver-textdocument "^1.0.11" + jsonc-parser "^3.3.1" + vscode-languageserver-textdocument "^1.0.12" vscode-languageserver-types "^3.17.5" vscode-uri "^3.0.8" -vscode-jsonrpc@9.0.0-next.2: - version "9.0.0-next.2" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.2.tgz#29e9741c742c80329bba1c60ce38fd014651ba80" - integrity sha512-meIaXAgChCHzWy45QGU8YpCNyqnZQ/sYeCj32OLDDbUYsCF7AvgpdXx3nnZn9yzr8ed0Od9bW+NGphEmXsqvIQ== +vscode-jsonrpc@9.0.0-next.4: + version "9.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz#ba403ddb3b82ca578179963dbe08e120a935f50d" + integrity sha512-zSVIr58lJSMYKIsZ5P7GtBbv1eEx25eNyOf0NmEzxmn1GhUNJAVAb5hkA1poKUwj1FRMwN6CeyWxZypmr8SsQQ== -vscode-languageserver-protocol@3.17.6-next.4: - version "3.17.6-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.4.tgz#3c56f6eb588bb42fccc0ac54a0d5daf2d02f0a1b" - integrity sha512-/2bleKBxZLyRObS4mkpaWlVI9xGiUqMVmh/ztZ2vL4uP2XyIpraT45JBpn9AtXr0alqKJPKLuKr+/qcYULvm/w== +vscode-languageserver-protocol@3.17.6-next.6: + version "3.17.6-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.6.tgz#8863a4dc8b395a8c31106ffdc945a00f9163b68b" + integrity sha512-naxM9kc/phpl0kAFNVPejMUWUtzFXdPYY/BtQTYtfbBbHf8sceHOrKkmf6yynZRu1A4oFtRZNqV3wyFRTWqUHw== dependencies: - vscode-jsonrpc "9.0.0-next.2" - vscode-languageserver-types "3.17.6-next.3" + vscode-jsonrpc "9.0.0-next.4" + vscode-languageserver-types "3.17.6-next.4" -vscode-languageserver-textdocument@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf" - integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA== +vscode-languageserver-textdocument@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631" + integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA== -vscode-languageserver-types@3.17.6-next.3: - version "3.17.6-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.3.tgz#f71d6c57f18d921346cfe0c227aabd72eb8cd2f0" - integrity sha512-l5kNFXFRQGuzriXpuBqFpRmkf6f6A4VoU3h95OsVkqIOoi1k7KbwSo600cIdsKSJWrPg/+vX+QMPcMw1oI7ItA== +vscode-languageserver-types@3.17.6-next.4: + version "3.17.6-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz#6670939eb98f00aa7b05021dc3dd7fe9aa4453ea" + integrity sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q== vscode-languageserver-types@^3.17.5: version "3.17.5" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== -vscode-languageserver@^10.0.0-next.3: - version "10.0.0-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-10.0.0-next.3.tgz#a63c5ea9fab1be93d7732ab0fdc18c9b37956e07" - integrity sha512-4x1qHImf6ePji4+8PX43lnBCBfBNdi2jneGX2k5FswJhx/cxaYYmusShmmtO/clyL1iurxJacrQoXfw9+ikhvg== +vscode-languageserver@10.0.0-next.6: + version "10.0.0-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-10.0.0-next.6.tgz#0db118a93fe010c6b40cd04e91a15d09e7b60b60" + integrity sha512-0Lh1nhQfSxo5Ob+ayYO1QTIsDix2/Lc72Urm1KZrCFxK5zIFYaEh3QFeM9oZih4Rzs0ZkQPXXnoHtpvs5GT+Zw== dependencies: - vscode-languageserver-protocol "3.17.6-next.4" + vscode-languageserver-protocol "3.17.6-next.6" vscode-uri@^3.0.8: version "3.0.8" diff --git a/patched-vscode/extensions/json-language-features/yarn.lock b/patched-vscode/extensions/json-language-features/yarn.lock index b7ca9371..82a6ebb8 100644 --- a/patched-vscode/extensions/json-language-features/yarn.lock +++ b/patched-vscode/extensions/json-language-features/yarn.lock @@ -137,10 +137,10 @@ minimatch@^9.0.3: dependencies: brace-expansion "^2.0.1" -request-light@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.7.0.tgz#885628bb2f8040c26401ebf258ec51c4ae98ac2a" - integrity sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q== +request-light@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.8.0.tgz#6ec01e4b526638358c2c6353efa1c6aaf057dfa0" + integrity sha512-bH6E4PMmsEXYrLX6Kr1vu+xI3HproB1vECAwaPSJeroLE1kpWE3HR27uB4icx+6YORu1ajqBJXxuedv8ZQg5Lw== semver@^7.6.0: version "7.6.0" @@ -154,32 +154,32 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -vscode-jsonrpc@9.0.0-next.2: - version "9.0.0-next.2" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.2.tgz#29e9741c742c80329bba1c60ce38fd014651ba80" - integrity sha512-meIaXAgChCHzWy45QGU8YpCNyqnZQ/sYeCj32OLDDbUYsCF7AvgpdXx3nnZn9yzr8ed0Od9bW+NGphEmXsqvIQ== +vscode-jsonrpc@9.0.0-next.4: + version "9.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.4.tgz#ba403ddb3b82ca578179963dbe08e120a935f50d" + integrity sha512-zSVIr58lJSMYKIsZ5P7GtBbv1eEx25eNyOf0NmEzxmn1GhUNJAVAb5hkA1poKUwj1FRMwN6CeyWxZypmr8SsQQ== -vscode-languageclient@^10.0.0-next.5: - version "10.0.0-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-10.0.0-next.5.tgz#7431d88255a5fd99e9423659ac484b1f968200f3" - integrity sha512-JIf1WE7fvV0RElFM062bAummI433vcxuFwqoYAp+1zTVhta/jznxkTz1zs3Hbj2tiDfclf0TZ0qCxflAP1mY2Q== +vscode-languageclient@10.0.0-next.8: + version "10.0.0-next.8" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-10.0.0-next.8.tgz#5afa0ced3b2ac68d31cc1c48edc4f289744542a0" + integrity sha512-D9inIHgqKayO9Tv0MeLb3XIL76yTuWmKdHqcGZKzjtQrMGJgASJDYWTapu+yAjEpDp0gmVOaCYyIlLB86ncDoQ== dependencies: minimatch "^9.0.3" semver "^7.6.0" - vscode-languageserver-protocol "3.17.6-next.4" + vscode-languageserver-protocol "3.17.6-next.6" -vscode-languageserver-protocol@3.17.6-next.4: - version "3.17.6-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.4.tgz#3c56f6eb588bb42fccc0ac54a0d5daf2d02f0a1b" - integrity sha512-/2bleKBxZLyRObS4mkpaWlVI9xGiUqMVmh/ztZ2vL4uP2XyIpraT45JBpn9AtXr0alqKJPKLuKr+/qcYULvm/w== +vscode-languageserver-protocol@3.17.6-next.6: + version "3.17.6-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.6.tgz#8863a4dc8b395a8c31106ffdc945a00f9163b68b" + integrity sha512-naxM9kc/phpl0kAFNVPejMUWUtzFXdPYY/BtQTYtfbBbHf8sceHOrKkmf6yynZRu1A4oFtRZNqV3wyFRTWqUHw== dependencies: - vscode-jsonrpc "9.0.0-next.2" - vscode-languageserver-types "3.17.6-next.3" + vscode-jsonrpc "9.0.0-next.4" + vscode-languageserver-types "3.17.6-next.4" -vscode-languageserver-types@3.17.6-next.3: - version "3.17.6-next.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.3.tgz#f71d6c57f18d921346cfe0c227aabd72eb8cd2f0" - integrity sha512-l5kNFXFRQGuzriXpuBqFpRmkf6f6A4VoU3h95OsVkqIOoi1k7KbwSo600cIdsKSJWrPg/+vX+QMPcMw1oI7ItA== +vscode-languageserver-types@3.17.6-next.4: + version "3.17.6-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz#6670939eb98f00aa7b05021dc3dd7fe9aa4453ea" + integrity sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q== yallist@^4.0.0: version "4.0.0" diff --git a/patched-vscode/extensions/julia/cgmanifest.json b/patched-vscode/extensions/julia/cgmanifest.json index 9daaee1f..b5d8a03b 100644 --- a/patched-vscode/extensions/julia/cgmanifest.json +++ b/patched-vscode/extensions/julia/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "JuliaEditorSupport/atom-language-julia", "repositoryUrl": "https://github.com/JuliaEditorSupport/atom-language-julia", - "commitHash": "663bf8d943fd8440f4ae7565f73327dd616bf191" + "commitHash": "c686684f18153687886e7d19c1bfc3a33076b1ab" } }, "license": "MIT", - "version": "0.22.1" + "version": "0.23.0" } ], "version": 1 diff --git a/patched-vscode/extensions/julia/syntaxes/julia.tmLanguage.json b/patched-vscode/extensions/julia/syntaxes/julia.tmLanguage.json index 35a4ea7d..f66fda97 100644 --- a/patched-vscode/extensions/julia/syntaxes/julia.tmLanguage.json +++ b/patched-vscode/extensions/julia/syntaxes/julia.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/JuliaEditorSupport/atom-language-julia/commit/663bf8d943fd8440f4ae7565f73327dd616bf191", + "version": "https://github.com/JuliaEditorSupport/atom-language-julia/commit/c686684f18153687886e7d19c1bfc3a33076b1ab", "name": "Julia", "scopeName": "source.julia", "comment": "This grammar is used by Atom (Oniguruma), GitHub (PCRE), and VSCode (Oniguruma),\nso all regexps must be compatible with both engines.\n\nSpecs:\n- https://github.com/kkos/oniguruma/blob/master/doc/RE\n- https://www.pcre.org/current/doc/html/", @@ -333,7 +333,7 @@ "name": "keyword.control.using.julia" }, { - "match": "(?<=\\w\\s)\\b(as)\\b(?=\\s\\w)", + "match": "(?<=\\S\\s+)\\b(as)\\b(?=\\s+\\S)", "name": "keyword.control.as.julia" }, { diff --git a/patched-vscode/extensions/latex/cgmanifest.json b/patched-vscode/extensions/latex/cgmanifest.json index 609d875a..3c7203d5 100644 --- a/patched-vscode/extensions/latex/cgmanifest.json +++ b/patched-vscode/extensions/latex/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jlelong/vscode-latex-basics", "repositoryUrl": "https://github.com/jlelong/vscode-latex-basics", - "commitHash": "56e2dc967e6bafafc1acfeeb80af42b8328b021a" + "commitHash": "969429cb9230a63f9155987f069acd4234d10e1a" } }, "license": "MIT", - "version": "1.7.0", + "version": "1.9.0", "description": "The files in syntaxes/ were originally part of https://github.com/James-Yu/LaTeX-Workshop. They have been extracted in the hope that they can useful outside of the LaTeX-Workshop extension.", "licenseDetail": [ "Copyright (c) vscode-latex-basics authors", diff --git a/patched-vscode/extensions/latex/syntaxes/LaTeX.tmLanguage.json b/patched-vscode/extensions/latex/syntaxes/LaTeX.tmLanguage.json index aad6c5c4..bc97a73b 100644 --- a/patched-vscode/extensions/latex/syntaxes/LaTeX.tmLanguage.json +++ b/patched-vscode/extensions/latex/syntaxes/LaTeX.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/3d141a124a16558958e95c54267f7ca37986de6f", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/969429cb9230a63f9155987f069acd4234d10e1a", "name": "LaTeX", "scopeName": "text.tex.latex", "patterns": [ @@ -94,7 +94,7 @@ "4": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -1900,7 +1900,7 @@ "3": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -1910,7 +1910,7 @@ "5": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -2368,7 +2368,7 @@ "3": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -2404,7 +2404,7 @@ "3": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -2562,7 +2562,7 @@ "name": "meta.scope.item.latex" }, { - "begin": "((\\\\)(?:[aA]uto|foot|full|no|ref|short|[tT]ext|[pP]aren|[sS]mart)?[cC]ite(?:al)?(?:p|s|t|author|year(?:par)?|title)?[ANP]*\\*?)((?:(?:\\([^\\)]*\\)){0,2}(?:\\[[^\\]]*\\]){0,2}\\{[\\p{Alphabetic}:.]*\\})*)(?:([<\\[])[^\\]<>]*([>\\]]))?(?:(\\[)[^\\]]*(\\]))?(\\{)", + "begin": "((\\\\)(?:[aA]uto|foot|full|no|ref|short|[tT]ext|[pP]aren|[sS]mart)?[cC]ite(?:al)?(?:p|s|t|author|year(?:par)?|title)?[ANP]*\\*?)((?:(?:\\([^\\)]*\\)){0,2}(?:\\[[^\\]]*\\]){0,2}\\{[\\p{Alphabetic}\\p{Number}_:.-]*\\})*)(<[^\\]<>]*>)?((?:\\[[^\\]]*\\])*)(\\{)", "captures": { "1": { "name": "keyword.control.cite.latex" @@ -2578,18 +2578,20 @@ ] }, "4": { - "name": "punctuation.definition.arguments.optional.begin.latex" + "patterns": [ + { + "include": "#optional-arg-angle-no-highlight" + } + ] }, "5": { - "name": "punctuation.definition.arguments.optional.end.latex" + "patterns": [ + { + "include": "#optional-arg-bracket-no-highlight" + } + ] }, "6": { - "name": "punctuation.definition.arguments.optional.begin.latex" - }, - "7": { - "name": "punctuation.definition.arguments.optional.end.latex" - }, - "8": { "name": "punctuation.definition.arguments.begin.latex" } }, @@ -2602,6 +2604,7 @@ "name": "meta.citation.latex", "patterns": [ { + "match": "((%).*)$", "captures": { "1": { "name": "comment.line.percentage.tex" @@ -2609,8 +2612,7 @@ "2": { "name": "punctuation.definition.comment.tex" } - }, - "match": "((%).*)$" + } }, { "match": "[\\p{Alphabetic}\\p{Number}:.-]+", @@ -2646,7 +2648,7 @@ ] }, { - "begin": "((\\\\)(?:\\w*[rR]ef\\*?))(\\{)", + "begin": "((\\\\)(?:\\w*[rR]ef\\*?))(?:\\[[^\\]]*\\])?(\\{)", "beginCaptures": { "1": { "name": "keyword.control.ref.latex" @@ -2740,7 +2742,7 @@ "3": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -2783,7 +2785,7 @@ "3": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -2820,7 +2822,7 @@ "3": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -2867,7 +2869,7 @@ "3": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -3048,7 +3050,7 @@ "name": "punctuation.definition.variable.latex" } }, - "match": "(\\\\)[cgl](?:[_\\p{Alphabetic}@]+)+_(?:bitset|clist|dim|fp|int|muskip|str|tl|bool|box|coffin|flag|fparray|intarray|ior|iow|prop|regex|seq)", + "match": "(\\\\)(?:[cgl]_+[_\\p{Alphabetic}@]+_[a-z]+|[qs]_[_\\p{Alphabetic}@]+[\\p{Alphabetic}@])", "name": "variable.other.latex3.latex" }, { @@ -3073,27 +3075,29 @@ { "captures": { "1": { - "name": "punctuation.definition.arguments.optional.begin.latex" + "patterns": [ + { + "include": "#optional-arg-parenthesis-no-highlight" + } + ] }, "2": { - "name": "punctuation.definition.arguments.optional.end.latex" + "patterns": [ + { + "include": "#optional-arg-bracket-no-highlight" + } + ] }, "3": { - "name": "punctuation.definition.arguments.optional.begin.latex" - }, - "4": { - "name": "punctuation.definition.arguments.optional.end.latex" - }, - "5": { "name": "punctuation.definition.arguments.begin.latex" }, - "6": { + "4": { "name": "constant.other.reference.citation.latex" }, - "7": { + "5": { "name": "punctuation.definition.arguments.end.latex" }, - "8": { + "6": { "patterns": [ { "include": "#autocites-arg" @@ -3101,7 +3105,7 @@ ] } }, - "match": "(?:(\\()[^\\)]*(\\))){0,2}(?:(\\[)[^\\]]*(\\])){0,2}(\\{)([\\p{Alphabetic}\\p{Number}:.]+)(\\})(.*)" + "match": "((?:\\([^\\)]*\\)){0,2})((?:\\[[^\\]]*\\]){0,2})(\\{)([\\p{Alphabetic}\\p{Number}_:.-]+)(\\})(.*)" } ] }, @@ -3148,7 +3152,7 @@ "match": "\\s*((\\\\)(?:begin|end))(\\{)([a-zA-Z]*\\*?)(\\})(?:(\\[)([^\\]]*)(\\])){,2}(?:(\\{)([^{}]*)(\\}))?" }, "definition-label": { - "begin": "((\\\\)label)((?:\\[[^\\[]*?\\])*)(\\{)", + "begin": "((\\\\)z?label)((?:\\[[^\\[]*?\\])*)(\\{)", "beginCaptures": { "1": { "name": "keyword.control.label.latex" @@ -3159,7 +3163,7 @@ "3": { "patterns": [ { - "include": "#optional-arg" + "include": "#optional-arg-bracket" } ] }, @@ -3222,7 +3226,7 @@ } ] }, - "optional-arg": { + "optional-arg-bracket": { "patterns": [ { "captures": { @@ -3240,6 +3244,73 @@ "name": "meta.parameter.optional.latex" } ] + }, + "optional-arg-parenthesis": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.arguments.optional.begin.latex" + }, + "2": { + "name": "variable.parameter.function.latex" + }, + "3": { + "name": "punctuation.definition.arguments.optional.end.latex" + } + }, + "match": "(\\()([^\\(]*?)(\\))", + "name": "meta.parameter.optional.latex" + } + ] + }, + "optional-arg-bracket-no-highlight": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.arguments.optional.begin.latex" + }, + "2": { + "name": "punctuation.definition.arguments.optional.end.latex" + } + }, + "match": "(\\[)[^\\[]*?(\\])", + "name": "meta.parameter.optional.latex" + } + ] + }, + "optional-arg-angle-no-highlight": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.arguments.optional.begin.latex" + }, + "2": { + "name": "punctuation.definition.arguments.optional.end.latex" + } + }, + "match": "(<)[^<]*?(>)", + "name": "meta.parameter.optional.latex" + } + ] + }, + "optional-arg-parenthesis-no-highlight": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.arguments.optional.begin.latex" + }, + "2": { + "name": "punctuation.definition.arguments.optional.end.latex" + } + }, + "match": "(\\()[^\\(]*?(\\))", + "name": "meta.parameter.optional.latex" + } + ] } } } \ No newline at end of file diff --git a/patched-vscode/extensions/latex/syntaxes/TeX.tmLanguage.json b/patched-vscode/extensions/latex/syntaxes/TeX.tmLanguage.json index 205d8bdf..0cb03e61 100644 --- a/patched-vscode/extensions/latex/syntaxes/TeX.tmLanguage.json +++ b/patched-vscode/extensions/latex/syntaxes/TeX.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/8624d0bdae950a70cdf4a1c3d19c7398ef851721", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/5d7c2a4e451a932b776f6d9342087be6a1e8c0a1", "name": "TeX", "scopeName": "text.tex", "patterns": [ @@ -108,7 +108,25 @@ "name": "punctuation.definition.function.tex" } }, - "match": "(\\\\)(?:[,;]|(?:[\\p{Alphabetic}@]+(?:(?:_[\\p{Alphabetic}@]+)*:[NncVvoxefTFpwD]*)?))", + "match": "(\\\\)_*[\\p{Alphabetic}@]+(?:_[\\p{Alphabetic}@]+)*:[NncVvoxefTFpwD]*", + "name": "support.class.general.latex3.tex" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.function.tex" + } + }, + "match": "(\\.)[\\p{Alphabetic}@]+(?:_[\\p{Alphabetic}@]+)*:[NncVvoxefTFpwD]*", + "name": "support.class.general.latex3.tex" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.function.tex" + } + }, + "match": "(\\\\)(?:[,;]|(?:[\\p{Alphabetic}@]+))", "name": "support.function.general.tex" }, { diff --git a/patched-vscode/extensions/less/cgmanifest.json b/patched-vscode/extensions/less/cgmanifest.json index caf908bb..69a66b5d 100644 --- a/patched-vscode/extensions/less/cgmanifest.json +++ b/patched-vscode/extensions/less/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "language-less", "repositoryUrl": "https://github.com/radium-v/Better-Less", - "commitHash": "24047277622c245dbe9309f0004d0ccb8f02636f" + "commitHash": "fb9c21917193746433743a7c971b70230b40bc2b" } }, "license": "MIT", diff --git a/patched-vscode/extensions/less/syntaxes/less.tmLanguage.json b/patched-vscode/extensions/less/syntaxes/less.tmLanguage.json index 2acac688..6f57a48e 100644 --- a/patched-vscode/extensions/less/syntaxes/less.tmLanguage.json +++ b/patched-vscode/extensions/less/syntaxes/less.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/radium-v/Better-Less/commit/24047277622c245dbe9309f0004d0ccb8f02636f", + "version": "https://github.com/radium-v/Better-Less/commit/fb9c21917193746433743a7c971b70230b40bc2b", "name": "Less", "scopeName": "source.css.less", "patterns": [ @@ -40,6 +40,14 @@ "match": "(?i:[-+]?(?:(?:\\d*\\.\\d+(?:[eE](?:[-+]?\\d+))*)|(?:[-+]?\\d+))(deg|grad|rad|turn))\\b", "name": "constant.numeric.less" }, + "arbitrary-repetition": { + "captures": { + "1": { + "name": "punctuation.definition.arbitrary-repetition.less" + } + }, + "match": "\\s*(?:(,))" + }, "at-charset": { "begin": "\\s*((@)charset\\b)\\s*", "captures": { @@ -268,6 +276,9 @@ "patterns": [ { "include": "#keyframe-name" + }, + { + "include": "#arbitrary-repetition" } ] } @@ -615,6 +626,9 @@ { "include": "#filter-function" }, + { + "include": "#fit-content-function" + }, { "include": "#format-function" }, @@ -697,6 +711,9 @@ }, { "include": "#less-math" + }, + { + "include": "#relative-color" } ] } @@ -715,6 +732,7 @@ "name": "support.function.color.less" } }, + "comment": "rgb(), rgba()", "end": "\\)", "endCaptures": { "0": { @@ -738,9 +756,15 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "include": "#comma-delimiter" }, + { + "include": "#value-separator" + }, { "include": "#percentage-type" }, @@ -752,12 +776,13 @@ ] }, { - "begin": "\\b(hs(l|v)a?|hwb)(?=\\()", + "begin": "\\b(hsla|hsl|hwb|oklab|oklch|lab|lch)(?=\\()", "beginCaptures": { "1": { "name": "support.function.color.less" } }, + "comment": "hsla, hsl, hwb, oklab, oklch, lab, lch", "end": "\\)", "endCaptures": { "0": { @@ -775,12 +800,18 @@ }, "end": "(?=\\))", "patterns": [ + { + "include": "#color-values" + }, { "include": "#less-strings" }, { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "include": "#comma-delimiter" }, @@ -792,6 +823,47 @@ }, { "include": "#number-type" + }, + { + "include": "#calc-function" + }, + { + "include": "#value-separator" + } + ] + } + ] + }, + { + "begin": "\\b(light-dark)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.color.less" + } + }, + "comment": "light-dark()", + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#color-values" + }, + { + "include": "#comma-delimiter" } ] } @@ -813,6 +885,9 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "match": "\\b(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)\\b", "name": "support.constant.color.w3c-standard-color-name.less" @@ -833,6 +908,9 @@ }, "match": "(#)(\\h{3}|\\h{4}|\\h{6}|\\h{8})\\b", "name": "constant.other.color.rgb-value.less" + }, + { + "include": "#relative-color" } ] }, @@ -902,6 +980,9 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "match": "(?:--(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))+|-?(?:[[_a-zA-Z][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*)", "name": "entity.other.counter-name.less" @@ -961,6 +1042,9 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "include": "#literal-string" }, @@ -1030,12 +1114,16 @@ ] }, "cubic-bezier-function": { - "begin": "\\b(cubic-bezier)(?=\\()", + "begin": "\\b(cubic-bezier)(\\()", "beginCaptures": { - "0": { + "1": { "name": "support.function.timing.less" + }, + "2": { + "name": "punctuation.definition.group.begin.less" } }, + "contentName": "meta.group.less", "end": "\\)", "endCaptures": { "0": { @@ -1045,21 +1133,22 @@ "name": "meta.function-call.less", "patterns": [ { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.group.begin.less" - } - }, - "end": "(?=\\))", - "patterns": [ - { - "include": "#comma-delimiter" - }, - { - "include": "#number-type" - } - ] + "include": "#less-functions" + }, + { + "include": "#calc-function" + }, + { + "include": "#less-variables" + }, + { + "include": "#var-function" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#number-type" } ] }, @@ -1084,13 +1173,13 @@ "include": "#frequency-type" }, { - "include": "#length-type" + "include": "#time-type" }, { - "include": "#resolution-type" + "include": "#length-type" }, { - "include": "#time-type" + "include": "#resolution-type" } ] }, @@ -1275,6 +1364,49 @@ } ] }, + "fit-content-function": { + "begin": "\\b(fit-content)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.grid.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#var-function" + }, + { + "include": "#calc-function" + }, + { + "include": "#length-type" + }, + { + "include": "#percentage-type" + } + ] + } + ] + }, "format-function": { "patterns": [ { @@ -1348,6 +1480,9 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "include": "#angle-type" }, @@ -1402,6 +1537,9 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "include": "#color-values" }, @@ -1541,6 +1679,15 @@ } ] }, + "important": { + "captures": { + "1": { + "name": "punctuation.separator.less" + } + }, + "match": "(\\!)\\s*important", + "name": "keyword.other.important.less" + }, "integer-type": { "match": "(?:[-+]?\\d+)", "name": "constant.numeric.less" @@ -1628,6 +1775,9 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "include": "#comma-delimiter" }, @@ -1684,6 +1834,7 @@ "name": "support.function.color-definition.less" } }, + "comment": "argb()", "end": "\\)", "endCaptures": { "0": { @@ -1704,12 +1855,68 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "include": "#color-values" } ] } ] + }, + { + "begin": "\\b(hsva?)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.color.less" + } + }, + "comment": "hsva(), hsv()", + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#integer-type" + }, + { + "include": "#percentage-type" + }, + { + "include": "#number-type" + }, + { + "include": "#less-strings" + }, + { + "include": "#less-variables" + }, + { + "include": "#var-function" + }, + { + "include": "#calc-function" + }, + { + "include": "#comma-delimiter" + } + ] + } + ] } ] }, @@ -3360,6 +3567,9 @@ { "include": "#less-variables" }, + { + "include": "#var-function" + }, { "include": "#length-type" }, @@ -3421,61 +3631,114 @@ "property-value-constants": { "patterns": [ { - "match": "(?x)\\b(\n absolute|active|add\n |all(-(petite|small)-caps|-scroll)?\n |alpha(betic)?\n |alternate(-reverse)?\n |always|annotation|antialiased|at\n |auto(hiding-scrollbar)?\n |avoid(-column|-page|-region)?\n |background(-color|-image|-position|-size)?\n |backwards|balance|baseline|below|bevel|bicubic|bidi-override|blink\n |block(-line-height)?\n |blur\n |bold(er)?\n |border(-bottom|-left|-right|-top)?-(color|radius|width|style)\n |border-(bottom|top)-(left|right)-radius\n |border-image(-outset|-repeat|-slice|-source|-width)?\n |border(-bottom|-left|-right|-top|-collapse|-spacing|-box)?\n |both|bottom\n |box(-shadow)?\n |break-(all|word)\n |brightness\n |butt(on)?\n |capitalize\n |cent(er|ral)\n |char(acter-variant)?\n |cjk-ideographic|clip|clone|close-quote\n |closest-(corner|side)\n |col-resize|collapse\n |color(-stop|-burn|-dodge)?\n |column((-count|-gap|-reverse|-rule(-color|-width)?|-width)|s)?\n |common-ligatures|condensed|consider-shifts|contain\n |content(-box|s)?\n |contextual|contrast|cover\n |crisp(-e|E)dges\n |crop\n |cross(hair)?\n |da(rken|shed)\n |default|dense|diagonal-fractions|difference|disabled\n |discretionary-ligatures|disregard-shifts\n |distribute(-all-lines|-letter|-space)?\n |dotted|double|drop-shadow\n |(nwse|nesw|ns|ew|sw|se|nw|ne|w|s|e|n)-resize\n |ease(-in-out|-in|-out)?\n |element|ellipsis|embed|end|EndColorStr|evenodd\n |exclu(de(-ruby)?|sion)\n |expanded\n |(extra|semi|ultra)-(condensed|expanded)\n |farthest-(corner|side)?\n |fill(-box|-opacity)?\n |filter|fixed|flat\n |flex((-basis|-end|-grow|-shrink|-start)|box)?\n |flip|flood-color\n |font(-size(-adjust)?|-stretch|-weight)?\n |forwards\n |from(-image)?\n |full-width|geometricPrecision|glyphs|gradient|grayscale\n |grid(-height)?\n |groove|hand|hanging|hard-light|height|help|hidden|hide\n |historical-(forms|ligatures)\n |horizontal(-tb)?\n |hue\n |ideograph(-alpha|-numeric|-parenthesis|-space|ic)\n |inactive|include-ruby|infinite|inherit|initial\n |inline(-block|-box|-flex(box)?|-line-height|-table)?\n |inset|inside\n |inter(-ideograph|-word|sect)\n |invert|isolat(e|ion)|italic\n |jis(04|78|83|90)\n |justify(-all)?\n |keep-all\n |large[r]?\n |last|layout|left|letter-spacing\n |light(e[nr]|ing-color)\n |line(-edge|-height|-through)?\n |linear(-gradient|RGB)?\n |lining-nums|list-item|local|loose|lowercase|lr-tb|ltr\n |lumin(osity|ance)|manual\n |manipulation\n |margin(-bottom|-box|-left|-right|-top)?\n |marker(-offset|s)?\n |mathematical\n |max-(content|height|lines|size|width)\n |medium|middle\n |min-(content|height|width)\n |miter|mixed|move|multiply|newspaper\n |no-(change|clip|(close|open)-quote|(common|discretionary|historical)-ligatures|contextual|drop|repeat)\n |none|nonzero|normal|not-allowed|nowrap|oblique\n |offset(-after|-before|-end|-start)?\n |oldstyle-nums|opacity|open-quote\n |optimize(Legibility|Precision|Quality|Speed)\n |order|ordinal|ornaments\n |outline(-color|-offset|-width)?\n |outset|outside|over(line|-edge|lay)\n |padding(-bottom|-box|-left|-right|-top|-box)?\n |page|painted|paused\n |pan-(x|left|right|y|up|down)\n |perspective-origin\n |petite-caps|pixelated|pointer\n |pinch-zoom\n |pre(-line|-wrap)?\n |preserve-3d\n |progid:DXImageTransform.Microsoft.(Alpha|Blur|dropshadow|gradient|Shadow)\n |progress\n |proportional-(nums|width)\n |radial-gradient|recto|region|relative\n |repeat(-[xy])?\n |repeating-(linear|radial)-gradient\n |replaced|reset-size|reverse|ridge|right\n |round\n |row(-resize|-reverse)?\n |rtl|ruby|running|saturat(e|ion)|screen\n |scroll(-position|bar)?\n |separate|sepia\n |scale-down\n |shape-(image-threshold|margin|outside)\n |show\n |sideways(-lr|-rl)?\n |simplified\n |size\n |slashed-zero|slice\n |small(-caps|er)?\n |smooth|snap|solid|soft-light\n |space(-around|-between)?\n |span|sRGB\n |stack(ed-fractions)?\n |start(ColorStr)?\n |static\n |step-(end|start)\n |sticky\n |stop-(color|opacity)\n |stretch|strict\n |stroke(-box|-dash(array|offset)|-miterlimit|-opacity|-width)?\n |style(set)?\n |stylistic\n |sub(grid|pixel-antialiased|tract)?\n |super|swash\n |table(-caption|-cell|(-column|-footer|-header|-row)-group|-column|-row)?\n |tabular-nums|tb-rl\n |text((-bottom|-(decoration|emphasis)-color|-indent|-(over|under)-edge|-shadow|-size(-adjust)?|-top)|field)?\n |thi(ck|n)\n |titling-ca(ps|se)\n |to[p]?\n |touch|traditional\n |transform(-origin)?\n |under(-edge|line)?\n |unicase|unset|uppercase|upright\n |use-(glyph-orientation|script)\n |verso\n |vertical(-align|-ideographic|-lr|-rl|-text)?\n |view-box\n |viewport-fill(-opacity)?\n |visibility\n |visible(Fill|Painted|Stroke)?\n |wait|wavy|weight|whitespace|(device-)?width|word-spacing\n |wrap(-reverse)?\n |x{1,2}-(large|small)\n |z-index|zero\n |zoom(-in|-out)?\n |((?xi:arabic-indic|armenian|bengali|cambodian|circle|cjk-decimal|cjk-earthly-branch|cjk-heavenly-stem|decimal-leading-zero|decimal|devanagari|disclosure-closed|disclosure-open|disc|ethiopic-numeric|georgian|gujarati|gurmukhi|hebrew|hiragana-iroha|hiragana|japanese-formal|japanese-informal|kannada|katakana-iroha|katakana|khmer|korean-hangul-formal|korean-hanja-formal|korean-hanja-informal|lao|lower-alpha|lower-armenian|lower-greek|lower-latin|lower-roman|malayalam|mongolian|myanmar|oriya|persian|simp-chinese-formal|simp-chinese-informal|square|tamil|telugu|thai|tibetan|trad-chinese-formal|trad-chinese-informal|upper-alpha|upper-armenian|upper-latin|upper-roman)))\\b", + "comment": "align-content, align-items, align-self, justify-content, justify-items, justify-self", + "match": "(?x)\\b(?:\n flex-start|flex-end|start|end|space-between|space-around|space-evenly\n |stretch|baseline|safe|unsafe|legacy|anchor-center|first|last|self-start|self-end\n)\\b", "name": "support.constant.property-value.less" }, { - "match": "\\b(?i:sans-serif|serif|monospace|fantasy|cursive)\\b(?=\\s*[;,\\n}])", - "name": "support.constant.font-name.less" - } - ] - }, - "property-values": { - "patterns": [ - { - "include": "#comment-block" + "comment": "alignment-baseline", + "match": "(?x)\\b(?:\n text-before-edge|before-edge|middle|central|text-after-edge\n |after-edge|ideographic|alphabetic|hanging|mathematical|top|center|bottom\n)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#vendor-prefix" + "comment": "all/global values", + "match": "\\b(?:initial|inherit|unset|revert-layer|revert)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#builtin-functions" + "include": "#cubic-bezier-function" }, { - "include": "#color-functions" + "include": "#steps-function" }, { - "include": "#less-math" + "comment": "animation-composition", + "match": "\\b(?:replace|add|accumulate)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#less-functions" + "comment": "animation-direction", + "match": "\\b(?:normal|alternate-reverse|alternate|reverse)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#less-variables" + "comment": "animation-fill-mode", + "match": "\\b(?:forwards|backwards|both)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#unicode-range" + "comment": "animation-iteration-count", + "match": "\\b(?:infinite)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#numeric-values" + "comment": "animation-play-state", + "match": "\\b(?:running|paused)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#color-values" + "comment": "animation-range, animation-range-start, animation-range-end", + "match": "\\b(?:entry-crossing|exit-crossing|entry|exit)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#property-value-constants" + "comment": "animation-timing-function", + "match": "\\b(?:linear|ease-in-out|ease-in|ease-out|ease|step-start|step-end)\\b", + "name": "support.constant.property-value.less" }, { - "include": "#literal-string" + "match": "(?x)\\b(\n absolute|active|add\n|all(-(petite|small)-caps|-scroll)?\n|alpha(betic)?\n|alternate(-reverse)?\n|always|annotation|antialiased|at\n|auto(hiding-scrollbar)?\n|avoid(-column|-page|-region)?\n|background(-color|-image|-position|-size)?\n|backwards|balance|baseline|below|bevel|bicubic|bidi-override|blink\n|block(-(line-height|start|end))?\n|blur\n|bold(er)?\n|border-top-left-radius\n|border-top-right-radius\n|border-bottom-left-radius\n|border-bottom-right-radius\n|border-end-end-radius\n|border-end-start-radius\n|border-start-end-radius\n|border-start-start-radius\n|border-block-start-color\n|border-block-start-style\n|border-block-start-width\n|border-block-start\n|border-block-end-color\n|border-block-end-style\n|border-block-end-width\n|border-block-end\n|border-block-color\n|border-block-style\n|border-block-width\n|border-block\n|border-inline-start-color\n|border-inline-start-style\n|border-inline-start-width\n|border-inline-start\n|border-inline-end-color\n|border-inline-end-style\n|border-inline-end-width\n|border-inline-end\n|border-inline-color\n|border-inline-style\n|border-inline-width\n|border-inline\n|border-top-color\n|border-top-style\n|border-top-width\n|border-top\n|border-right-color\n|border-right-style\n|border-right-width\n|border-right\n|border-bottom-color\n|border-bottom-style\n|border-bottom-width\n|border-bottom\n|border-left-color\n|border-left-style\n|border-left-width\n|border-left\n|border-image-outset\n|border-image-repeat\n|border-image-slice\n|border-image-source\n|border-image-width\n|border-image\n|border-color\n|border-style\n|border-width\n|border-radius\n|border-collapse\n|border-spacing\n|border\n|both\n|bottom\n|box(-shadow)?\n|break-(all|word|spaces)\n|brightness\n|butt(on)?\n|capitalize\n|cent(er|ral)\n|char(acter-variant)?\n|cjk-ideographic|clip|clone|close-quote\n|closest-(corner|side)\n|col-resize|collapse\n|color(-stop|-burn|-dodge)?\n|column((-count|-gap|-reverse|-rule(-color|-width)?|-width)|s)?\n|common-ligatures|condensed|consider-shifts|contain\n|content(-box|s)?\n|contextual|contrast|cover\n|crisp(-e|E)dges\n|crop\n|cross(hair)?\n|da(rken|shed)\n|default|dense|diagonal-fractions|difference|disabled\n|discard|discretionary-ligatures|disregard-shifts\n|distribute(-all-lines|-letter|-space)?\n|dotted|double|drop-shadow\n|(nwse|nesw|ns|ew|sw|se|nw|ne|w|s|e|n)-resize\n|ease(-in-out|-in|-out)?\n|element|ellipsis|embed|end|EndColorStr|evenodd\n|exclu(de(-ruby)?|sion)\n|expanded\n|(extra|semi|ultra)-(condensed|expanded)\n|farthest-(corner|side)?\n|fill(-box|-opacity)?\n|filter\n|fit-content\n|fixed\n|flat\n|flex((-basis|-end|-grow|-shrink|-start)|box)?\n|flip|flood-color\n|font(-size(-adjust)?|-stretch|-weight)?\n|forwards\n|from(-image)?\n|full-width|gap|geometricPrecision|glyphs|gradient|grayscale\n|grid((-column|-row)?-gap|-height)?\n|groove|hand|hanging|hard-light|height|help|hidden|hide\n|historical-(forms|ligatures)\n|horizontal(-tb)?\n|hue\n|ideograph(-alpha|-numeric|-parenthesis|-space|ic)\n|inactive|include-ruby|infinite|inherit|initial\n|inline(-(block|box|flex(box)?|line-height|table|start|end))?\n|inset|inside\n|inter(-ideograph|-word|sect)\n|invert|isolat(e|ion)|italic\n|jis(04|78|83|90)\n|justify(-all)?\n|keep-all\n|large[r]?\n|last|layout|left|letter-spacing\n|light(e[nr]|ing-color)\n|line(-edge|-height|-through)?\n|linear(-gradient|RGB)?\n|lining-nums|list-item|local|loose|lowercase|lr-tb|ltr\n|lumin(osity|ance)|manual\n|manipulation\n|margin(-bottom|-box|-left|-right|-top)?\n|marker(-offset|s)?\n|match-parent\n|mathematical\n|max-(content|height|lines|size|width)\n|medium|middle\n|min-(content|height|width)\n|miter|mixed|move|multiply|newspaper\n|no-(change|clip|(close|open)-quote|(common|discretionary|historical)-ligatures|contextual|drop|repeat)\n|none|nonzero|normal|not-allowed|nowrap|oblique\n|offset(-after|-before|-end|-start)?\n|oldstyle-nums|opacity|open-quote\n|optimize(Legibility|Precision|Quality|Speed)\n|order|ordinal|ornaments\n|outline(-color|-offset|-width)?\n|outset|outside|over(line|-edge|lay)\n|padding(-bottom|-box|-left|-right|-top|-box)?\n|page|paint(ed)?|paused\n|pan-(x|left|right|y|up|down)\n|perspective-origin\n|petite-caps|pixelated|pointer\n|pinch-zoom\n|pretty\n|pre(-line|-wrap)?\n|preserve(-3d|-breaks|-spaces)?\n|progid:DXImageTransform.Microsoft.(Alpha|Blur|dropshadow|gradient|Shadow)\n|progress\n|proportional-(nums|width)\n|radial-gradient|recto|region|relative\n|repeat(-[xy])?\n|repeating-(linear|radial)-gradient\n|replaced|reset-size|reverse|revert(-layer)?|ridge|right\n|round\n|row(-gap|-resize|-reverse)?\n|rtl|ruby|running|saturat(e|ion)|screen\n|scroll(-position|bar)?\n|separate|sepia\n|scale-down\n|shape-(image-threshold|margin|outside)\n|show\n|sideways(-lr|-rl)?\n|simplified\n|size\n|slashed-zero|slice\n|small(-caps|er)?\n|smooth|snap|solid|soft-light\n|space(-around|-between)?\n|span|sRGB\n|stable\n|stack(ed-fractions)?\n|start(ColorStr)?\n|static\n|step-(end|start)\n|sticky\n|stop-(color|opacity)\n|stretch|strict\n|stroke(-box|-dash(array|offset)|-miterlimit|-opacity|-width)?\n|style(set)?\n|stylistic\n|sub(grid|pixel-antialiased|tract)?\n|super|swash\n|table(-caption|-cell|(-column|-footer|-header|-row)-group|-column|-row)?\n|tabular-nums|tb-rl\n|text((-bottom|-(decoration|emphasis)-color|-indent|-(over|under)-edge|-shadow|-size(-adjust)?|-top)|field)?\n|thi(ck|n)\n|titling-ca(ps|se)\n|to[p]?\n|touch|traditional\n|transform(-origin)?\n|under(-edge|line)?\n|unicase|unset|uppercase|upright\n|use-(glyph-orientation|script)\n|verso\n|vertical(-align|-ideographic|-lr|-rl|-text)?\n|view-box\n|viewport-fill(-opacity)?\n|visibility\n|visible(Fill|Painted|Stroke)?\n|wait|wavy|weight|whitespace|(device-)?width|word-spacing\n|wrap(-reverse)?\n|x{1,2}-(large|small)\n|z-index|zero\n|zoom(-in|-out)?\n|((?xi:arabic-indic|armenian|bengali|cambodian|circle|cjk-decimal|cjk-earthly-branch|cjk-heavenly-stem|decimal-leading-zero|decimal|devanagari|disclosure-closed|disclosure-open|disc|ethiopic-numeric|georgian|gujarati|gurmukhi|hebrew|hiragana-iroha|hiragana|japanese-formal|japanese-informal|kannada|katakana-iroha|katakana|khmer|korean-hangul-formal|korean-hanja-formal|korean-hanja-informal|lao|lower-alpha|lower-armenian|lower-greek|lower-latin|lower-roman|malayalam|mongolian|myanmar|oriya|persian|simp-chinese-formal|simp-chinese-informal|square|tamil|telugu|thai|tibetan|trad-chinese-formal|trad-chinese-informal|upper-alpha|upper-armenian|upper-latin|upper-roman)))\\b", + "name": "support.constant.property-value.less" }, { - "captures": { - "1": { - "name": "punctuation.separator.less" - } - }, - "match": "(\\!)\\s*important", - "name": "keyword.other.important.less" + "match": "\\b(?i:sans-serif|serif|monospace|fantasy|cursive)\\b(?=\\s*[;,\\n}])", + "name": "support.constant.font-name.less" + } + ] + }, + "property-values": { + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#vendor-prefix" + }, + { + "include": "#builtin-functions" + }, + { + "include": "#color-functions" + }, + { + "include": "#less-functions" + }, + { + "include": "#less-variables" + }, + { + "include": "#unicode-range" + }, + { + "include": "#numeric-values" + }, + { + "include": "#color-values" + }, + { + "include": "#property-value-constants" + }, + { + "include": "#less-math" + }, + { + "include": "#literal-string" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#important" } ] }, @@ -3733,6 +3996,18 @@ } ] }, + "relative-color": { + "patterns": [ + { + "match": "from", + "name": "keyword.other.less" + }, + { + "match": "\\b[hslawbch]\\b", + "name": "keyword.other.less" + } + ] + }, "resolution-type": { "captures": { "1": { @@ -3802,6 +4077,41 @@ { "include": "#filter-function" }, + { + "begin": "\\b(border((-(bottom|top)-(left|right))|((-(start|end)){2}))?-radius|(border-image(?!-)))\\b", + "beginCaptures": { + "0": { + "name": "support.type.property-name.less" + } + }, + "comment": "border-radius and border-image properties utilize a slash as a separator", + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "patterns": [ + { + "begin": "(((\\+_?)?):)(?=[\\s\\t]*)", + "beginCaptures": { + "1": { + "name": "punctuation.separator.key-value.less" + } + }, + "contentName": "meta.property-value.less", + "end": "(?=\\s*(;)|(?=[})]))", + "patterns": [ + { + "include": "#value-separator" + }, + { + "include": "#property-values" + } + ] + } + ] + }, { "captures": { "1": { @@ -3854,7 +4164,7 @@ ] }, { - "begin": "\\banimation(-(delay|direction|duration|fill-mode|iteration-count|name|play-state|timing-function))?\\b", + "begin": "\\banimation-timeline\\b", "beginCaptures": { "0": { "name": "support.type.property-name.less" @@ -3874,42 +4184,91 @@ "name": "punctuation.separator.key-value.less" } }, - "captures": { + "contentName": "meta.property-value.less", + "end": "(?=\\s*(;)|(?=[})]))", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#custom-property-name" + }, + { + "include": "#scroll-function" + }, + { + "include": "#view-function" + }, + { + "include": "#property-values" + }, + { + "include": "#less-variables" + }, + { + "include": "#arbitrary-repetition" + }, + { + "include": "#important" + } + ] + } + ] + }, + { + "begin": "\\banimation(?:-name)?(?=(?:\\+_?)?:)\\b", + "beginCaptures": { + "0": { + "name": "support.type.property-name.less" + } + }, + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "patterns": [ + { + "begin": "(((\\+_?)?):)(?=[\\s\\t]*)", + "beginCaptures": { "1": { - "name": "punctuation.definition.arbitrary-repetition.less" + "name": "punctuation.separator.key-value.less" } }, "contentName": "meta.property-value.less", "end": "(?=\\s*(;)|(?=[})]))", "patterns": [ { - "match": "\\b(linear|ease(-in)?(-out)?|step-(start|end)|none|forwards|backwards|both|normal|alternate(-reverse)?|reverse|running|paused)\\b", - "name": "support.constant.property-value.less" + "include": "#comment-block" }, { - "include": "#cubic-bezier-function" + "include": "#builtin-functions" }, { - "include": "#steps-function" + "include": "#less-functions" }, { - "include": "#time-type" + "include": "#less-variables" + }, + { + "include": "#numeric-values" }, { - "include": "#number-type" + "include": "#property-value-constants" }, { "match": "-?(?:[_a-zA-Z]|[^\\x{00}-\\x{7F}]|(?:(:?\\\\[0-9a-f]{1,6}(\\r\\n|[\\s\\t\\r\\n\\f])?)|\\\\[^\\r\\n\\f0-9a-f]))(?:[-_a-zA-Z0-9]|[^\\x{00}-\\x{7F}]|(?:(:?\\\\[0-9a-f]{1,6}(\\r\\n|[\\t\\r\\n\\f])?)|\\\\[^\\r\\n\\f0-9a-f]))*", - "name": "variable.other.constant.animation-name.less" + "name": "variable.other.constant.animation-name.less string.unquoted.less" }, { - "include": "#literal-string" + "include": "#less-math" }, { - "include": "#property-values" + "include": "#arbitrary-repetition" }, { - "match": "\\s*(?:(,))" + "include": "#important" } ] } @@ -3918,9 +4277,6 @@ { "begin": "\\b(transition(-(property|duration|delay|timing-function))?)\\b", "beginCaptures": { - "0": { - "name": "meta.property-name.less" - }, "1": { "name": "support.type.property-name.less" } @@ -3933,35 +4289,31 @@ }, "patterns": [ { - "captures": { + "begin": "(((\\+_?)?):)(?=[\\s\\t]*)", + "beginCaptures": { "1": { "name": "punctuation.separator.key-value.less" - }, - "4": { - "name": "meta.property-value.less" } }, - "match": "(((\\+_?)?):)([\\s\\t]*)" - }, - { - "include": "#time-type" - }, - { - "include": "#property-values" - }, - { - "include": "#cubic-bezier-function" - }, - { - "include": "#steps-function" - }, - { - "captures": { - "1": { - "name": "punctuation.definition.arbitrary-repetition.less" + "contentName": "meta.property-value.less", + "end": "(?=\\s*(;)|(?=[})]))", + "patterns": [ + { + "include": "#time-type" + }, + { + "include": "#property-values" + }, + { + "include": "#cubic-bezier-function" + }, + { + "include": "#steps-function" + }, + { + "include": "#arbitrary-repetition" } - }, - "match": "\\s*(?:(,))" + ] } ] }, @@ -4035,12 +4387,7 @@ "name": "support.constant.property-value.less" }, { - "captures": { - "1": { - "name": "punctuation.definition.arbitrary-repetition.less" - } - }, - "match": "\\s*(?:(,))" + "include": "#arbitrary-repetition" } ] }, @@ -4084,7 +4431,11 @@ ] }, { - "match": "(?x)\\b( accent-height | align-content | align-items | align-self | alignment-baseline | all | animation-timing-function | animation-play-state | animation-name | animation-iteration-count | animation-fill-mode | animation-duration | animation-direction | animation-delay | animation | appearance | ascent | azimuth | backface-visibility | background-size | background-repeat-y | background-repeat-x | background-repeat | background-position-y | background-position-x | background-position | background-origin | background-image | background-color | background-clip | background-blend-mode | background-attachment | background | baseline-shift | begin | bias | blend-mode | border-((top|right|bottom|left)-)?(width|style|color) | border-(top|bottom)-(right|left)-radius | border-image-(width|source|slice|repeat|outset) | border-(top|right|bottom|left|collapse|image|radius|spacing) | border | bottom | box-(align|decoration-break|direction|flex|ordinal-group|orient|pack|shadow|sizing) | break-(after|before|inside) | caption-side | clear | clip-path | clip-rule | clip | color(-(interpolation(-filters)?|profile|rendering))? | columns | column-(break-before|count|fill|gap|(rule(-(color|style|width))?)|span|width) | contain | content | counter-(increment|reset) | cursor | (c|d|f)(x|y) | direction | display | divisor | dominant-baseline | dur | elevation | empty-cells | enable-background | end | fallback | fill(-(opacity|rule))? | filter | flex(-(align|basis|direction|flow|grow|item-align|line-pack|negative|order|pack|positive|preferred-size|shrink|wrap))? | float | flood-(color|opacity) | font-display | font-family | font-feature-settings | font-kerning | font-language-override | font-size(-adjust)? | font-smoothing | font-stretch | font-style | font-synthesis | font-variant(-(alternates|caps|east-asian|ligatures|numeric|position))? | font-weight | font | fr | glyph-orientation-(horizontal|vertical) | grid-(area|gap) | grid-auto-(columns|flow|rows) | grid-(column|row)(-(end|gap|start))? | grid-template(-(areas|columns|rows))? | height | hyphens | image-(orientation|rendering|resolution) | isolation | justify-content | kerning | left | letter-spacing | lighting-color | line-(box-contain|break|clamp|height) | list-style(-(image|position|type))? | margin(-(bottom|left|right|top))? | marker(-(end|mid|start))? | mask(-(clip||composite|image|origin|position|repeat|size|type))? | (max|min)-(height|width) | mix-blend-mode | nbsp-mode | negative | object-(fit|position) | opacity | operator | order | orphans | outline(-(color|offset|style|width))? | overflow(-(scrolling|wrap|x|y))? | pad(ding(-(bottom|left|right|top))?)? | page(-break-(after|before|inside))? | paint-order | pause(-(after|before))? | perspective(-origin(-(x|y))?)? | pitch(-range)? | pointer-events | position | prefix | quotes | range | resize | right | rotate | scale | scroll-behavior | shape-(image-threshold|margin|outside|rendering) | size | speak(-as)? | src | stop-(color|opacity) | stroke(-(dash(array|offset)|line(cap|join)|miterlimit|opacity|width))? | suffix | symbols | system | tab-size | table-layout | tap-highlight-color | text-align(-last)? | text-decoration(-(color|line|style))? | text-emphasis(-(color|position|style))? | text-(anchor|fill-color|height|indent|justify|orientation|overflow|rendering|shadow|transform|underline-position) | top | touch-action | transform(-origin(-(x|y))?) | transform(-style)? | transition(-(delay|duration|property|timing-function))? | translate | unicode-(bidi|range) | user-(drag|select) | vertical-align | visibility | white-space | widows | width | will-change | word-(break|spacing|wrap) | writing-mode | z-index | zoom )\\b", + "match": "(?x)\\b( accent-height | align-content | align-items | align-self | alignment-baseline | all | animation-timing-function | animation-range-start | animation-range-end | animation-range | animation-play-state | animation-name | animation-iteration-count | animation-fill-mode | animation-duration | animation-direction | animation-delay | animation-composition | animation | appearance | ascent | azimuth | backface-visibility | background-size | background-repeat-y | background-repeat-x | background-repeat | background-position-y | background-position-x | background-position | background-origin | background-image | background-color | background-clip | background-blend-mode | background-attachment | background | baseline-shift | begin | bias | blend-mode | border-top-left-radius | border-top-right-radius | border-bottom-left-radius | border-bottom-right-radius | border-end-end-radius | border-end-start-radius | border-start-end-radius | border-start-start-radius | border-block-start-color | border-block-start-style | border-block-start-width | border-block-start | border-block-end-color | border-block-end-style | border-block-end-width | border-block-end | border-block-color | border-block-style | border-block-width | border-block | border-inline-start-color | border-inline-start-style | border-inline-start-width | border-inline-start | border-inline-end-color | border-inline-end-style | border-inline-end-width | border-inline-end | border-inline-color | border-inline-style | border-inline-width | border-inline | border-top-color | border-top-style | border-top-width | border-top | border-right-color | border-right-style | border-right-width | border-right | border-bottom-color | border-bottom-style | border-bottom-width | border-bottom | border-left-color | border-left-style | border-left-width | border-left | border-image-outset | border-image-repeat | border-image-slice | border-image-source | border-image-width | border-image | border-color | border-style | border-width | border-radius | border-collapse | border-spacing | border | bottom | box-(align|decoration-break|direction|flex|ordinal-group|orient|pack|shadow|sizing) | break-(after|before|inside) | caption-side | clear | clip-path | clip-rule | clip | color(-(interpolation(-filters)?|profile|rendering))? | columns | column-(break-before|count|fill|gap|(rule(-(color|style|width))?)|span|width) | contain(-intrinsic-((((block|inline)-)?size)|height|width))? | content | counter-(increment|reset) | cursor | (c|d|f)(x|y) | direction | display | divisor | dominant-baseline | dur | elevation | empty-cells | enable-background | end | fallback | fill(-(opacity|rule))? | filter | flex(-(align|basis|direction|flow|grow|item-align|line-pack|negative|order|pack|positive|preferred-size|shrink|wrap))? | float | flood-(color|opacity) | font-display | font-family | font-feature-settings | font-kerning | font-language-override | font-size(-adjust)? | font-smoothing | font-stretch | font-style | font-synthesis | font-variant(-(alternates|caps|east-asian|ligatures|numeric|position))? | font-weight | font | fr | ((column|row)-)?gap | glyph-orientation-(horizontal|vertical) | grid-(area|gap) | grid-auto-(columns|flow|rows) | grid-(column|row)(-(end|gap|start))? | grid-template(-(areas|columns|rows))? | height | hyphens | image-(orientation|rendering|resolution) | inset(-(block|inline))?(-(start|end))? | isolation | justify-content | justify-items | justify-self | kerning | left | letter-spacing | lighting-color | line-(box-contain|break|clamp|height) | list-style(-(image|position|type))? | (margin|padding)(-(bottom|left|right|top)|(-(block|inline)?(-(end|start))?))? | marker(-(end|mid|start))? | mask(-(clip||composite|image|origin|position|repeat|size|type))? | (max|min)-(height|width) | mix-blend-mode | nbsp-mode | negative | object-(fit|position) | opacity | operator | order | orphans | outline(-(color|offset|style|width))? | overflow(-((inline|block)|scrolling|wrap|x|y))? | overscroll-behavior(-block|-(inline|x|y))? | pad(ding(-(bottom|left|right|top))?)? | page(-break-(after|before|inside))? | paint-order | pause(-(after|before))? | perspective(-origin(-(x|y))?)? | pitch(-range)? | place-content | place-self | pointer-events | position | prefix | quotes | range | resize | right | rotate | scale | scroll-behavior | shape-(image-threshold|margin|outside|rendering) | size | speak(-as)? | src | stop-(color|opacity) | stroke(-(dash(array|offset)|line(cap|join)|miterlimit|opacity|width))? | suffix | symbols | system | tab-size | table-layout | tap-highlight-color | text-align(-last)? | text-decoration(-(color|line|style))? | text-emphasis(-(color|position|style))? | text-(anchor|fill-color|height|indent|justify|orientation|overflow|rendering|size-adjust|shadow|transform|underline-position|wrap) | top | touch-action | transform(-origin(-(x|y))?) | transform(-style)? | transition(-(delay|duration|property|timing-function))? | translate | unicode-(bidi|range) | user-(drag|select) | vertical-align | visibility | white-space(-collapse)? | widows | width | will-change | word-(break|spacing|wrap) | writing-mode | z-index | zoom )\\b", + "name": "support.type.property-name.less" + }, + { + "match": "(?x)\\b(((contain-intrinsic|max|min)-)?(block|inline)?-size)\\b", "name": "support.type.property-name.less" }, { @@ -4093,7 +4444,15 @@ ] }, { - "begin": "\\b(((\\+_?)?):)([\\s\\t]*)", + "begin": "\\b((?:(?:\\+_?)?):)([\\s\\t]*)", + "beginCaptures": { + "1": { + "name": "punctuation.separator.key-value.less" + }, + "2": { + "name": "meta.property-value.less" + } + }, "captures": { "1": { "name": "punctuation.separator.key-value.less" @@ -4120,6 +4479,40 @@ } ] }, + "scroll-function": { + "begin": "\\b(scroll)(\\()", + "beginCaptures": { + "1": { + "name": "support.function.scroll.less" + }, + "2": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "match": "root|nearest|self", + "name": "support.constant.scroller.less" + }, + { + "match": "block|inline|x|y", + "name": "support.constant.axis.less" + }, + { + "include": "#less-variables" + }, + { + "include": "#var-function" + } + ] + }, "selector": { "patterns": [ { @@ -4140,13 +4533,7 @@ "include": "#less-variable-interpolation" }, { - "captures": { - "1": { - "name": "punctuation.separator.less" - } - }, - "match": "(\\!)\\s*important", - "name": "keyword.other.important.less" + "include": "#important" } ] } @@ -4288,12 +4675,7 @@ ] }, { - "captures": { - "1": { - "name": "punctuation.definition.arbitrary-repetition.less" - } - }, - "match": "\\s*(?:(,))" + "include": "#arbitrary-repetition" }, { "match": "\\*", @@ -4462,12 +4844,16 @@ ] }, "steps-function": { - "begin": "\\b(steps)(?=\\()", + "begin": "\\b(steps)(\\()", "beginCaptures": { - "0": { + "1": { "name": "support.function.timing.less" + }, + "2": { + "name": "punctuation.definition.group.begin.less" } }, + "contentName": "meta.group.less", "end": "\\)", "endCaptures": { "0": { @@ -4477,25 +4863,23 @@ "name": "meta.function-call.less", "patterns": [ { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.group.begin.less" - } - }, - "end": "(?=\\))", - "patterns": [ - { - "include": "#comma-delimiter" - }, - { - "include": "#integer-type" - }, - { - "match": "(end|middle|start)", - "name": "support.keyword.timing-direction.less" - } - ] + "match": "jump-start|jump-end|jump-none|jump-both|start|end", + "name": "support.constant.step-position.less" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#integer-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#var-function" + }, + { + "include": "#calc-function" } ] }, @@ -4968,42 +5352,49 @@ } ] }, + "value-separator": { + "captures": { + "1": { + "name": "punctuation.separator.less" + } + }, + "match": "\\s*(/)\\s*" + }, "var-function": { + "begin": "\\b(var)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.var.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", "patterns": [ { - "begin": "\\b(var)(?=\\()", + "begin": "\\(", "beginCaptures": { - "1": { - "name": "support.function.var.less" - } - }, - "end": "\\)", - "endCaptures": { "0": { - "name": "punctuation.definition.group.end.less" + "name": "punctuation.definition.group.begin.less" } }, - "name": "meta.function-call.less", + "end": "(?=\\))", "patterns": [ { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.group.begin.less" - } - }, - "end": "(?=\\))", - "patterns": [ - { - "include": "#comma-delimiter" - }, - { - "include": "#custom-property-name" - }, - { - "include": "#less-variables" - } - ] + "include": "#comma-delimiter" + }, + { + "include": "#custom-property-name" + }, + { + "include": "#less-variables" + }, + { + "include": "#property-values" } ] } @@ -5012,6 +5403,56 @@ "vendor-prefix": { "match": "-(?:webkit|moz(-osx)?|ms|o)-", "name": "support.type.vendor-prefix.less" + }, + "view-function": { + "begin": "\\b(view)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.view.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "match": "block|inline|x|y|auto", + "name": "support.constant.property-value.less" + }, + { + "include": "#length-type" + }, + { + "include": "#percentage-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#var-function" + }, + { + "include": "#calc-function" + }, + { + "include": "#arbitrary-repetition" + } + ] + } + ] } } } \ No newline at end of file diff --git a/patched-vscode/extensions/markdown-basics/cgmanifest.json b/patched-vscode/extensions/markdown-basics/cgmanifest.json index 60c6b192..380b0c74 100644 --- a/patched-vscode/extensions/markdown-basics/cgmanifest.json +++ b/patched-vscode/extensions/markdown-basics/cgmanifest.json @@ -33,7 +33,7 @@ "git": { "name": "microsoft/vscode-markdown-tm-grammar", "repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar", - "commitHash": "f75d5f55730e72ee7ff386841949048b2395e440" + "commitHash": "7418dd20d76c72e82fadee2909e03239e9973b35" } }, "license": "MIT", diff --git a/patched-vscode/extensions/markdown-basics/language-configuration.json b/patched-vscode/extensions/markdown-basics/language-configuration.json index f1e7859c..6e1766db 100644 --- a/patched-vscode/extensions/markdown-basics/language-configuration.json +++ b/patched-vscode/extensions/markdown-basics/language-configuration.json @@ -79,6 +79,10 @@ [ "<", ">" + ], + [ + "~", + "~" ] ], "folding": { diff --git a/patched-vscode/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/patched-vscode/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index c84c468b..9761ca71 100644 --- a/patched-vscode/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/patched-vscode/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/f75d5f55730e72ee7ff386841949048b2395e440", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/7418dd20d76c72e82fadee2909e03239e9973b35", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -2480,14 +2480,34 @@ "name": "meta.separator.markdown" }, "frontMatter": { - "begin": "\\A-{3}\\s*$", - "contentName": "meta.embedded.block.frontmatter", + "begin": "\\A(?=(-{3,}))", + "end": "^ {,3}\\1-*[ \\t]*$|^[ \\t]*\\.{3}$", + "applyEndPatternLast": 1, + "endCaptures": { + "0": { + "name": "punctuation.definition.end.frontmatter" + } + }, "patterns": [ { - "include": "source.yaml" + "begin": "\\A(-{3,})(.*)$", + "while": "^(?! {,3}\\1-*[ \\t]*$|[ \\t]*\\.{3}$)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.begin.frontmatter" + }, + "2": { + "name": "comment.frontmatter" + } + }, + "contentName": "meta.embedded.block.frontmatter", + "patterns": [ + { + "include": "source.yaml" + } + ] } - ], - "end": "(^|\\G)-{3}|\\.{3}\\s*$" + ] }, "table": { "name": "markup.table.markdown", diff --git a/patched-vscode/extensions/markdown-language-features/.vscodeignore b/patched-vscode/extensions/markdown-language-features/.vscodeignore index 588916d3..fdbb001a 100644 --- a/patched-vscode/extensions/markdown-language-features/.vscodeignore +++ b/patched-vscode/extensions/markdown-language-features/.vscodeignore @@ -15,11 +15,4 @@ webpack.config.js esbuild-notebook.js esbuild-preview.js .gitignore -server/src/** -server/extension.webpack.config.js -server/extension-browser.webpack.config.js -server/tsconfig.json -server/.vscode/** -server/node_modules/** -server/yarn.lock -server/.npmignore +**/*.d.ts diff --git a/patched-vscode/extensions/markdown-language-features/extension-browser.webpack.config.js b/patched-vscode/extensions/markdown-language-features/extension-browser.webpack.config.js index bad27aab..47f50aa5 100644 --- a/patched-vscode/extensions/markdown-language-features/extension-browser.webpack.config.js +++ b/patched-vscode/extensions/markdown-language-features/extension-browser.webpack.config.js @@ -7,13 +7,25 @@ 'use strict'; -const withBrowserDefaults = require('../shared.webpack.config').browser; +const CopyPlugin = require('copy-webpack-plugin'); +const { browserPlugins, browser } = require('../shared.webpack.config'); -module.exports = withBrowserDefaults({ +module.exports = browser({ context: __dirname, entry: { extension: './src/extension.browser.ts' - } + }, + plugins: [ + ...browserPlugins(__dirname), // add plugins, don't replace inherited + new CopyPlugin({ + patterns: [ + { + from: './node_modules/vscode-markdown-languageserver/dist/browser/workerMain.js', + to: 'serverWorkerMain.js', + } + ], + }), + ], }, { configFile: 'tsconfig.browser.json' }); diff --git a/patched-vscode/extensions/markdown-language-features/extension.webpack.config.js b/patched-vscode/extensions/markdown-language-features/extension.webpack.config.js index de88398e..588d0632 100644 --- a/patched-vscode/extensions/markdown-language-features/extension.webpack.config.js +++ b/patched-vscode/extensions/markdown-language-features/extension.webpack.config.js @@ -7,6 +7,7 @@ 'use strict'; +const CopyPlugin = require('copy-webpack-plugin'); const withDefaults = require('../shared.webpack.config'); module.exports = withDefaults({ @@ -16,5 +17,16 @@ module.exports = withDefaults({ }, entry: { extension: './src/extension.ts', - } + }, + plugins: [ + ...withDefaults.nodePlugins(__dirname), // add plugins, don't replace inherited + new CopyPlugin({ + patterns: [ + { + from: './node_modules/vscode-markdown-languageserver/dist/node/workerMain.js', + to: 'serverWorkerMain.js', + } + ], + }), + ], }); diff --git a/patched-vscode/extensions/markdown-language-features/media/markdown.css b/patched-vscode/extensions/markdown-language-features/media/markdown.css index 168f6a8a..800be985 100644 --- a/patched-vscode/extensions/markdown-language-features/media/markdown.css +++ b/patched-vscode/extensions/markdown-language-features/media/markdown.css @@ -205,7 +205,7 @@ table > tbody > tr + tr > td { blockquote { margin: 0; - padding: 2px 16px 0 10px; + padding: 0px 16px 0 10px; border-left-width: 5px; border-left-style: solid; border-radius: 2px; diff --git a/patched-vscode/extensions/markdown-language-features/package.json b/patched-vscode/extensions/markdown-language-features/package.json index e238a24b..894c1035 100644 --- a/patched-vscode/extensions/markdown-language-features/package.json +++ b/patched-vscode/extensions/markdown-language-features/package.json @@ -708,14 +708,11 @@ "%configuration.markdown.preferredMdPathExtensionStyle.removeExtension%" ] }, - "markdown.experimental.updateLinksOnPaste": { + "markdown.editor.updateLinksOnPaste.enabled": { "type": "boolean", - "default": false, - "markdownDescription": "%configuration.markdown.experimental.updateLinksOnPaste%", + "markdownDescription": "%configuration.markdown.editor.updateLinksOnPaste.enabled%", "scope": "resource", - "tags": [ - "experimental" - ] + "default": true } } }, @@ -756,8 +753,8 @@ ] }, "scripts": { - "compile": "gulp compile-extension:markdown-language-features-languageService && gulp compile-extension:markdown-language-features-server && gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", - "watch": "npm run build-preview && gulp watch-extension:markdown-language-features watch-extension:markdown-language-features-languageService watch-extension:markdown-language-features-server", + "compile": "gulp compile-extension:markdown-language-features-languageService && gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", + "watch": "npm run build-preview && gulp watch-extension:markdown-language-features watch-extension:markdown-language-features-languageService", "vscode:prepublish": "npm run build-ext && npm run build-preview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", "build-notebook": "node ./esbuild-notebook", @@ -775,6 +772,7 @@ "picomatch": "^2.3.1", "vscode-languageclient": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.11", + "vscode-markdown-languageserver": "^0.5.0-alpha.8", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/patched-vscode/extensions/markdown-language-features/package.nls.json b/patched-vscode/extensions/markdown-language-features/package.nls.json index 3549ec58..da567b46 100644 --- a/patched-vscode/extensions/markdown-language-features/package.nls.json +++ b/patched-vscode/extensions/markdown-language-features/package.nls.json @@ -91,6 +91,6 @@ "configuration.markdown.preferredMdPathExtensionStyle.removeExtension": "Prefer removing the file extension. For example, path completions to a file named `file.md` will insert `file` without the `.md`.", "configuration.markdown.editor.filePaste.videoSnippet": "Snippet used when adding videos to Markdown. This snippet can use the following variables:\n- `${src}` — The resolved path of the video file.\n- `${title}` — The title used for the video. A snippet placeholder will automatically be created for this variable.", "configuration.markdown.editor.filePaste.audioSnippet": "Snippet used when adding audio to Markdown. This snippet can use the following variables:\n- `${src}` — The resolved path of the audio file.\n- `${title}` — The title used for the audio. A snippet placeholder will automatically be created for this variable.", - "configuration.markdown.experimental.updateLinksOnPaste": "Enable/disable automatic updating of links in text that is copied and pasted from one Markdown editor to another.", + "configuration.markdown.editor.updateLinksOnPaste.enabled": "Enable/disable a paste option that updates links and reference in text that is copied and pasted between Markdown editors.\n\nTo use this feature, after pasting text that contains updatable links, just click on the Paste Widget and select `Paste and update pasted links`.", "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/patched-vscode/extensions/markdown-language-features/schemas/package.schema.json b/patched-vscode/extensions/markdown-language-features/schemas/package.schema.json index 5591d0b0..8dea48f7 100644 --- a/patched-vscode/extensions/markdown-language-features/schemas/package.schema.json +++ b/patched-vscode/extensions/markdown-language-features/schemas/package.schema.json @@ -1,6 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Markdown contributions to package.json", "type": "object", "properties": { "contributes": { diff --git a/patched-vscode/extensions/markdown-language-features/server/.npmignore b/patched-vscode/extensions/markdown-language-features/server/.npmignore deleted file mode 100644 index bfd42159..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/.npmignore +++ /dev/null @@ -1,12 +0,0 @@ -.vscode/ -.github/ -out/test/ -src/ -.eslintrc.js -.gitignore -tsconfig*.json -*.tsbuildinfo -*.map -example.cjs -CODE_OF_CONDUCT.md -SECURITY.md \ No newline at end of file diff --git a/patched-vscode/extensions/markdown-language-features/server/.vscode/launch.json b/patched-vscode/extensions/markdown-language-features/server/.vscode/launch.json deleted file mode 100644 index fd9033bf..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/.vscode/launch.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": "0.1.0", - // List of configurations. Add new configurations or edit existing ones. - "configurations": [ - { - "name": "Attach", - "type": "node", - "request": "attach", - "port": 7997, - "sourceMaps": true, - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ] - } - ] -} \ No newline at end of file diff --git a/patched-vscode/extensions/markdown-language-features/server/.vscode/settings.json b/patched-vscode/extensions/markdown-language-features/server/.vscode/settings.json deleted file mode 100644 index 7a73a41b..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/.vscode/settings.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/patched-vscode/extensions/markdown-language-features/server/.vscode/tasks.json b/patched-vscode/extensions/markdown-language-features/server/.vscode/tasks.json deleted file mode 100644 index ecc951a7..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/.vscode/tasks.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "version": "2.0.0", - "command": "npm", - "args": [ - "run", - "watch" - ], - "isBackground": true, - "problemMatcher": "$tsc-watch", - "tasks": [ - { - "label": "npm", - "type": "shell", - "command": "npm", - "args": [ - "run", - "watch" - ], - "isBackground": true, - "problemMatcher": "$tsc-watch", - "group": { - "_id": "build", - "isDefault": false - } - } - ] -} \ No newline at end of file diff --git a/patched-vscode/extensions/markdown-language-features/server/CHANGELOG.md b/patched-vscode/extensions/markdown-language-features/server/CHANGELOG.md deleted file mode 100644 index a5cc9d15..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# Changelog - -# 0.4.0-alpha.3 — June 2, 2023 -- Pick up [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) 0.4.0-alpha.3. See [CHANGELOG](https://github.com/microsoft/vscode-markdown-languageservice/blob/main/CHANGELOG.md#040-alpha3--may-30-2023) for details. - -## 0.3.0 - March 28, 2023 -- Pick up [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) 0.3.0. See [CHANGELOG](https://github.com/microsoft/vscode-markdown-languageservice/blob/main/CHANGELOG.md#030--march-16-2023) for details. diff --git a/patched-vscode/extensions/markdown-language-features/server/README.md b/patched-vscode/extensions/markdown-language-features/server/README.md deleted file mode 100644 index 4114d269..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/README.md +++ /dev/null @@ -1,131 +0,0 @@ -# Markdown Language Server - -> **◠Import** This is still in development. While the language server is being used by VS Code, it has not yet been tested with other clients. - -The Markdown language server powers VS Code's built-in markdown support, providing tools for writing and browsing Markdown files. It runs as a separate executable and implements the [language server protocol](https://microsoft.github.io/language-server-protocol/overview). - -This server uses the [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) to implement almost all of the language features. You can use that library if you need a library for working with Markdown instead of a full language server. - -## Server capabilities - -- [Completions](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for Markdown links. - -- [Folding](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) of Markdown regions, block elements, and header sections. - -- [Smart selection](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange) for inline elements, block elements, and header sections. - -- [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to headers in a document. - -- [Workspace Symbols](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) for quick navigation to headers in the workspace - -- [Document links](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink) for making Markdown links in a document clickable. - -- [Find all references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) to headers and links across all Markdown files in the workspace. - -- [Go to definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) from links to headers or link definitions. - -- [Rename](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename) of headers and links across all Markdown files in the workspace. - -- Find all references to a file. Uses a custom `markdown/getReferencesToFileInWorkspace` message. - -- [Code Actions](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction) - - - Organize link definitions source action. - - Extract link to definition refactoring. - -- Updating links when a file is moved / renamed. Uses a custom `markdown/getEditForFileRenames` message. - -- [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links. - -## Client requirements - -### Initialization options - -The client can send the following initialization options to the server: - -- `markdownFileExtensions` Array file extensions that should be considered as Markdown. These should not include the leading `.`. For example: `['md', 'mdown', 'markdown']`. - -### Settings - -Clients may send a `workspace/didChangeConfiguration` notification to notify the server of settings changes. -The server supports the following settings: - -- `markdown` - - `suggest` - - `paths` - - `enabled` — Enable/disable path suggestions. - - - `occurrencesHighlight` - - `enabled` — Enable/disable highlighting of link occurrences. - - - `validate` - - `enabled` — Enable/disable all validation. - - `referenceLinks` - - `enabled` — Enable/disable validation of reference links: `[text][ref]` - - `fragmentLinks` - - `enabled` — Enable/disable validation of links to fragments in the current files: `[text](#head)` - - `fileLinks` - - `enabled` — Enable/disable validation of links to file in the workspace. - - `markdownFragmentLinks` — Enable/disable validation of links to headers in other Markdown files. Use `inherit` to inherit the `fragmentLinks` setting. - - `ignoredLinks` — Array of glob patterns for files that should not be validated. - - `unusedLinkDefinitions` - - `enabled` — Enable/disable validation of unused link definitions. - - `duplicateLinkDefinitions` - - `enabled` — Enable/disable validation of duplicated link definitions. - -### Custom requests - -To support all of the features of the language server, the client needs to implement a few custom request types. The definitions of these request types can be found in [`protocol.ts`](./src/protocol.ts) - -#### `markdown/parse` - -Get the tokens for a Markdown file. Clients are expected to use [Markdown-it](https://github.com/markdown-it/markdown-it) for this. - -We require that clients bring their own version of Markdown-it so that they can customize/extend Markdown-it. - -#### `markdown/fs/readFile` - -Read the contents of a file in the workspace. - -#### `markdown/fs/readDirectory` - -Read the contents of a directory in the workspace. - -#### `markdown/fs/stat` - -Check if a given file/directory exists in the workspace. - -#### `markdown/fs/watcher/create` - -Create a file watcher. This is needed for diagnostics support. - -#### `markdown/fs/watcher/delete` - -Delete a previously created file watcher. - -#### `markdown/findMarkdownFilesInWorkspace` - -Get a list of all markdown files in the workspace. - -## Contribute - -The source code of the Markdown language server can be found in the [VSCode repository](https://github.com/microsoft/vscode) at [extensions/markdown-language-features/server](https://github.com/microsoft/vscode/tree/master/extensions/markdown-language-features/server). - -File issues and pull requests in the [VSCode GitHub Issues](https://github.com/microsoft/vscode/issues). See the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) on how to build and run from source. - -Most of the functionality of the server is located in libraries: - -- [vscode-markdown-languageservice](https://github.com/microsoft/vscode-markdown-languageservice) contains the implementation of all features as a reusable library. -- [vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node) contains the implementation of language server for NodeJS. - -Help on any of these projects is very welcome. - -## Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## License - -Copyright (c) Microsoft Corporation. All rights reserved. - -Licensed under the [MIT](https://github.com/microsoft/vscode/blob/master/LICENSE.txt) License. diff --git a/patched-vscode/extensions/markdown-language-features/server/build/pipeline.yml b/patched-vscode/extensions/markdown-language-features/server/build/pipeline.yml deleted file mode 100644 index 0c9e3bdb..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/build/pipeline.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: $(Date:yyyyMMdd)$(Rev:.r) - -trigger: none -pr: none - -resources: - repositories: - - repository: templates - type: github - name: microsoft/vscode-engineering - ref: main - endpoint: Monaco - -parameters: - - name: publishPackage - displayName: Publish vscode-markdown-languageserver - type: boolean - default: false - -extends: - template: azure-pipelines/npm-package/pipeline.yml@templates - parameters: - npmPackages: - - name: vscode-markdown-languageserver - workingDirectory: extensions/markdown-language-features/server - - buildSteps: - - script: yarn install - displayName: Install dependencies - - - script: gulp compile-extension:markdown-language-features-server - displayName: Compile - - publishPackage: ${{ parameters.publishPackage }} - packagePlatform: 'Windows' diff --git a/patched-vscode/extensions/markdown-language-features/server/extension-browser.webpack.config.js b/patched-vscode/extensions/markdown-language-features/server/extension-browser.webpack.config.js deleted file mode 100644 index 2a9de70b..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/extension-browser.webpack.config.js +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../../shared.webpack.config').browser; -const path = require('path'); - -module.exports = withBrowserDefaults({ - context: __dirname, - entry: { - extension: './src/browser/workerMain.ts', - }, - output: { - filename: 'workerMain.js', - path: path.join(__dirname, 'dist', 'browser'), - libraryTarget: 'var', - library: 'serverExportVar' - } -}); diff --git a/patched-vscode/extensions/markdown-language-features/server/package.json b/patched-vscode/extensions/markdown-language-features/server/package.json deleted file mode 100644 index 532c2dec..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "vscode-markdown-languageserver", - "description": "Markdown language server", - "version": "0.5.0-alpha.6", - "author": "Microsoft Corporation", - "license": "MIT", - "engines": { - "node": "*" - }, - "main": "./out/node/main", - "browser": "./dist/browser/main", - "files": [ - "dist/**/*.js", - "out/**/*.js" - ], - "dependencies": { - "@vscode/l10n": "^0.0.11", - "vscode-languageserver": "^8.1.0", - "vscode-languageserver-textdocument": "^1.0.8", - "vscode-languageserver-types": "^3.17.3", - "vscode-markdown-languageservice": "^0.5.0-alpha.6", - "vscode-uri": "^3.0.7" - }, - "devDependencies": { - "@types/node": "20.x" - }, - "scripts": { - "compile": "gulp compile-extension:markdown-language-features-server", - "prepublishOnly": "npm run compile", - "watch": "gulp watch-extension:markdown-language-features-server", - "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", - "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" - } -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/browser/main.ts b/patched-vscode/extensions/markdown-language-features/server/src/browser/main.ts deleted file mode 100644 index 12612108..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/browser/main.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser'; -import { startVsCodeServer } from '../server'; - -const messageReader = new BrowserMessageReader(self); -const messageWriter = new BrowserMessageWriter(self); - -const connection = createConnection(messageReader, messageWriter); - -startVsCodeServer(connection); diff --git a/patched-vscode/extensions/markdown-language-features/server/src/browser/workerMain.ts b/patched-vscode/extensions/markdown-language-features/server/src/browser/workerMain.ts deleted file mode 100644 index e653751a..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/browser/workerMain.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import * as l10n from '@vscode/l10n'; - -let initialized = false; -const pendingMessages: any[] = []; - -const messageHandler = async (e: any) => { - if (!initialized) { - const l10nLog: string[] = []; - initialized = true; - const i10lLocation = e.data.i10lLocation; - if (i10lLocation) { - try { - await l10n.config({ uri: i10lLocation }); - l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}.`); - } catch (e) { - l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}.`); - } - } else { - l10nLog.push(`l10n: No bundle configured.`); - } - - await import('./main'); - - if (self.onmessage !== messageHandler) { - pendingMessages.forEach(msg => self.onmessage?.(msg)); - pendingMessages.length = 0; - } - - l10nLog.forEach(console.log); - } else { - pendingMessages.push(e); - } -}; -self.onmessage = messageHandler; diff --git a/patched-vscode/extensions/markdown-language-features/server/src/config.ts b/patched-vscode/extensions/markdown-language-features/server/src/config.ts deleted file mode 100644 index 5992258b..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/config.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { LsConfiguration } from 'vscode-markdown-languageservice'; - -export { LsConfiguration }; - -const defaultConfig: LsConfiguration = { - markdownFileExtensions: ['md'], - knownLinkedToFileExtensions: [ - 'jpg', - 'jpeg', - 'png', - 'gif', - 'webp', - 'bmp', - 'tiff', - ], - excludePaths: [ - '**/.*', - '**/node_modules/**', - ] -}; - -export function getLsConfiguration(overrides: Partial): LsConfiguration { - return { - ...defaultConfig, - ...overrides, - }; -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/configuration.ts b/patched-vscode/extensions/markdown-language-features/server/src/configuration.ts deleted file mode 100644 index 949573cf..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/configuration.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Connection, Emitter } from 'vscode-languageserver'; -import { Disposable } from './util/dispose'; - -export type ValidateEnabled = 'ignore' | 'warning' | 'error' | 'hint'; - -export interface Settings { - readonly markdown: { - readonly server: { - readonly log: 'off' | 'debug' | 'trace'; - }; - - readonly preferredMdPathExtensionStyle: 'auto' | 'includeExtension' | 'removeExtension'; - - readonly occurrencesHighlight: { - readonly enabled: boolean; - }; - - readonly suggest: { - readonly paths: { - readonly enabled: boolean; - readonly includeWorkspaceHeaderCompletions: 'never' | 'onSingleOrDoubleHash' | 'onDoubleHash'; - }; - }; - - readonly validate: { - readonly enabled: true; - readonly referenceLinks: { - readonly enabled: ValidateEnabled; - }; - readonly fragmentLinks: { - readonly enabled: ValidateEnabled; - }; - readonly fileLinks: { - readonly enabled: ValidateEnabled; - readonly markdownFragmentLinks: ValidateEnabled | 'inherit'; - }; - readonly ignoredLinks: readonly string[]; - readonly unusedLinkDefinitions: { - readonly enabled: ValidateEnabled; - }; - readonly duplicateLinkDefinitions: { - readonly enabled: ValidateEnabled; - }; - }; - }; -} - - -export class ConfigurationManager extends Disposable { - - private readonly _onDidChangeConfiguration = this._register(new Emitter()); - public readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; - - private _settings?: Settings; - - constructor(connection: Connection) { - super(); - - // The settings have changed. Is send on server activation as well. - this._register(connection.onDidChangeConfiguration((change) => { - this._settings = change.settings; - this._onDidChangeConfiguration.fire(this._settings!); - })); - } - - public getSettings(): Settings | undefined { - return this._settings; - } -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts b/patched-vscode/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts deleted file mode 100644 index d21a6fdb..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts +++ /dev/null @@ -1,114 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Connection, FullDocumentDiagnosticReport, TextDocuments, UnchangedDocumentDiagnosticReport } from 'vscode-languageserver'; -import * as md from 'vscode-markdown-languageservice'; -import { Disposable } from 'vscode-notebook-renderer/events'; -import { URI } from 'vscode-uri'; -import { ConfigurationManager, ValidateEnabled } from '../configuration'; -import { disposeAll } from '../util/dispose'; - -const defaultDiagnosticOptions: md.DiagnosticOptions = { - validateFileLinks: md.DiagnosticLevel.ignore, - validateReferences: md.DiagnosticLevel.ignore, - validateFragmentLinks: md.DiagnosticLevel.ignore, - validateMarkdownFileLinkFragments: md.DiagnosticLevel.ignore, - validateUnusedLinkDefinitions: md.DiagnosticLevel.ignore, - validateDuplicateLinkDefinitions: md.DiagnosticLevel.ignore, - ignoreLinks: [], -}; - -function convertDiagnosticLevel(enabled: ValidateEnabled): md.DiagnosticLevel | undefined { - switch (enabled) { - case 'error': return md.DiagnosticLevel.error; - case 'warning': return md.DiagnosticLevel.warning; - case 'ignore': return md.DiagnosticLevel.ignore; - case 'hint': return md.DiagnosticLevel.hint; - default: return md.DiagnosticLevel.ignore; - } -} - -function getDiagnosticsOptions(config: ConfigurationManager): md.DiagnosticOptions { - const settings = config.getSettings(); - if (!settings) { - return defaultDiagnosticOptions; - } - - const validateFragmentLinks = convertDiagnosticLevel(settings.markdown.validate.fragmentLinks.enabled); - return { - validateFileLinks: convertDiagnosticLevel(settings.markdown.validate.fileLinks.enabled), - validateReferences: convertDiagnosticLevel(settings.markdown.validate.referenceLinks.enabled), - validateFragmentLinks: convertDiagnosticLevel(settings.markdown.validate.fragmentLinks.enabled), - validateMarkdownFileLinkFragments: settings.markdown.validate.fileLinks.markdownFragmentLinks === 'inherit' ? validateFragmentLinks : convertDiagnosticLevel(settings.markdown.validate.fileLinks.markdownFragmentLinks), - validateUnusedLinkDefinitions: convertDiagnosticLevel(settings.markdown.validate.unusedLinkDefinitions.enabled), - validateDuplicateLinkDefinitions: convertDiagnosticLevel(settings.markdown.validate.duplicateLinkDefinitions.enabled), - ignoreLinks: settings.markdown.validate.ignoredLinks, - }; -} - -export function registerValidateSupport( - connection: Connection, - workspace: md.IWorkspace, - documents: TextDocuments, - ls: md.IMdLanguageService, - config: ConfigurationManager, - logger: md.ILogger, -): Disposable { - let diagnosticOptions: md.DiagnosticOptions = defaultDiagnosticOptions; - function updateDiagnosticsSetting(): void { - diagnosticOptions = getDiagnosticsOptions(config); - } - - const subs: Disposable[] = []; - const manager = ls.createPullDiagnosticsManager(); - subs.push(manager); - - subs.push(manager.onLinkedToFileChanged(() => { - // TODO: We only need to refresh certain files - connection.languages.diagnostics.refresh(); - })); - - const emptyDiagnosticsResponse = Object.freeze({ kind: 'full', items: [] }); - - connection.languages.diagnostics.on(async (params, token): Promise => { - logger.log(md.LogLevel.Debug, 'connection.languages.diagnostics.on', { document: params.textDocument.uri }); - - if (!config.getSettings()?.markdown.validate.enabled) { - return emptyDiagnosticsResponse; - } - - const uri = URI.parse(params.textDocument.uri); - if (!workspace.hasMarkdownDocument(uri)) { - return emptyDiagnosticsResponse; - } - - const document = await workspace.openMarkdownDocument(uri); - if (!document) { - return emptyDiagnosticsResponse; - } - - const diagnostics = await manager.computeDiagnostics(document, diagnosticOptions, token); - return { - kind: 'full', - items: diagnostics, - }; - }); - - updateDiagnosticsSetting(); - subs.push(config.onDidChangeConfiguration(() => { - updateDiagnosticsSetting(); - connection.languages.diagnostics.refresh(); - })); - - subs.push(documents.onDidClose(e => { - manager.disposeDocumentResources(URI.parse(e.document.uri)); - })); - - return { - dispose: () => { - disposeAll(subs); - } - }; -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/logging.ts b/patched-vscode/extensions/markdown-language-features/server/src/logging.ts deleted file mode 100644 index 0df6b8e0..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/logging.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as md from 'vscode-markdown-languageservice'; -import { ConfigurationManager } from './configuration'; -import { Disposable } from './util/dispose'; - -export class LogFunctionLogger extends Disposable implements md.ILogger { - - private static now(): string { - const now = new Date(); - return String(now.getUTCHours()).padStart(2, '0') - + ':' + String(now.getMinutes()).padStart(2, '0') - + ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + String(now.getMilliseconds()).padStart(3, '0'); - } - - private static data2String(data: any): string { - if (data instanceof Error) { - if (typeof data.stack === 'string') { - return data.stack; - } - return data.message; - } - if (typeof data === 'string') { - return data; - } - return JSON.stringify(data, undefined, 2); - } - - private _logLevel: md.LogLevel; - - constructor( - private readonly _logFn: typeof console.log, - private readonly _config: ConfigurationManager, - ) { - super(); - - this._register(this._config.onDidChangeConfiguration(() => { - this._logLevel = LogFunctionLogger.readLogLevel(this._config); - })); - - this._logLevel = LogFunctionLogger.readLogLevel(this._config); - } - - private static readLogLevel(config: ConfigurationManager): md.LogLevel { - switch (config.getSettings()?.markdown.server.log) { - case 'trace': return md.LogLevel.Trace; - case 'debug': return md.LogLevel.Debug; - case 'off': - default: - return md.LogLevel.Off; - } - } - - get level(): md.LogLevel { return this._logLevel; } - - public log(level: md.LogLevel, message: string, data?: any): void { - if (this.level < level) { - return; - } - - this.appendLine(`[${this.toLevelLabel(level)} ${LogFunctionLogger.now()}] ${message}`); - if (data) { - this.appendLine(LogFunctionLogger.data2String(data)); - } - } - - private toLevelLabel(level: md.LogLevel): string { - switch (level) { - case md.LogLevel.Off: return 'Off'; - case md.LogLevel.Debug: return 'Debug'; - case md.LogLevel.Trace: return 'Trace'; - } - } - - private appendLine(value: string): void { - this._logFn(value); - } -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/node/main.ts b/patched-vscode/extensions/markdown-language-features/server/src/node/main.ts deleted file mode 100644 index 7945d44a..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/node/main.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Connection, createConnection } from 'vscode-languageserver/node'; -import { startVsCodeServer } from '../server'; - -// Create a connection for the server. -const connection: Connection = createConnection(); - -console.log = connection.console.log.bind(connection.console); -console.error = connection.console.error.bind(connection.console); - -process.on('unhandledRejection', (e: any) => { - connection.console.error(`Unhandled exception ${e}`); -}); - -startVsCodeServer(connection); diff --git a/patched-vscode/extensions/markdown-language-features/server/src/node/workerMain.ts b/patched-vscode/extensions/markdown-language-features/server/src/node/workerMain.ts deleted file mode 100644 index f3369768..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/node/workerMain.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import * as l10n from '@vscode/l10n'; - -async function setupMain() { - const l10nLog: string[] = []; - - const i10lLocation = process.env['VSCODE_L10N_BUNDLE_LOCATION']; - if (i10lLocation) { - try { - await l10n.config({ uri: i10lLocation }); - l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}`); - } catch (e) { - l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}`); - } - } - await import('./main'); - - l10nLog.forEach(console.log); -} -setupMain(); diff --git a/patched-vscode/extensions/markdown-language-features/server/src/protocol.ts b/patched-vscode/extensions/markdown-language-features/server/src/protocol.ts deleted file mode 100644 index d06edbd4..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/protocol.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { FileRename, RequestType } from 'vscode-languageserver'; -import type * as lsp from 'vscode-languageserver-types'; -import type * as md from 'vscode-markdown-languageservice'; - -//#region From server -export const parse = new RequestType<{ uri: string; text?: string }, md.Token[], any>('markdown/parse'); - -export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile'); -export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory'); -export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat'); - -export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions; watchParentDirs: boolean }, void, any>('markdown/fs/watcher/create'); -export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete'); - -export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace'); -//#endregion - -//#region To server -export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace'); -export const getEditForFileRenames = new RequestType('markdown/getEditForFileRenames'); - -export const prepareUpdatePastedLinks = new RequestType<{ uri: string; ranges: lsp.Range[] }, string, any>('markdown/prepareUpdatePastedLinks'); -export const getUpdatePastedLinksEdit = new RequestType<{ pasteIntoDoc: string; metadata: string; edits: lsp.TextEdit[] }, lsp.TextEdit[] | undefined, any>('markdown/getUpdatePastedLinksEdit'); - -export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange'); - -export const resolveLinkTarget = new RequestType<{ linkText: string; uri: string }, md.ResolvedDocumentLinkTarget, any>('markdown/resolveLinkTarget'); -//#endregion diff --git a/patched-vscode/extensions/markdown-language-features/server/src/server.ts b/patched-vscode/extensions/markdown-language-features/server/src/server.ts deleted file mode 100644 index f1df5494..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/server.ts +++ /dev/null @@ -1,399 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as l10n from '@vscode/l10n'; -import { CancellationToken, CompletionRegistrationOptions, CompletionRequest, Connection, Disposable, DocumentHighlightRegistrationOptions, DocumentHighlightRequest, InitializeParams, InitializeResult, NotebookDocuments, ResponseError, TextDocuments } from 'vscode-languageserver'; -import { TextDocument } from 'vscode-languageserver-textdocument'; -import * as lsp from 'vscode-languageserver-types'; -import * as md from 'vscode-markdown-languageservice'; -import { URI } from 'vscode-uri'; -import { LsConfiguration, getLsConfiguration } from './config'; -import { ConfigurationManager, Settings } from './configuration'; -import { registerValidateSupport } from './languageFeatures/diagnostics'; -import { LogFunctionLogger } from './logging'; -import * as protocol from './protocol'; -import { IDisposable } from './util/dispose'; -import { VsCodeClientWorkspace } from './workspace'; - -interface MdServerInitializationOptions extends LsConfiguration { } - -const organizeLinkDefKind = 'source.organizeLinkDefinitions'; - -export async function startVsCodeServer(connection: Connection) { - const configurationManager = new ConfigurationManager(connection); - const logger = new LogFunctionLogger(connection.console.log.bind(connection.console), configurationManager); - - const parser = new class implements md.IMdParser { - slugifier = md.githubSlugifier; - - tokenize(document: md.ITextDocument): Promise { - return connection.sendRequest(protocol.parse, { - uri: document.uri, - - // Clients won't be able to read temp documents. - // Send along the full text for parsing. - text: document.version < 0 ? document.getText() : undefined - }); - } - }; - - const documents = new TextDocuments(TextDocument); - const notebooks = new NotebookDocuments(documents); - - const workspaceFactory: WorkspaceFactory = ({ connection, config, workspaceFolders }) => { - const workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks, logger); - workspace.workspaceFolders = (workspaceFolders ?? []).map(x => URI.parse(x.uri)); - return workspace; - }; - - return startServer(connection, { documents, notebooks, configurationManager, logger, parser, workspaceFactory }); -} - -type WorkspaceFactory = (config: { - connection: Connection; - config: LsConfiguration; - workspaceFolders?: lsp.WorkspaceFolder[] | null; -}) => md.IWorkspace; - -export async function startServer(connection: Connection, serverConfig: { - documents: TextDocuments; - notebooks?: NotebookDocuments; - configurationManager: ConfigurationManager; - logger: md.ILogger; - parser: md.IMdParser; - workspaceFactory: WorkspaceFactory; -}) { - const { documents, notebooks } = serverConfig; - - let mdLs: md.IMdLanguageService | undefined; - - connection.onInitialize((params: InitializeParams): InitializeResult => { - const initOptions = params.initializationOptions as MdServerInitializationOptions | undefined; - - const mdConfig = getLsConfiguration(initOptions ?? {}); - - const workspace = serverConfig.workspaceFactory({ connection, config: mdConfig, workspaceFolders: params.workspaceFolders }); - mdLs = md.createLanguageService({ - workspace, - parser: serverConfig.parser, - logger: serverConfig.logger, - ...mdConfig, - get preferredMdPathExtensionStyle() { - switch (serverConfig.configurationManager.getSettings()?.markdown.preferredMdPathExtensionStyle) { - case 'includeExtension': return md.PreferredMdPathExtensionStyle.includeExtension; - case 'removeExtension': return md.PreferredMdPathExtensionStyle.removeExtension; - case 'auto': - default: - return md.PreferredMdPathExtensionStyle.auto; - } - } - }); - - registerCompletionsSupport(connection, documents, mdLs, serverConfig.configurationManager); - registerDocumentHighlightSupport(connection, documents, mdLs, serverConfig.configurationManager); - registerValidateSupport(connection, workspace, documents, mdLs, serverConfig.configurationManager, serverConfig.logger); - - return { - capabilities: { - diagnosticProvider: { - documentSelector: null, - identifier: 'markdown', - interFileDependencies: true, - workspaceDiagnostics: false, - }, - codeActionProvider: { - resolveProvider: true, - codeActionKinds: [ - organizeLinkDefKind, - 'quickfix', - 'refactor', - ] - }, - definitionProvider: true, - documentLinkProvider: { resolveProvider: true }, - documentSymbolProvider: true, - foldingRangeProvider: true, - hoverProvider: true, - referencesProvider: true, - renameProvider: { prepareProvider: true, }, - selectionRangeProvider: true, - workspaceSymbolProvider: true, - workspace: { - workspaceFolders: { - supported: true, - changeNotifications: true, - }, - } - } - }; - }); - - connection.onDocumentLinks(async (params, token): Promise => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return []; - } - return mdLs!.getDocumentLinks(document, token); - }); - - connection.onDocumentLinkResolve(async (link, token): Promise => { - return mdLs!.resolveDocumentLink(link, token); - }); - - connection.onDocumentSymbol(async (params, token): Promise => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return []; - } - return mdLs!.getDocumentSymbols(document, { includeLinkDefinitions: true }, token); - }); - - connection.onFoldingRanges(async (params, token): Promise => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return []; - } - return mdLs!.getFoldingRanges(document, token); - }); - - connection.onSelectionRanges(async (params, token): Promise => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return []; - } - return mdLs!.getSelectionRanges(document, params.positions, token); - }); - - connection.onWorkspaceSymbol(async (params, token): Promise => { - return mdLs!.getWorkspaceSymbols(params.query, token); - }); - - connection.onReferences(async (params, token): Promise => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return []; - } - return mdLs!.getReferences(document, params.position, params.context, token); - }); - - connection.onDefinition(async (params, token): Promise => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return undefined; - } - return mdLs!.getDefinition(document, params.position, token); - }); - - connection.onPrepareRename(async (params, token) => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return undefined; - } - - try { - return await mdLs!.prepareRename(document, params.position, token); - } catch (e) { - if (e instanceof md.RenameNotSupportedAtLocationError) { - throw new ResponseError(0, e.message); - } else { - throw e; - } - } - }); - - connection.onRenameRequest(async (params, token) => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return undefined; - } - return mdLs!.getRenameEdit(document, params.position, params.newName, token); - }); - - interface OrganizeLinkActionData { - readonly uri: string; - } - - connection.onCodeAction(async (params, token) => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return undefined; - } - - if (params.context.only?.some(kind => kind === 'source' || kind.startsWith('source.'))) { - const action: lsp.CodeAction = { - title: l10n.t("Organize link definitions"), - kind: organizeLinkDefKind, - data: { uri: document.uri } satisfies OrganizeLinkActionData, - }; - return [action]; - } - - return mdLs!.getCodeActions(document, params.range, params.context, token); - }); - - connection.onCodeActionResolve(async (codeAction, token) => { - if (codeAction.kind === organizeLinkDefKind) { - const data = codeAction.data as OrganizeLinkActionData; - const document = documents.get(data.uri); - if (!document) { - return codeAction; - } - - const edits = (await mdLs?.organizeLinkDefinitions(document, { removeUnused: true }, token)) || []; - codeAction.edit = { - changes: { - [data.uri]: edits - } - }; - return codeAction; - } - - return codeAction; - }); - - connection.onHover(async (params, token) => { - const document = documents.get(params.textDocument.uri); - if (!document) { - return null; - } - - return mdLs!.getHover(document, params.position, token); - }); - - connection.onRequest(protocol.getReferencesToFileInWorkspace, (async (params: { uri: string }, token: CancellationToken) => { - return mdLs!.getFileReferences(URI.parse(params.uri), token); - })); - - connection.onRequest(protocol.getEditForFileRenames, (async (params, token: CancellationToken) => { - const result = await mdLs!.getRenameFilesInWorkspaceEdit(params.map(x => ({ oldUri: URI.parse(x.oldUri), newUri: URI.parse(x.newUri) })), token); - if (!result) { - return result; - } - - return { - edit: result.edit, - participatingRenames: result.participatingRenames.map(rename => ({ oldUri: rename.oldUri.toString(), newUri: rename.newUri.toString() })) - }; - })); - - connection.onRequest(protocol.prepareUpdatePastedLinks, (async (params, token: CancellationToken) => { - const document = documents.get(params.uri); - if (!document) { - return undefined; - } - - return mdLs!.prepareUpdatePastedLinks(document, params.ranges, token); - })); - - connection.onRequest(protocol.getUpdatePastedLinksEdit, (async (params, token: CancellationToken) => { - const document = documents.get(params.pasteIntoDoc); - if (!document) { - return undefined; - } - - // TODO: Figure out why range types are lying - const edits = params.edits.map((edit: any) => lsp.TextEdit.replace(lsp.Range.create(edit.range[0].line, edit.range[0].character, edit.range[1].line, edit.range[1].character), edit.newText)); - return mdLs!.getUpdatePastedLinksEdit(document, edits, params.metadata, token); - })); - - connection.onRequest(protocol.resolveLinkTarget, (async (params, token: CancellationToken) => { - return mdLs!.resolveLinkTarget(params.linkText, URI.parse(params.uri), token); - })); - - documents.listen(connection); - notebooks?.listen(connection); - connection.listen(); -} - -function registerDynamicClientFeature( - config: ConfigurationManager, - isEnabled: (settings: Settings | undefined) => boolean, - register: () => Promise, -) { - let registration: Promise | undefined; - function update() { - const settings = config.getSettings(); - if (isEnabled(settings)) { - if (!registration) { - registration = register(); - } - } else { - registration?.then(x => x.dispose()); - registration = undefined; - } - } - - update(); - return config.onDidChangeConfiguration(() => update()); -} - -function registerCompletionsSupport( - connection: Connection, - documents: TextDocuments, - ls: md.IMdLanguageService, - config: ConfigurationManager, -): IDisposable { - function getIncludeWorkspaceHeaderCompletions(): md.IncludeWorkspaceHeaderCompletions { - switch (config.getSettings()?.markdown.suggest.paths.includeWorkspaceHeaderCompletions) { - case 'onSingleOrDoubleHash': return md.IncludeWorkspaceHeaderCompletions.onSingleOrDoubleHash; - case 'onDoubleHash': return md.IncludeWorkspaceHeaderCompletions.onDoubleHash; - case 'never': - default: return md.IncludeWorkspaceHeaderCompletions.never; - } - } - - connection.onCompletion(async (params, token): Promise => { - const settings = config.getSettings(); - if (!settings?.markdown.suggest.paths.enabled) { - return []; - } - - const document = documents.get(params.textDocument.uri); - if (document) { - // TODO: remove any type after picking up new release with correct types - return ls.getCompletionItems(document, params.position, { - ...(params.context || {}), - includeWorkspaceHeaderCompletions: getIncludeWorkspaceHeaderCompletions(), - } as any, token); - } - return []; - }); - - return registerDynamicClientFeature(config, (settings) => !!settings?.markdown.suggest.paths.enabled, () => { - const registrationOptions: CompletionRegistrationOptions = { - documentSelector: null, - triggerCharacters: ['.', '/', '#'], - }; - return connection.client.register(CompletionRequest.type, registrationOptions); - }); -} - -function registerDocumentHighlightSupport( - connection: Connection, - documents: TextDocuments, - mdLs: md.IMdLanguageService, - configurationManager: ConfigurationManager -) { - connection.onDocumentHighlight(async (params, token) => { - const settings = configurationManager.getSettings(); - if (!settings?.markdown.occurrencesHighlight.enabled) { - return undefined; - } - - const document = documents.get(params.textDocument.uri); - if (!document) { - return undefined; - } - - return mdLs!.getDocumentHighlights(document, params.position, token); - }); - - return registerDynamicClientFeature(configurationManager, (settings) => !!settings?.markdown.occurrencesHighlight.enabled, () => { - const registrationOptions: DocumentHighlightRegistrationOptions = { - documentSelector: null, - }; - return connection.client.register(DocumentHighlightRequest.type, registrationOptions); - }); -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/util/dispose.ts b/patched-vscode/extensions/markdown-language-features/server/src/util/dispose.ts deleted file mode 100644 index 2e9d8dc6..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/util/dispose.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function disposeAll(disposables: Iterable) { - const errors: any[] = []; - - for (const disposable of disposables) { - try { - disposable.dispose(); - } catch (e) { - errors.push(e); - } - } - - if (errors.length === 1) { - throw errors[0]; - } else if (errors.length > 1) { - throw new AggregateError(errors, 'Encountered errors while disposing of store'); - } -} - -export interface IDisposable { - dispose(): void; -} - -export abstract class Disposable { - private _isDisposed = false; - - protected _disposables: IDisposable[] = []; - - public dispose(): any { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - disposeAll(this._disposables); - } - - protected _register(value: T): T { - if (this._isDisposed) { - value.dispose(); - } else { - this._disposables.push(value); - } - return value; - } - - protected get isDisposed() { - return this._isDisposed; - } -} - diff --git a/patched-vscode/extensions/markdown-language-features/server/src/util/file.ts b/patched-vscode/extensions/markdown-language-features/server/src/util/file.ts deleted file mode 100644 index 10e95bf5..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/util/file.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TextDocument } from 'vscode-languageserver-textdocument'; -import { URI, Utils } from 'vscode-uri'; -import { LsConfiguration } from '../config'; - -export function looksLikeMarkdownPath(config: LsConfiguration, resolvedHrefPath: URI) { - return config.markdownFileExtensions.includes(Utils.extname(resolvedHrefPath).toLowerCase().replace('.', '')); -} - -export function isMarkdownFile(document: TextDocument) { - return document.languageId === 'markdown'; -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/util/limiter.ts b/patched-vscode/extensions/markdown-language-features/server/src/util/limiter.ts deleted file mode 100644 index bd4153cd..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/util/limiter.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -interface ILimitedTaskFactory { - factory: ITask>; - c: (value: T | Promise) => void; - e: (error?: unknown) => void; -} - -interface ITask { - (): T; -} - -/** - * A helper to queue N promises and run them all with a max degree of parallelism. The helper - * ensures that at any time no more than M promises are running at the same time. - * - * Taken from 'src/vs/base/common/async.ts' - */ -export class Limiter { - - private _size = 0; - private runningPromises: number; - private readonly maxDegreeOfParalellism: number; - private readonly outstandingPromises: ILimitedTaskFactory[]; - - constructor(maxDegreeOfParalellism: number) { - this.maxDegreeOfParalellism = maxDegreeOfParalellism; - this.outstandingPromises = []; - this.runningPromises = 0; - } - - get size(): number { - return this._size; - } - - queue(factory: ITask>): Promise { - this._size++; - - return new Promise((c, e) => { - this.outstandingPromises.push({ factory, c, e }); - this.consume(); - }); - } - - private consume(): void { - while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { - const iLimitedTask = this.outstandingPromises.shift()!; - this.runningPromises++; - - const promise = iLimitedTask.factory(); - promise.then(iLimitedTask.c, iLimitedTask.e); - promise.then(() => this.consumed(), () => this.consumed()); - } - } - - private consumed(): void { - this._size--; - this.runningPromises--; - - if (this.outstandingPromises.length > 0) { - this.consume(); - } - } -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/util/resourceMap.ts b/patched-vscode/extensions/markdown-language-features/server/src/util/resourceMap.ts deleted file mode 100644 index 7cec9d66..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/util/resourceMap.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from 'vscode-uri'; - - -type ResourceToKey = (uri: URI) => string; - -const defaultResourceToKey = (resource: URI): string => resource.toString(); - -export class ResourceMap { - - private readonly map = new Map(); - - private readonly toKey: ResourceToKey; - - constructor(toKey: ResourceToKey = defaultResourceToKey) { - this.toKey = toKey; - } - - public set(uri: URI, value: T): this { - this.map.set(this.toKey(uri), { uri, value }); - return this; - } - - public get(resource: URI): T | undefined { - return this.map.get(this.toKey(resource))?.value; - } - - public has(resource: URI): boolean { - return this.map.has(this.toKey(resource)); - } - - public get size(): number { - return this.map.size; - } - - public clear(): void { - this.map.clear(); - } - - public delete(resource: URI): boolean { - return this.map.delete(this.toKey(resource)); - } - - public *values(): IterableIterator { - for (const entry of this.map.values()) { - yield entry.value; - } - } - - public *keys(): IterableIterator { - for (const entry of this.map.values()) { - yield entry.uri; - } - } - - public *entries(): IterableIterator<[URI, T]> { - for (const entry of this.map.values()) { - yield [entry.uri, entry.value]; - } - } - - public [Symbol.iterator](): IterableIterator<[URI, T]> { - return this.entries(); - } -} diff --git a/patched-vscode/extensions/markdown-language-features/server/src/workspace.ts b/patched-vscode/extensions/markdown-language-features/server/src/workspace.ts deleted file mode 100644 index 13e5c6b4..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/src/workspace.ts +++ /dev/null @@ -1,433 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Connection, Emitter, FileChangeType, NotebookDocuments, Position, Range, TextDocuments } from 'vscode-languageserver'; -import { TextDocument } from 'vscode-languageserver-textdocument'; -import * as md from 'vscode-markdown-languageservice'; -import { URI } from 'vscode-uri'; -import { LsConfiguration } from './config'; -import * as protocol from './protocol'; -import { isMarkdownFile, looksLikeMarkdownPath } from './util/file'; -import { Limiter } from './util/limiter'; -import { ResourceMap } from './util/resourceMap'; -import { Schemes } from './util/schemes'; - -declare const TextDecoder: any; - -class VsCodeDocument implements md.ITextDocument { - - private inMemoryDoc?: TextDocument; - private onDiskDoc?: TextDocument; - - readonly uri: string; - - constructor(uri: string, init: { inMemoryDoc: TextDocument }); - constructor(uri: string, init: { onDiskDoc: TextDocument }); - constructor(uri: string, init: { inMemoryDoc?: TextDocument; onDiskDoc?: TextDocument }) { - this.uri = uri; - this.inMemoryDoc = init?.inMemoryDoc; - this.onDiskDoc = init?.onDiskDoc; - } - - get version(): number { - return this.inMemoryDoc?.version ?? this.onDiskDoc?.version ?? 0; - } - - get lineCount(): number { - return this.inMemoryDoc?.lineCount ?? this.onDiskDoc?.lineCount ?? 0; - } - - getText(range?: Range): string { - if (this.inMemoryDoc) { - return this.inMemoryDoc.getText(range); - } - - if (this.onDiskDoc) { - return this.onDiskDoc.getText(range); - } - - throw new Error('Document has been closed'); - } - - positionAt(offset: number): Position { - if (this.inMemoryDoc) { - return this.inMemoryDoc.positionAt(offset); - } - - if (this.onDiskDoc) { - return this.onDiskDoc.positionAt(offset); - } - - throw new Error('Document has been closed'); - } - - offsetAt(position: Position): number { - if (this.inMemoryDoc) { - return this.inMemoryDoc.offsetAt(position); - } - - if (this.onDiskDoc) { - return this.onDiskDoc.offsetAt(position); - } - - throw new Error('Document has been closed'); - } - - hasInMemoryDoc(): boolean { - return !!this.inMemoryDoc; - } - - isDetached(): boolean { - return !this.onDiskDoc && !this.inMemoryDoc; - } - - setInMemoryDoc(doc: TextDocument | undefined) { - this.inMemoryDoc = doc; - } - - setOnDiskDoc(doc: TextDocument | undefined) { - this.onDiskDoc = doc; - } -} - -export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { - - private readonly _onDidCreateMarkdownDocument = new Emitter(); - public readonly onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocument.event; - - private readonly _onDidChangeMarkdownDocument = new Emitter(); - public readonly onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocument.event; - - private readonly _onDidDeleteMarkdownDocument = new Emitter(); - public readonly onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocument.event; - - private readonly _documentCache = new ResourceMap(); - - private readonly _utf8Decoder = new TextDecoder('utf-8'); - - private _watcherPool = 0; - private readonly _watchers = new Map; - readonly onDidCreate: Emitter; - readonly onDidDelete: Emitter; - }>(); - - constructor( - private readonly connection: Connection, - private readonly config: LsConfiguration, - private readonly documents: TextDocuments, - private readonly notebooks: NotebookDocuments, - private readonly logger: md.ILogger, - ) { - documents.onDidOpen(e => { - if (!this.isRelevantMarkdownDocument(e.document)) { - return; - } - - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.TextDocument.onDidOpen', { document: e.document.uri }); - - const uri = URI.parse(e.document.uri); - const doc = this._documentCache.get(uri); - - if (doc) { - // File already existed on disk - doc.setInMemoryDoc(e.document); - - // The content visible to the language service may have changed since the in-memory doc - // may differ from the one on-disk. To be safe we always fire a change event. - this._onDidChangeMarkdownDocument.fire(doc); - } else { - // We're creating the file for the first time - const doc = new VsCodeDocument(e.document.uri, { inMemoryDoc: e.document }); - this._documentCache.set(uri, doc); - this._onDidCreateMarkdownDocument.fire(doc); - } - }); - - documents.onDidChangeContent(e => { - if (!this.isRelevantMarkdownDocument(e.document)) { - return; - } - - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.TextDocument.onDidChanceContent', { document: e.document.uri }); - - const uri = URI.parse(e.document.uri); - const entry = this._documentCache.get(uri); - if (entry) { - entry.setInMemoryDoc(e.document); - this._onDidChangeMarkdownDocument.fire(entry); - } - }); - - documents.onDidClose(async e => { - if (!this.isRelevantMarkdownDocument(e.document)) { - return; - } - - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.TextDocument.onDidClose', { document: e.document.uri }); - - const uri = URI.parse(e.document.uri); - const doc = this._documentCache.get(uri); - if (!doc) { - // Document was never opened - return; - } - - doc.setInMemoryDoc(undefined); - if (doc.isDetached()) { - // The document has been fully closed - this.doDeleteDocument(uri); - return; - } - - // Check that if file has been deleted on disk. - // This can happen when directories are renamed / moved. VS Code's file system watcher does not - // notify us when this happens. - if (!(await this.statBypassingCache(uri))) { - if (this._documentCache.get(uri) === doc && !doc.hasInMemoryDoc()) { - this.doDeleteDocument(uri); - return; - } - } - - // The document still exists on disk - // To be safe, tell the service that the document has changed because the - // in-memory doc contents may be different than the disk doc contents. - this._onDidChangeMarkdownDocument.fire(doc); - }); - - connection.onDidChangeWatchedFiles(async ({ changes }) => { - for (const change of changes) { - const resource = URI.parse(change.uri); - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.onDidChangeWatchedFiles', { type: change.type, resource: resource.toString() }); - switch (change.type) { - case FileChangeType.Changed: { - const entry = this._documentCache.get(resource); - if (entry) { - // Refresh the on-disk state - const document = await this.openMarkdownDocumentFromFs(resource); - if (document) { - this._onDidChangeMarkdownDocument.fire(document); - } - } - break; - } - case FileChangeType.Created: { - const entry = this._documentCache.get(resource); - if (entry) { - // Create or update the on-disk state - const document = await this.openMarkdownDocumentFromFs(resource); - if (document) { - this._onDidCreateMarkdownDocument.fire(document); - } - } - break; - } - case FileChangeType.Deleted: { - const entry = this._documentCache.get(resource); - if (entry) { - entry.setOnDiskDoc(undefined); - if (entry.isDetached()) { - this.doDeleteDocument(resource); - } - } - break; - } - } - } - }); - - connection.onRequest(protocol.fs_watcher_onChange, params => { - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.fs_watcher_onChange', { kind: params.kind, uri: params.uri }); - - const watcher = this._watchers.get(params.id); - if (!watcher) { - return; - } - - switch (params.kind) { - case 'create': watcher.onDidCreate.fire(URI.parse(params.uri)); return; - case 'change': watcher.onDidChange.fire(URI.parse(params.uri)); return; - case 'delete': watcher.onDidDelete.fire(URI.parse(params.uri)); return; - } - }); - } - - public listen() { - this.connection.workspace.onDidChangeWorkspaceFolders(async () => { - this.workspaceFolders = (await this.connection.workspace.getWorkspaceFolders() ?? []).map(x => URI.parse(x.uri)); - }); - } - - private _workspaceFolders: readonly URI[] = []; - - get workspaceFolders(): readonly URI[] { - return this._workspaceFolders; - } - - set workspaceFolders(value: readonly URI[]) { - this._workspaceFolders = value; - } - - async getAllMarkdownDocuments(): Promise> { - // Add opened files (such as untitled files) - const openTextDocumentResults = this.documents.all() - .filter(doc => this.isRelevantMarkdownDocument(doc)); - - const allDocs = new ResourceMap(); - for (const doc of openTextDocumentResults) { - allDocs.set(URI.parse(doc.uri), doc); - } - - // And then add files on disk - const maxConcurrent = 20; - const limiter = new Limiter(maxConcurrent); - const resources = await this.connection.sendRequest(protocol.findMarkdownFilesInWorkspace, {}); - await Promise.all(resources.map(strResource => { - return limiter.queue(async () => { - const resource = URI.parse(strResource); - if (allDocs.has(resource)) { - return; - } - - const doc = await this.openMarkdownDocument(resource); - if (doc) { - allDocs.set(resource, doc); - } - return doc; - }); - })); - - return allDocs.values(); - } - - hasMarkdownDocument(resource: URI): boolean { - return !!this.documents.get(resource.toString()); - } - - async openMarkdownDocument(resource: URI): Promise { - const existing = this._documentCache.get(resource); - if (existing) { - return existing; - } - - const matchingDocument = this.documents.get(resource.toString()); - if (matchingDocument) { - let entry = this._documentCache.get(resource); - if (entry) { - entry.setInMemoryDoc(matchingDocument); - } else { - entry = new VsCodeDocument(resource.toString(), { inMemoryDoc: matchingDocument }); - this._documentCache.set(resource, entry); - } - - return entry; - } - - return this.openMarkdownDocumentFromFs(resource); - } - - private async openMarkdownDocumentFromFs(resource: URI): Promise { - if (!looksLikeMarkdownPath(this.config, resource)) { - return undefined; - } - - try { - const response = await this.connection.sendRequest(protocol.fs_readFile, { uri: resource.toString() }); - // TODO: LSP doesn't seem to handle Array buffers well - const bytes = new Uint8Array(response); - - // We assume that markdown is in UTF-8 - const text = this._utf8Decoder.decode(bytes); - const doc = new VsCodeDocument(resource.toString(), { - onDiskDoc: TextDocument.create(resource.toString(), 'markdown', 0, text) - }); - this._documentCache.set(resource, doc); - return doc; - } catch (e) { - return undefined; - } - } - - async stat(resource: URI): Promise { - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.stat', { resource: resource.toString() }); - if (this._documentCache.has(resource)) { - return { isDirectory: false }; - } - return this.statBypassingCache(resource); - } - - private async statBypassingCache(resource: URI): Promise { - const uri = resource.toString(); - if (this.documents.get(uri)) { - return { isDirectory: false }; - } - const fsResult = await this.connection.sendRequest(protocol.fs_stat, { uri }); - return fsResult ?? undefined; // Force convert null to undefined - } - - async readDirectory(resource: URI): Promise<[string, md.FileStat][]> { - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.readDir', { resource: resource.toString() }); - return this.connection.sendRequest(protocol.fs_readDirectory, { uri: resource.toString() }); - } - - getContainingDocument(resource: URI): md.ContainingDocumentContext | undefined { - if (resource.scheme === Schemes.notebookCell) { - const nb = this.notebooks.findNotebookDocumentForCell(resource.toString()); - if (nb) { - return { - uri: URI.parse(nb.uri), - children: nb.cells.map(cell => ({ uri: URI.parse(cell.document) })), - }; - } - } - return undefined; - } - - watchFile(resource: URI, options: md.FileWatcherOptions): md.IFileSystemWatcher { - const id = this._watcherPool++; - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.watchFile', { id, resource: resource.toString() }); - - const entry = { - resource, - options, - onDidCreate: new Emitter(), - onDidChange: new Emitter(), - onDidDelete: new Emitter(), - }; - this._watchers.set(id, entry); - - this.connection.sendRequest(protocol.fs_watcher_create, { - id, - uri: resource.toString(), - options, - watchParentDirs: true, - }); - - return { - onDidCreate: entry.onDidCreate.event, - onDidChange: entry.onDidChange.event, - onDidDelete: entry.onDidDelete.event, - dispose: () => { - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.disposeWatcher', { id, resource: resource.toString() }); - this.connection.sendRequest(protocol.fs_watcher_delete, { id }); - this._watchers.delete(id); - } - }; - } - - private isRelevantMarkdownDocument(doc: TextDocument) { - return isMarkdownFile(doc) && URI.parse(doc.uri).scheme !== 'vscode-bulkeditpreview'; - } - - private doDeleteDocument(uri: URI) { - this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.deleteDocument', { document: uri.toString() }); - - this._documentCache.delete(uri); - this._onDidDeleteMarkdownDocument.fire(uri); - } -} diff --git a/patched-vscode/extensions/markdown-language-features/server/tsconfig.json b/patched-vscode/extensions/markdown-language-features/server/tsconfig.json deleted file mode 100644 index 0a73af08..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./out", - "lib": [ - "ES2020", - "ES2021.Promise", - "WebWorker" - ] - }, - "include": [ - "src/**/*" - ] -} diff --git a/patched-vscode/extensions/markdown-language-features/server/yarn.lock b/patched-vscode/extensions/markdown-language-features/server/yarn.lock deleted file mode 100644 index 14878343..00000000 --- a/patched-vscode/extensions/markdown-language-features/server/yarn.lock +++ /dev/null @@ -1,176 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@types/node@20.x": - version "20.11.24" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.24.tgz#cc207511104694e84e9fb17f9a0c4c42d4517792" - integrity sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long== - dependencies: - undici-types "~5.26.4" - -"@vscode/l10n@^0.0.10": - version "0.0.10" - resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.10.tgz#9c513107c690c0dd16e3ec61e453743de15ebdb0" - integrity sha512-E1OCmDcDWa0Ya7vtSjp/XfHFGqYJfh+YPC1RkATU71fTac+j1JjCcB3qwSzmlKAighx2WxhLlfhS0RwAN++PFQ== - -"@vscode/l10n@^0.0.11": - version "0.0.11" - resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.11.tgz#325d7beb2cfb87162bc624d16c4d546de6a73b72" - integrity sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA== - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -css-select@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" - integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== - dependencies: - boolbase "^1.0.0" - css-what "^6.1.0" - domhandler "^5.0.2" - domutils "^3.0.1" - nth-check "^2.0.1" - -css-what@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -dom-serializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" - integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.2" - entities "^4.2.0" - -domelementtype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^5.0.2, domhandler@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" - integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - dependencies: - domelementtype "^2.3.0" - -domutils@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" - integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== - dependencies: - dom-serializer "^2.0.0" - domelementtype "^2.3.0" - domhandler "^5.0.3" - -entities@^4.2.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -node-html-parser@^6.1.5: - version "6.1.5" - resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.5.tgz#c819dceb13a10a7642ff92f94f870b4f77968097" - integrity sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg== - dependencies: - css-select "^5.1.0" - he "1.2.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -vscode-jsonrpc@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz#cb9989c65e219e18533cc38e767611272d274c94" - integrity sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw== - -vscode-jsonrpc@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" - integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== - -vscode-languageserver-protocol@3.17.3: - version "3.17.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz#6d0d54da093f0c0ee3060b81612cce0f11060d57" - integrity sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA== - dependencies: - vscode-jsonrpc "8.1.0" - vscode-languageserver-types "3.17.3" - -vscode-languageserver-protocol@^3.17.1: - version "3.17.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea" - integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== - dependencies: - vscode-jsonrpc "8.2.0" - vscode-languageserver-types "3.17.5" - -vscode-languageserver-textdocument@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf" - integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA== - -vscode-languageserver-textdocument@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0" - integrity sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q== - -vscode-languageserver-types@3.17.3, vscode-languageserver-types@^3.17.3: - version "3.17.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" - integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== - -vscode-languageserver-types@3.17.5: - version "3.17.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" - integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== - -vscode-languageserver@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz#5024253718915d84576ce6662dd46a791498d827" - integrity sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw== - dependencies: - vscode-languageserver-protocol "3.17.3" - -vscode-markdown-languageservice@^0.5.0-alpha.6: - version "0.5.0-alpha.6" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.5.0-alpha.6.tgz#3aa5fc94fea3d5d7f0cd970e64348e2791643dc0" - integrity sha512-mA1JCA7aHHSek5gr8Yv7C3esEPo2hRrgxmoZUDRro+pnwbdsJuRaWOKWtCWxejRUVVVhc/5yTK2X64Jx9OCmFQ== - dependencies: - "@vscode/l10n" "^0.0.10" - node-html-parser "^6.1.5" - picomatch "^2.3.1" - vscode-languageserver-protocol "^3.17.1" - vscode-languageserver-textdocument "^1.0.11" - vscode-uri "^3.0.7" - -vscode-uri@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.7.tgz#6d19fef387ee6b46c479e5fb00870e15e58c1eb8" - integrity sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA== diff --git a/patched-vscode/extensions/markdown-language-features/src/extension.browser.ts b/patched-vscode/extensions/markdown-language-features/src/extension.browser.ts index 30639672..2bfc63fc 100644 --- a/patched-vscode/extensions/markdown-language-features/src/extension.browser.ts +++ b/patched-vscode/extensions/markdown-language-features/src/extension.browser.ts @@ -27,7 +27,7 @@ export async function activate(context: vscode.ExtensionContext) { } function startServer(context: vscode.ExtensionContext, parser: IMdParser): Promise { - const serverMain = vscode.Uri.joinPath(context.extensionUri, 'server/dist/browser/workerMain.js'); + const serverMain = vscode.Uri.joinPath(context.extensionUri, 'dist', 'browser', 'serverWorkerMain.js'); const worker = new Worker(serverMain.toString()); worker.postMessage({ i10lLocation: vscode.l10n.uri?.toString() ?? '' }); diff --git a/patched-vscode/extensions/markdown-language-features/src/extension.ts b/patched-vscode/extensions/markdown-language-features/src/extension.ts index b14ab6d0..98ea87df 100644 --- a/patched-vscode/extensions/markdown-language-features/src/extension.ts +++ b/patched-vscode/extensions/markdown-language-features/src/extension.ts @@ -27,10 +27,15 @@ export async function activate(context: vscode.ExtensionContext) { } function startServer(context: vscode.ExtensionContext, parser: IMdParser): Promise { - const clientMain = vscode.extensions.getExtension('vscode.markdown-language-features')?.packageJSON?.main || ''; - - const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/workerMain`; - const serverModule = context.asAbsolutePath(serverMain); + const isDebugBuild = context.extension.packageJSON.main.includes('/out/'); + + const serverModule = context.asAbsolutePath( + isDebugBuild + // For local non bundled version of vscode-markdown-languageserver + // ? './node_modules/vscode-markdown-languageserver/out/node/workerMain' + ? './node_modules/vscode-markdown-languageserver/dist/node/workerMain' + : './dist/serverWorkerMain' + ); // The debug options for the server const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (7000 + Math.round(Math.random() * 999))] }; diff --git a/patched-vscode/extensions/markdown-language-features/src/languageFeatures/updateLinksOnPaste.ts b/patched-vscode/extensions/markdown-language-features/src/languageFeatures/updateLinksOnPaste.ts index 36b6eacf..c8ad4c72 100644 --- a/patched-vscode/extensions/markdown-language-features/src/languageFeatures/updateLinksOnPaste.ts +++ b/patched-vscode/extensions/markdown-language-features/src/languageFeatures/updateLinksOnPaste.ts @@ -9,7 +9,7 @@ import { Mime } from '../util/mimes'; class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider { - public static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'markdown', 'updateLinks'); + public static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'updateLinks'); public static readonly metadataMime = 'vnd.vscode.markdown.updateLinksMetadata'; @@ -26,6 +26,7 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider if (token.isCancellationRequested) { return; } + dataTransfer.set(UpdatePastedLinksEditProvider.metadataMime, new vscode.DataTransferItem(metadata)); } @@ -33,7 +34,7 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, - _context: vscode.DocumentPasteEditContext, + context: vscode.DocumentPasteEditContext, token: vscode.CancellationToken, ): Promise { if (!this._isEnabled(document)) { @@ -56,7 +57,7 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider // - Copy with multiple cursors and paste into multiple locations // - ... const edits = await this._client.getUpdatePastedLinksEdit(document.uri, ranges.map(x => new vscode.TextEdit(x, text)), metadata, token); - if (!edits || !edits.length || token.isCancellationRequested) { + if (!edits?.length || token.isCancellationRequested) { return; } @@ -64,11 +65,16 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider const workspaceEdit = new vscode.WorkspaceEdit(); workspaceEdit.set(document.uri, edits.map(x => new vscode.TextEdit(new vscode.Range(x.range.start.line, x.range.start.character, x.range.end.line, x.range.end.character,), x.newText))); pasteEdit.additionalEdit = workspaceEdit; + + if (!context.only || !UpdatePastedLinksEditProvider.kind.contains(context.only)) { + pasteEdit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Empty.append('text')]; + } + return [pasteEdit]; } private _isEnabled(document: vscode.TextDocument): boolean { - return vscode.workspace.getConfiguration('markdown', document.uri).get('experimental.updateLinksOnPaste', false); + return vscode.workspace.getConfiguration('markdown', document.uri).get('editor.updateLinksOnPaste.enabled', true); } } diff --git a/patched-vscode/extensions/markdown-language-features/src/markdownEngine.ts b/patched-vscode/extensions/markdown-language-features/src/markdownEngine.ts index 103cbc19..5f6e746a 100644 --- a/patched-vscode/extensions/markdown-language-features/src/markdownEngine.ts +++ b/patched-vscode/extensions/markdown-language-features/src/markdownEngine.ts @@ -313,7 +313,7 @@ export class MarkdownItEngine implements IMdParser { private _addNamedHeaders(md: MarkdownIt): void { const original = md.renderer.rules.heading_open; md.renderer.rules.heading_open = (tokens: Token[], idx: number, options, env, self) => { - const title = tokens[idx + 1].children!.reduce((acc, t) => acc + t.content, ''); + const title = this._tokenToPlainText(tokens[idx + 1]); let slug = this.slugifier.fromHeading(title); if (this._slugCount.has(slug.value)) { @@ -334,6 +334,21 @@ export class MarkdownItEngine implements IMdParser { }; } + private _tokenToPlainText(token: Token): string { + if (token.children) { + return token.children.map(x => this._tokenToPlainText(x)).join(''); + } + + switch (token.type) { + case 'text': + case 'emoji': + case 'code_inline': + return token.content; + default: + return ''; + } + } + private _addLinkRenderer(md: MarkdownIt): void { const original = md.renderer.rules.link_open; diff --git a/patched-vscode/extensions/markdown-language-features/yarn.lock b/patched-vscode/extensions/markdown-language-features/yarn.lock index 2b688e40..52579503 100644 --- a/patched-vscode/extensions/markdown-language-features/yarn.lock +++ b/patched-vscode/extensions/markdown-language-features/yarn.lock @@ -166,6 +166,11 @@ resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.10.tgz#9c513107c690c0dd16e3ec61e453743de15ebdb0" integrity sha512-E1OCmDcDWa0Ya7vtSjp/XfHFGqYJfh+YPC1RkATU71fTac+j1JjCcB3qwSzmlKAighx2WxhLlfhS0RwAN++PFQ== +"@vscode/l10n@^0.0.11": + version "0.0.11" + resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.11.tgz#325d7beb2cfb87162bc624d16c4d546de6a73b72" + integrity sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA== + "@vscode/markdown-it-katex@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.2.tgz#27ba579fa3896b2944b71209dd30d0f983983f11" @@ -183,6 +188,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -201,16 +211,72 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + dompurify@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.5.tgz#eb3d9cfa10037b6e73f32c586682c4b2ab01fbed" integrity sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A== +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + entities@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + highlight.js@^11.8.0: version "11.8.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.8.0.tgz#966518ea83257bae2e7c9a48596231856555bb65" @@ -275,6 +341,21 @@ morphdom@^2.6.1: resolved "https://registry.yarnpkg.com/morphdom/-/morphdom-2.6.1.tgz#e868e24f989fa3183004b159aed643e628b4306e" integrity sha512-Y8YRbAEP3eKykroIBWrjcfMw7mmwJfjhqdpSvoqinu8Y702nAwikpXcNFDiIkyvfCLxLM9Wu95RZqo4a9jFBaA== +node-html-parser@^6.1.5: + version "6.1.13" + resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.13.tgz#a1df799b83df5c6743fcd92740ba14682083b7e4" + integrity sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg== + dependencies: + css-select "^5.1.0" + he "1.2.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -297,6 +378,16 @@ vscode-jsonrpc@8.0.2: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz#f239ed2cd6004021b6550af9fd9d3e47eee3cac9" integrity sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ== +vscode-jsonrpc@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz#cb9989c65e219e18533cc38e767611272d274c94" + integrity sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw== + +vscode-jsonrpc@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" + integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== + vscode-languageclient@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.2.tgz#f1f23ce8c8484aa11e4b7dfb24437d3e59bb61c6" @@ -314,7 +405,23 @@ vscode-languageserver-protocol@3.17.2: vscode-jsonrpc "8.0.2" vscode-languageserver-types "3.17.2" -vscode-languageserver-textdocument@^1.0.11: +vscode-languageserver-protocol@3.17.3: + version "3.17.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz#6d0d54da093f0c0ee3060b81612cce0f11060d57" + integrity sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA== + dependencies: + vscode-jsonrpc "8.1.0" + vscode-languageserver-types "3.17.3" + +vscode-languageserver-protocol@^3.17.1: + version "3.17.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea" + integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== + dependencies: + vscode-jsonrpc "8.2.0" + vscode-languageserver-types "3.17.5" + +vscode-languageserver-textdocument@^1.0.11, vscode-languageserver-textdocument@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf" integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA== @@ -329,6 +436,35 @@ vscode-languageserver-types@3.17.2, vscode-languageserver-types@^3.17.1, vscode- resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2" integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA== +vscode-languageserver-types@3.17.3: + version "3.17.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" + integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== + +vscode-languageserver-types@3.17.5, vscode-languageserver-types@^3.17.3: + version "3.17.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" + integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== + +vscode-languageserver@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz#5024253718915d84576ce6662dd46a791498d827" + integrity sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw== + dependencies: + vscode-languageserver-protocol "3.17.3" + +vscode-markdown-languageserver@^0.5.0-alpha.8: + version "0.5.0-alpha.8" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageserver/-/vscode-markdown-languageserver-0.5.0-alpha.8.tgz#87ced4b241636b6aeda7aacc41badced0c2cc992" + integrity sha512-Bp6YXHy4EMQ8JpsmXpQHa78byLvv83wVnLmhVfTaJsZTBwF8IOJ56NBUasepg9L6QVffUA9T2H/PfBqEOGpeOQ== + dependencies: + "@vscode/l10n" "^0.0.11" + vscode-languageserver "^8.1.0" + vscode-languageserver-textdocument "^1.0.8" + vscode-languageserver-types "^3.17.3" + vscode-markdown-languageservice "^0.5.0-alpha.7" + vscode-uri "^3.0.7" + vscode-markdown-languageservice@^0.3.0-alpha.3: version "0.3.0-alpha.3" resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.3.0-alpha.3.tgz#219a4880cfc0ea037b5a1833bc0b0039bfd1e2db" @@ -340,11 +476,28 @@ vscode-markdown-languageservice@^0.3.0-alpha.3: vscode-languageserver-types "^3.17.1" vscode-uri "^3.0.3" +vscode-markdown-languageservice@^0.5.0-alpha.7: + version "0.5.0-alpha.7" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.5.0-alpha.7.tgz#0cc0939ea803d2afcb7a6e99b55feec664ec1b37" + integrity sha512-Iq9S5YGHm3D/UG9Usm8a/O5tYCo9FwaMF7nJsDQCxKgVZu5OzwOj3ixDkhoM+c8GKXiwt23DxhhWRuvI4odkTg== + dependencies: + "@vscode/l10n" "^0.0.10" + node-html-parser "^6.1.5" + picomatch "^2.3.1" + vscode-languageserver-protocol "^3.17.1" + vscode-languageserver-textdocument "^1.0.11" + vscode-uri "^3.0.7" + vscode-uri@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== +vscode-uri@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f" + integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" diff --git a/patched-vscode/extensions/markdown-math/notebook/katex.ts b/patched-vscode/extensions/markdown-math/notebook/katex.ts index 94aad4f3..ccc43046 100644 --- a/patched-vscode/extensions/markdown-math/notebook/katex.ts +++ b/patched-vscode/extensions/markdown-math/notebook/katex.ts @@ -51,7 +51,8 @@ export async function activate(ctx: RendererContext) { return md.use(katex, { globalGroup: true, enableBareBlocks: true, - macros + enableFencedBlocks: true, + macros, }); }); } diff --git a/patched-vscode/extensions/markdown-math/package.json b/patched-vscode/extensions/markdown-math/package.json index 44b442b3..9669efc2 100644 --- a/patched-vscode/extensions/markdown-math/package.json +++ b/patched-vscode/extensions/markdown-math/package.json @@ -56,6 +56,16 @@ "meta.embedded.math.markdown": "latex", "punctuation.definition.math.end.markdown": "latex" } + }, + { + "scopeName": "markdown.math.codeblock", + "path": "./syntaxes/md-math-fence.tmLanguage.json", + "injectTo": [ + "text.html.markdown" + ], + "embeddedLanguages": { + "meta.embedded.math.markdown": "latex" + } } ], "notebookRenderer": [ @@ -99,7 +109,7 @@ "build-notebook": "node ./esbuild" }, "dependencies": { - "@vscode/markdown-it-katex": "^1.0.3" + "@vscode/markdown-it-katex": "^1.1.0" }, "devDependencies": { "@types/markdown-it": "^0.0.0", diff --git a/patched-vscode/extensions/markdown-math/src/extension.ts b/patched-vscode/extensions/markdown-math/src/extension.ts index 1c27036b..6491b0c1 100644 --- a/patched-vscode/extensions/markdown-math/src/extension.ts +++ b/patched-vscode/extensions/markdown-math/src/extension.ts @@ -30,7 +30,11 @@ export function activate(context: vscode.ExtensionContext) { if (isEnabled()) { const katex = require('@vscode/markdown-it-katex').default; const settingsMacros = getMacros(); - const options = { globalGroup: true, macros: { ...settingsMacros } }; + const options = { + enableFencedBlocks: true, + globalGroup: true, + macros: { ...settingsMacros } + }; md.core.ruler.push('reset-katex-macros', () => { options.macros = { ...settingsMacros }; }); @@ -39,4 +43,4 @@ export function activate(context: vscode.ExtensionContext) { return md; } }; -} +} \ No newline at end of file diff --git a/patched-vscode/extensions/markdown-math/syntaxes/md-math-block.tmLanguage.json b/patched-vscode/extensions/markdown-math/syntaxes/md-math-block.tmLanguage.json index 43fd1bda..543568bf 100644 --- a/patched-vscode/extensions/markdown-math/syntaxes/md-math-block.tmLanguage.json +++ b/patched-vscode/extensions/markdown-math/syntaxes/md-math-block.tmLanguage.json @@ -82,4 +82,4 @@ } }, "scopeName": "markdown.math.block" -} +} \ No newline at end of file diff --git a/patched-vscode/extensions/markdown-math/syntaxes/md-math-fence.tmLanguage.json b/patched-vscode/extensions/markdown-math/syntaxes/md-math-fence.tmLanguage.json new file mode 100644 index 00000000..556c579d --- /dev/null +++ b/patched-vscode/extensions/markdown-math/syntaxes/md-math-fence.tmLanguage.json @@ -0,0 +1,28 @@ +{ + "fileTypes": [], + "injectionSelector": "L:markup.fenced_code.block.markdown", + "patterns": [ + { + "include": "#math-code-block" + } + ], + "repository": { + "math-code-block": { + "begin": "(?<=[`~])math(\\s+[^`~]*)?$", + "end": "(^|\\G)(?=\\s*[`~]{3,}\\s*$)", + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.math.markdown", + "patterns": [ + { + "include": "text.html.markdown.math#math" + } + ] + } + ] + } + }, + "scopeName": "markdown.math.codeblock" +} diff --git a/patched-vscode/extensions/markdown-math/yarn.lock b/patched-vscode/extensions/markdown-math/yarn.lock index f6b6729f..52f3ff95 100644 --- a/patched-vscode/extensions/markdown-math/yarn.lock +++ b/patched-vscode/extensions/markdown-math/yarn.lock @@ -12,10 +12,10 @@ resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.72.0.tgz#8943dc3cef0ced2dfb1e04c0a933bd289e7d5199" integrity sha512-5iTjb39DpLn03ULUwrDR3L2Dy59RV4blSUHy0oLdQuIY11PhgWO4mXIcoFS0VxY1GZQ4IcjSf3ooT2Jrrcahnw== -"@vscode/markdown-it-katex@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.0.3.tgz#5364e4dbcb0f7e7fd2fdab3847ba5d6b0c3ce9d9" - integrity sha512-a8ppdac0CG2lAQC6E6lT8dxmXkUk9gRtYNtILx31FyrPEwj875AAHc6tpRGeJBpWMpiMtcvz7ymWYBwYgxuFmw== +"@vscode/markdown-it-katex@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vscode/markdown-it-katex/-/markdown-it-katex-1.1.0.tgz#e991b58f6eb7cf56aef74b98e1a5edc1494649bb" + integrity sha512-9cF2eJpsJOEs2V1cCAoJW/boKz9GQQLvZhNvI030K90z6ZE9lRGc9hDVvKut8zdFO2ObjwylPXXXVYvTdP2O2Q== dependencies: katex "^0.16.4" diff --git a/patched-vscode/extensions/microsoft-authentication/extension-browser.webpack.config.js b/patched-vscode/extensions/microsoft-authentication/extension-browser.webpack.config.js index 2513c7d0..0d395fc0 100644 --- a/patched-vscode/extensions/microsoft-authentication/extension-browser.webpack.config.js +++ b/patched-vscode/extensions/microsoft-authentication/extension-browser.webpack.config.js @@ -22,10 +22,9 @@ module.exports = withBrowserDefaults({ }, resolve: { alias: { - './node/crypto': path.resolve(__dirname, 'src/browser/crypto'), './node/authServer': path.resolve(__dirname, 'src/browser/authServer'), './node/buffer': path.resolve(__dirname, 'src/browser/buffer'), - './node/fetch': path.resolve(__dirname, 'src/browser/fetch'), + './node/authProvider': path.resolve(__dirname, 'src/browser/authProvider'), } } }); diff --git a/patched-vscode/extensions/microsoft-authentication/package.json b/patched-vscode/extensions/microsoft-authentication/package.json index 3d73a762..ad6a4d5c 100644 --- a/patched-vscode/extensions/microsoft-authentication/package.json +++ b/patched-vscode/extensions/microsoft-authentication/package.json @@ -95,6 +95,15 @@ ] } } + }, + { + "title": "Microsoft", + "properties": { + "microsoft.useMsal": { + "type": "boolean", + "description": "%useMsal.description%" + } + } } ] }, @@ -116,9 +125,10 @@ "@types/uuid": "8.0.0" }, "dependencies": { - "node-fetch": "2.6.7", "@azure/ms-rest-azure-env": "^2.0.0", - "@vscode/extension-telemetry": "^0.9.0" + "@azure/msal-node": "^2.13.0", + "@vscode/extension-telemetry": "^0.9.0", + "vscode-tas-client": "^0.1.84" }, "repository": { "type": "git", diff --git a/patched-vscode/extensions/microsoft-authentication/package.nls.json b/patched-vscode/extensions/microsoft-authentication/package.nls.json index 14c625dc..80cbb32d 100644 --- a/patched-vscode/extensions/microsoft-authentication/package.nls.json +++ b/patched-vscode/extensions/microsoft-authentication/package.nls.json @@ -3,6 +3,7 @@ "description": "Microsoft authentication provider", "signIn": "Sign In", "signOut": "Sign Out", + "useMsal.description": "Use the Microsoft Authentication Library (MSAL) to sign in with a Microsoft account.", "microsoft-sovereign-cloud.environment.description": { "message": "The Sovereign Cloud to use for authentication. If you select `custom`, you must also set the `#microsoft-sovereign-cloud.customEnvironment#` setting.", "comment": [ diff --git a/patched-vscode/extensions/microsoft-authentication/src/AADHelper.ts b/patched-vscode/extensions/microsoft-authentication/src/AADHelper.ts index df36686d..713f5f12 100644 --- a/patched-vscode/extensions/microsoft-authentication/src/AADHelper.ts +++ b/patched-vscode/extensions/microsoft-authentication/src/AADHelper.ts @@ -11,7 +11,6 @@ import { generateCodeChallenge, generateCodeVerifier, randomUUID } from './crypt import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage'; import { LoopbackAuthServer } from './node/authServer'; import { base64Decode } from './node/buffer'; -import { fetching } from './node/fetch'; import { UriEventHandler } from './UriEventHandler'; import TelemetryReporter from '@vscode/extension-telemetry'; import { Environment } from '@azure/ms-rest-azure-env'; @@ -203,11 +202,13 @@ export class AzureActiveDirectoryService { return this._sessionChangeEmitter.event; } - public getSessions(scopes?: string[]): Promise { + public getSessions(scopes?: string[], account?: vscode.AuthenticationSessionAccountInformation): Promise { if (!scopes) { this._logger.info('Getting sessions for all scopes...'); - const sessions = this._tokens.map(token => this.convertToSessionSync(token)); - this._logger.info(`Got ${sessions.length} sessions for all scopes...`); + const sessions = this._tokens + .filter(token => !account?.label || token.account.label === account.label) + .map(token => this.convertToSessionSync(token)); + this._logger.info(`Got ${sessions.length} sessions for all scopes${account ? ` for account '${account.label}'` : ''}...`); return Promise.resolve(sessions); } @@ -238,23 +239,43 @@ export class AzureActiveDirectoryService { tenant: this.getTenantId(scopes), }; - this._logger.trace(`[${scopeData.scopeStr}] Queued getting sessions`); - return this._sequencer.queue(modifiedScopesStr, () => this.doGetSessions(scopeData)); + this._logger.trace(`[${scopeData.scopeStr}] Queued getting sessions` + account ? ` for ${account?.label}` : ''); + return this._sequencer.queue(modifiedScopesStr, () => this.doGetSessions(scopeData, account)); } - private async doGetSessions(scopeData: IScopeData): Promise { - this._logger.info(`[${scopeData.scopeStr}] Getting sessions`); + private async doGetSessions(scopeData: IScopeData, account?: vscode.AuthenticationSessionAccountInformation): Promise { + this._logger.info(`[${scopeData.scopeStr}] Getting sessions` + account ? ` for ${account?.label}` : ''); - const matchingTokens = this._tokens.filter(token => token.scope === scopeData.scopeStr); + const matchingTokens = this._tokens + .filter(token => token.scope === scopeData.scopeStr) + .filter(token => !account?.label || token.account.label === account.label); // If we still don't have a matching token try to get a new token from an existing token by using // the refreshToken. This is documented here: // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token // "Refresh tokens are valid for all permissions that your client has already received consent for." if (!matchingTokens.length) { - // Get a token with the correct client id. - const token = scopeData.clientId === DEFAULT_CLIENT_ID - ? this._tokens.find(t => t.refreshToken && !t.scope.includes('VSCODE_CLIENT_ID')) - : this._tokens.find(t => t.refreshToken && t.scope.includes(`VSCODE_CLIENT_ID:${scopeData.clientId}`)); + // Get a token with the correct client id and account. + let token: IToken | undefined; + for (const t of this._tokens) { + // No refresh token, so we can't make a new token from this session + if (!t.refreshToken) { + continue; + } + // Need to make sure the account matches if we were provided one + if (account?.label && t.account.label !== account.label) { + continue; + } + // If the client id is the default client id, then check for the absence of the VSCODE_CLIENT_ID scope + if (scopeData.clientId === DEFAULT_CLIENT_ID && !t.scope.includes('VSCODE_CLIENT_ID')) { + token = t; + break; + } + // If the client id is not the default client id, then check for the matching VSCODE_CLIENT_ID scope + if (scopeData.clientId !== DEFAULT_CLIENT_ID && t.scope.includes(`VSCODE_CLIENT_ID:${scopeData.clientId}`)) { + token = t; + break; + } + } if (token) { this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Found a matching token with a different scopes '${token.scope}'. Attempting to get a new session using the existing session.`); @@ -275,7 +296,7 @@ export class AzureActiveDirectoryService { .map(result => (result as PromiseFulfilledResult).value); } - public createSession(scopes: string[]): Promise { + public createSession(scopes: string[], account?: vscode.AuthenticationSessionAccountInformation): Promise { let modifiedScopes = [...scopes]; if (!modifiedScopes.includes('openid')) { modifiedScopes.push('openid'); @@ -301,11 +322,11 @@ export class AzureActiveDirectoryService { }; this._logger.trace(`[${scopeData.scopeStr}] Queued creating session`); - return this._sequencer.queue(scopeData.scopeStr, () => this.doCreateSession(scopeData)); + return this._sequencer.queue(scopeData.scopeStr, () => this.doCreateSession(scopeData, account)); } - private async doCreateSession(scopeData: IScopeData): Promise { - this._logger.info(`[${scopeData.scopeStr}] Creating session`); + private async doCreateSession(scopeData: IScopeData, account?: vscode.AuthenticationSessionAccountInformation): Promise { + this._logger.info(`[${scopeData.scopeStr}] Creating session` + account ? ` for ${account?.label}` : ''); const runsRemote = vscode.env.remoteName !== undefined; const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web; @@ -316,17 +337,17 @@ export class AzureActiveDirectoryService { return await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Signing in to your account...'), cancellable: true }, async (_progress, token) => { if (runsRemote || runsServerless) { - return await this.createSessionWithoutLocalServer(scopeData, token); + return await this.createSessionWithoutLocalServer(scopeData, account?.label, token); } try { - return await this.createSessionWithLocalServer(scopeData, token); + return await this.createSessionWithLocalServer(scopeData, account?.label, token); } catch (e) { this._logger.error(`[${scopeData.scopeStr}] Error creating session: ${e}`); // If the error was about starting the server, try directly hitting the login endpoint instead if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') { - return this.createSessionWithoutLocalServer(scopeData, token); + return this.createSessionWithoutLocalServer(scopeData, account?.label, token); } throw e; @@ -334,7 +355,7 @@ export class AzureActiveDirectoryService { }); } - private async createSessionWithLocalServer(scopeData: IScopeData, token: vscode.CancellationToken): Promise { + private async createSessionWithLocalServer(scopeData: IScopeData, loginHint: string | undefined, token: vscode.CancellationToken): Promise { this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with local server`); const codeVerifier = generateCodeVerifier(); const codeChallenge = await generateCodeChallenge(codeVerifier); @@ -344,11 +365,15 @@ export class AzureActiveDirectoryService { client_id: scopeData.clientId, redirect_uri: redirectUrl, scope: scopeData.scopesToSend, - prompt: 'select_account', code_challenge_method: 'S256', code_challenge: codeChallenge, - }).toString(); - const loginUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize?${qs}`, this._env.activeDirectoryEndpointUrl).toString(); + }); + if (loginHint) { + qs.set('login_hint', loginHint); + } else { + qs.set('prompt', 'select_account'); + } + const loginUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize?${qs.toString()}`, this._env.activeDirectoryEndpointUrl).toString(); const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl); await server.start(); @@ -370,7 +395,7 @@ export class AzureActiveDirectoryService { return session; } - private async createSessionWithoutLocalServer(scopeData: IScopeData, token: vscode.CancellationToken): Promise { + private async createSessionWithoutLocalServer(scopeData: IScopeData, loginHint: string | undefined, token: vscode.CancellationToken): Promise { this._logger.trace(`[${scopeData.scopeStr}] Starting login flow without local server`); let callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`)); const nonce = generateCodeVerifier(); @@ -383,17 +408,22 @@ export class AzureActiveDirectoryService { const codeVerifier = generateCodeVerifier(); const codeChallenge = await generateCodeChallenge(codeVerifier); const signInUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize`, this._env.activeDirectoryEndpointUrl); - signInUrl.search = new URLSearchParams({ + const qs = new URLSearchParams({ response_type: 'code', client_id: encodeURIComponent(scopeData.clientId), response_mode: 'query', redirect_uri: redirectUrl, state, scope: scopeData.scopesToSend, - prompt: 'select_account', code_challenge_method: 'S256', code_challenge: codeChallenge, - }).toString(); + }); + if (loginHint) { + qs.append('login_hint', loginHint); + } else { + qs.append('prompt', 'select_account'); + } + signInUrl.search = qs.toString(); const uri = vscode.Uri.parse(signInUrl.toString()); vscode.env.openExternal(uri); @@ -775,7 +805,7 @@ export class AzureActiveDirectoryService { let result; let errorMessage: string | undefined; try { - result = await fetching(endpoint, { + result = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', diff --git a/patched-vscode/extensions/microsoft-authentication/src/UriEventHandler.ts b/patched-vscode/extensions/microsoft-authentication/src/UriEventHandler.ts index 3dc753af..f525912f 100644 --- a/patched-vscode/extensions/microsoft-authentication/src/UriEventHandler.ts +++ b/patched-vscode/extensions/microsoft-authentication/src/UriEventHandler.ts @@ -6,7 +6,14 @@ import * as vscode from 'vscode'; export class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { - public handleUri(uri: vscode.Uri) { + private _disposable = vscode.window.registerUriHandler(this); + + handleUri(uri: vscode.Uri) { this.fire(uri); } + + override dispose(): void { + super.dispose(); + this._disposable.dispose(); + } } diff --git a/patched-vscode/extensions/microsoft-authentication/src/browser/authProvider.ts b/patched-vscode/extensions/microsoft-authentication/src/browser/authProvider.ts new file mode 100644 index 00000000..3b4da5b1 --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/browser/authProvider.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationSession, EventEmitter } from 'vscode'; + +export class MsalAuthProvider implements AuthenticationProvider { + private _onDidChangeSessions = new EventEmitter(); + onDidChangeSessions = this._onDidChangeSessions.event; + + initialize(): Thenable { + throw new Error('Method not implemented.'); + } + + getSessions(): Thenable { + throw new Error('Method not implemented.'); + } + createSession(): Thenable { + throw new Error('Method not implemented.'); + } + removeSession(): Thenable { + throw new Error('Method not implemented.'); + } + + dispose() { + this._onDidChangeSessions.dispose(); + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/async.ts b/patched-vscode/extensions/microsoft-authentication/src/common/async.ts index 641faaff..09486151 100644 --- a/patched-vscode/extensions/microsoft-authentication/src/common/async.ts +++ b/patched-vscode/extensions/microsoft-authentication/src/common/async.ts @@ -3,7 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationError, CancellationToken, Disposable } from 'vscode'; +import { CancellationError, CancellationToken, Disposable, Event, EventEmitter } from 'vscode'; + +/** + * Can be passed into the Delayed to defer using a microtask + */ +export const MicrotaskDelay = Symbol('MicrotaskDelay'); export class SequencerByKey { @@ -80,3 +85,473 @@ export function raceTimeoutError(promise: Promise, timeout: number): Promi export function raceCancellationAndTimeoutError(promise: Promise, token: CancellationToken, timeout: number): Promise { return raceCancellationError(raceTimeoutError(promise, timeout), token); } + +interface ILimitedTaskFactory { + factory: () => Promise; + c: (value: T | Promise) => void; + e: (error?: unknown) => void; +} + +export interface ILimiter { + + readonly size: number; + + queue(factory: () => Promise): Promise; + + clear(): void; +} + +/** + * A helper to queue N promises and run them all with a max degree of parallelism. The helper + * ensures that at any time no more than M promises are running at the same time. + */ +export class Limiter implements ILimiter { + + private _size = 0; + private _isDisposed = false; + private runningPromises: number; + private readonly maxDegreeOfParalellism: number; + private readonly outstandingPromises: ILimitedTaskFactory[]; + private readonly _onDrained: EventEmitter; + + constructor(maxDegreeOfParalellism: number) { + this.maxDegreeOfParalellism = maxDegreeOfParalellism; + this.outstandingPromises = []; + this.runningPromises = 0; + this._onDrained = new EventEmitter(); + } + + /** + * + * @returns A promise that resolved when all work is done (onDrained) or when + * there is nothing to do + */ + whenIdle(): Promise { + return this.size > 0 + ? toPromise(this.onDrained) + : Promise.resolve(); + } + + get onDrained(): Event { + return this._onDrained.event; + } + + get size(): number { + return this._size; + } + + queue(factory: () => Promise): Promise { + if (this._isDisposed) { + throw new Error('Object has been disposed'); + } + this._size++; + + return new Promise((c, e) => { + this.outstandingPromises.push({ factory, c, e }); + this.consume(); + }); + } + + private consume(): void { + while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { + const iLimitedTask = this.outstandingPromises.shift()!; + this.runningPromises++; + + const promise = iLimitedTask.factory(); + promise.then(iLimitedTask.c, iLimitedTask.e); + promise.then(() => this.consumed(), () => this.consumed()); + } + } + + private consumed(): void { + if (this._isDisposed) { + return; + } + this.runningPromises--; + if (--this._size === 0) { + this._onDrained.fire(); + } + + if (this.outstandingPromises.length > 0) { + this.consume(); + } + } + + clear(): void { + if (this._isDisposed) { + throw new Error('Object has been disposed'); + } + this.outstandingPromises.length = 0; + this._size = this.runningPromises; + } + + dispose(): void { + this._isDisposed = true; + this.outstandingPromises.length = 0; // stop further processing + this._size = 0; + this._onDrained.dispose(); + } +} + + +interface IScheduledLater extends Disposable { + isTriggered(): boolean; +} + +const timeoutDeferred = (timeout: number, fn: () => void): IScheduledLater => { + let scheduled = true; + const handle = setTimeout(() => { + scheduled = false; + fn(); + }, timeout); + return { + isTriggered: () => scheduled, + dispose: () => { + clearTimeout(handle); + scheduled = false; + }, + }; +}; + +const microtaskDeferred = (fn: () => void): IScheduledLater => { + let scheduled = true; + queueMicrotask(() => { + if (scheduled) { + scheduled = false; + fn(); + } + }); + + return { + isTriggered: () => scheduled, + dispose: () => { scheduled = false; }, + }; +}; + +/** + * A helper to delay (debounce) execution of a task that is being requested often. + * + * Following the throttler, now imagine the mail man wants to optimize the number of + * trips proactively. The trip itself can be long, so he decides not to make the trip + * as soon as a letter is submitted. Instead he waits a while, in case more + * letters are submitted. After said waiting period, if no letters were submitted, he + * decides to make the trip. Imagine that N more letters were submitted after the first + * one, all within a short period of time between each other. Even though N+1 + * submissions occurred, only 1 delivery was made. + * + * The delayer offers this behavior via the trigger() method, into which both the task + * to be executed and the waiting period (delay) must be passed in as arguments. Following + * the example: + * + * const delayer = new Delayer(WAITING_PERIOD); + * const letters = []; + * + * function letterReceived(l) { + * letters.push(l); + * delayer.trigger(() => { return makeTheTrip(); }); + * } + */ +export class Delayer implements Disposable { + + private deferred: IScheduledLater | null; + private completionPromise: Promise | null; + private doResolve: ((value?: any | Promise) => void) | null; + private doReject: ((err: any) => void) | null; + private task: (() => T | Promise) | null; + + constructor(public defaultDelay: number | typeof MicrotaskDelay) { + this.deferred = null; + this.completionPromise = null; + this.doResolve = null; + this.doReject = null; + this.task = null; + } + + trigger(task: () => T | Promise, delay = this.defaultDelay): Promise { + this.task = task; + this.cancelTimeout(); + + if (!this.completionPromise) { + this.completionPromise = new Promise((resolve, reject) => { + this.doResolve = resolve; + this.doReject = reject; + }).then(() => { + this.completionPromise = null; + this.doResolve = null; + if (this.task) { + const task = this.task; + this.task = null; + return task(); + } + return undefined; + }); + } + + const fn = () => { + this.deferred = null; + this.doResolve?.(null); + }; + + this.deferred = delay === MicrotaskDelay ? microtaskDeferred(fn) : timeoutDeferred(delay, fn); + + return this.completionPromise; + } + + isTriggered(): boolean { + return !!this.deferred?.isTriggered(); + } + + cancel(): void { + this.cancelTimeout(); + + if (this.completionPromise) { + this.doReject?.(new CancellationError()); + this.completionPromise = null; + } + } + + private cancelTimeout(): void { + this.deferred?.dispose(); + this.deferred = null; + } + + dispose(): void { + this.cancel(); + } +} + +/** + * A helper to prevent accumulation of sequential async tasks. + * + * Imagine a mail man with the sole task of delivering letters. As soon as + * a letter submitted for delivery, he drives to the destination, delivers it + * and returns to his base. Imagine that during the trip, N more letters were submitted. + * When the mail man returns, he picks those N letters and delivers them all in a + * single trip. Even though N+1 submissions occurred, only 2 deliveries were made. + * + * The throttler implements this via the queue() method, by providing it a task + * factory. Following the example: + * + * const throttler = new Throttler(); + * const letters = []; + * + * function deliver() { + * const lettersToDeliver = letters; + * letters = []; + * return makeTheTrip(lettersToDeliver); + * } + * + * function onLetterReceived(l) { + * letters.push(l); + * throttler.queue(deliver); + * } + */ +export class Throttler implements Disposable { + + private activePromise: Promise | null; + private queuedPromise: Promise | null; + private queuedPromiseFactory: (() => Promise) | null; + + private isDisposed = false; + + constructor() { + this.activePromise = null; + this.queuedPromise = null; + this.queuedPromiseFactory = null; + } + + queue(promiseFactory: () => Promise): Promise { + if (this.isDisposed) { + return Promise.reject(new Error('Throttler is disposed')); + } + + if (this.activePromise) { + this.queuedPromiseFactory = promiseFactory; + + if (!this.queuedPromise) { + const onComplete = () => { + this.queuedPromise = null; + + if (this.isDisposed) { + return; + } + + const result = this.queue(this.queuedPromiseFactory!); + this.queuedPromiseFactory = null; + + return result; + }; + + this.queuedPromise = new Promise(resolve => { + this.activePromise!.then(onComplete, onComplete).then(resolve); + }); + } + + return new Promise((resolve, reject) => { + this.queuedPromise!.then(resolve, reject); + }); + } + + this.activePromise = promiseFactory(); + + return new Promise((resolve, reject) => { + this.activePromise!.then((result: T) => { + this.activePromise = null; + resolve(result); + }, (err: unknown) => { + this.activePromise = null; + reject(err); + }); + }); + } + + dispose(): void { + this.isDisposed = true; + } +} + +/** + * A helper to delay execution of a task that is being requested often, while + * preventing accumulation of consecutive executions, while the task runs. + * + * The mail man is clever and waits for a certain amount of time, before going + * out to deliver letters. While the mail man is going out, more letters arrive + * and can only be delivered once he is back. Once he is back the mail man will + * do one more trip to deliver the letters that have accumulated while he was out. + */ +export class ThrottledDelayer { + + private delayer: Delayer>; + private throttler: Throttler; + + constructor(defaultDelay: number) { + this.delayer = new Delayer(defaultDelay); + this.throttler = new Throttler(); + } + + trigger(promiseFactory: () => Promise, delay?: number): Promise { + return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise; + } + + isTriggered(): boolean { + return this.delayer.isTriggered(); + } + + cancel(): void { + this.delayer.cancel(); + } + + dispose(): void { + this.delayer.dispose(); + this.throttler.dispose(); + } +} + +/** + * A queue is handles one promise at a time and guarantees that at any time only one promise is executing. + */ +export class Queue extends Limiter { + + constructor() { + super(1); + } +} + +/** + * Given an event, returns another event which only fires once. + * + * @param event The event source for the new event. + */ +export function once(event: Event): Event { + return (listener, thisArgs = null, disposables?) => { + // we need this, in case the event fires during the listener call + let didFire = false; + let result: Disposable | undefined = undefined; + result = event(e => { + if (didFire) { + return; + } else if (result) { + result.dispose(); + } else { + didFire = true; + } + + return listener.call(thisArgs, e); + }, null, disposables); + + if (didFire) { + result.dispose(); + } + + return result; + }; +} + +/** + * Creates a promise out of an event, using the {@link Event.once} helper. + */ +export function toPromise(event: Event): Promise { + return new Promise(resolve => once(event)(resolve)); +} + +export type ValueCallback = (value: T | Promise) => void; + +const enum DeferredOutcome { + Resolved, + Rejected +} + +/** + * Creates a promise whose resolution or rejection can be controlled imperatively. + */ +export class DeferredPromise { + + private completeCallback!: ValueCallback; + private errorCallback!: (err: unknown) => void; + private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T }; + + public get isRejected() { + return this.outcome?.outcome === DeferredOutcome.Rejected; + } + + public get isResolved() { + return this.outcome?.outcome === DeferredOutcome.Resolved; + } + + public get isSettled() { + return !!this.outcome; + } + + public get value() { + return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined; + } + + public readonly p: Promise; + + constructor() { + this.p = new Promise((c, e) => { + this.completeCallback = c; + this.errorCallback = e; + }); + } + + public complete(value: T) { + return new Promise(resolve => { + this.completeCallback(value); + this.outcome = { outcome: DeferredOutcome.Resolved, value }; + resolve(); + }); + } + + public error(err: unknown) { + return new Promise(resolve => { + this.errorCallback(err); + this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; + resolve(); + }); + } + + public cancel() { + return this.error(new CancellationError()); + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/cachePlugin.ts b/patched-vscode/extensions/microsoft-authentication/src/common/cachePlugin.ts new file mode 100644 index 00000000..91b4f0ee --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/cachePlugin.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ICachePlugin, TokenCacheContext } from '@azure/msal-node'; +import { Disposable, EventEmitter, SecretStorage } from 'vscode'; + +export class SecretStorageCachePlugin implements ICachePlugin { + private readonly _onDidChange: EventEmitter = new EventEmitter(); + readonly onDidChange = this._onDidChange.event; + + private _disposable: Disposable; + + private _value: string | undefined; + + constructor( + private readonly _secretStorage: SecretStorage, + private readonly _key: string + ) { + this._disposable = Disposable.from( + this._onDidChange, + this._registerChangeHandler() + ); + } + + private _registerChangeHandler() { + return this._secretStorage.onDidChange(e => { + if (e.key === this._key) { + this._onDidChange.fire(); + } + }); + } + + async beforeCacheAccess(tokenCacheContext: TokenCacheContext): Promise { + const data = await this._secretStorage.get(this._key); + this._value = data; + if (data) { + tokenCacheContext.tokenCache.deserialize(data); + } + } + + async afterCacheAccess(tokenCacheContext: TokenCacheContext): Promise { + if (tokenCacheContext.cacheHasChanged) { + const value = tokenCacheContext.tokenCache.serialize(); + if (value !== this._value) { + await this._secretStorage.store(this._key, value); + } + } + } + + dispose() { + this._disposable.dispose(); + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/experimentation.ts b/patched-vscode/extensions/microsoft-authentication/src/common/experimentation.ts new file mode 100644 index 00000000..dd383c4f --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/experimentation.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import { getExperimentationService, IExperimentationService, IExperimentationTelemetry, TargetPopulation } from 'vscode-tas-client'; + +export async function createExperimentationService( + context: vscode.ExtensionContext, + experimentationTelemetry: IExperimentationTelemetry, + isPreRelease: boolean, +): Promise { + const id = context.extension.id; + const version = context.extension.packageJSON['version']; + + const service = getExperimentationService( + id, + version, + isPreRelease ? TargetPopulation.Insiders : TargetPopulation.Public, + experimentationTelemetry, + context.globalState, + ) as unknown as IExperimentationService; + await service.initializePromise; + await service.initialFetch; + return service; +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/loggerOptions.ts b/patched-vscode/extensions/microsoft-authentication/src/common/loggerOptions.ts new file mode 100644 index 00000000..86443c02 --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/loggerOptions.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LogLevel as MsalLogLevel } from '@azure/msal-node'; +import { env, LogLevel, LogOutputChannel } from 'vscode'; + +export class MsalLoggerOptions { + piiLoggingEnabled = false; + + constructor(private readonly _output: LogOutputChannel) { } + + get logLevel(): MsalLogLevel { + return this._toMsalLogLevel(env.logLevel); + } + + loggerCallback(level: MsalLogLevel, message: string, containsPii: boolean): void { + if (containsPii) { + return; + } + + switch (level) { + case MsalLogLevel.Error: + this._output.error(message); + return; + case MsalLogLevel.Warning: + this._output.warn(message); + return; + case MsalLogLevel.Info: + this._output.info(message); + return; + case MsalLogLevel.Verbose: + this._output.debug(message); + return; + case MsalLogLevel.Trace: + this._output.trace(message); + return; + default: + this._output.info(message); + return; + } + } + + private _toMsalLogLevel(logLevel: LogLevel): MsalLogLevel { + switch (logLevel) { + case LogLevel.Trace: + return MsalLogLevel.Trace; + case LogLevel.Debug: + return MsalLogLevel.Verbose; + case LogLevel.Info: + return MsalLogLevel.Info; + case LogLevel.Warning: + return MsalLogLevel.Warning; + case LogLevel.Error: + return MsalLogLevel.Error; + default: + return MsalLogLevel.Info; + } + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts b/patched-vscode/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts new file mode 100644 index 00000000..4a455ea5 --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { ILoopbackClient, ServerAuthorizationCodeResponse } from '@azure/msal-node'; +import type { UriEventHandler } from '../UriEventHandler'; +import { env, Uri } from 'vscode'; +import { toPromise } from './async'; + +export interface ILoopbackClientAndOpener extends ILoopbackClient { + openBrowser(url: string): Promise; +} + +export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { + constructor( + private readonly _uriHandler: UriEventHandler, + private readonly _redirectUri: string + ) { } + + async listenForAuthCode(successTemplate?: string, errorTemplate?: string): Promise { + console.log(successTemplate, errorTemplate); + const url = await toPromise(this._uriHandler.event); + const result = new URL(url.toString(true)); + + return { + code: result.searchParams.get('code') ?? undefined, + state: result.searchParams.get('state') ?? undefined, + error: result.searchParams.get('error') ?? undefined, + error_description: result.searchParams.get('error_description') ?? undefined, + error_uri: result.searchParams.get('error_uri') ?? undefined, + }; + } + + getRedirectUri(): string { + // We always return the constant redirect URL because + // it will handle redirecting back to the extension + return this._redirectUri; + } + + closeServer(): void { + // No-op + } + + async openBrowser(url: string): Promise { + const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); + + const uri = Uri.parse(url + `&state=${encodeURI(callbackUri.toString(true))}`); + await env.openExternal(uri); + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/publicClientCache.ts b/patched-vscode/extensions/microsoft-authentication/src/common/publicClientCache.ts new file mode 100644 index 00000000..cb9339f9 --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/publicClientCache.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import type { AccountInfo, AuthenticationResult, InteractiveRequest, SilentFlowRequest } from '@azure/msal-node'; +import type { Disposable, Event } from 'vscode'; + +export interface ICachedPublicClientApplication extends Disposable { + initialize(): Promise; + acquireTokenSilent(request: SilentFlowRequest): Promise; + acquireTokenInteractive(request: InteractiveRequest): Promise; + removeAccount(account: AccountInfo): Promise; + accounts: AccountInfo[]; + clientId: string; + authority: string; +} + +export interface ICachedPublicClientApplicationManager { + getOrCreate(clientId: string, authority: string): Promise; + getAll(): ICachedPublicClientApplication[]; +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/scopeData.ts b/patched-vscode/extensions/microsoft-authentication/src/common/scopeData.ts new file mode 100644 index 00000000..148658de --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/scopeData.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const DEFAULT_CLIENT_ID = 'aebc6443-996d-45c2-90f0-388ff96faa56'; +const DEFAULT_TENANT = 'organizations'; + +export class ScopeData { + + /** + * The full list of scopes including: + * * the original scopes passed to the constructor + * * internal VS Code scopes (e.g. `VSCODE_CLIENT_ID:...`) + * * the default scopes (`openid`, `email`, `profile`, `offline_access`) + */ + readonly allScopes: string[]; + + /** + * The full list of scopes as a space-separated string. For logging. + */ + readonly scopeStr: string; + + /** + * The list of scopes to send to the token endpoint. This is the same as `scopes` but without the internal VS Code scopes. + */ + readonly scopesToSend: string[]; + + /** + * The client ID to use for the token request. This is the value of the `VSCODE_CLIENT_ID:...` scope if present, otherwise the default client ID. + */ + readonly clientId: string; + + /** + * The tenant ID to use for the token request. This is the value of the `VSCODE_TENANT:...` scope if present, otherwise the default tenant ID. + */ + readonly tenant: string; + + constructor(readonly originalScopes: readonly string[] = []) { + const modifiedScopes = [...originalScopes]; + if (!modifiedScopes.includes('openid')) { + modifiedScopes.push('openid'); + } + if (!modifiedScopes.includes('email')) { + modifiedScopes.push('email'); + } + if (!modifiedScopes.includes('profile')) { + modifiedScopes.push('profile'); + } + if (!modifiedScopes.includes('offline_access')) { + modifiedScopes.push('offline_access'); + } + modifiedScopes.sort(); + this.allScopes = modifiedScopes; + this.scopeStr = modifiedScopes.join(' '); + this.scopesToSend = this.originalScopes.filter(s => !s.startsWith('VSCODE_')); + this.clientId = this.getClientId(this.allScopes); + this.tenant = this.getTenantId(this.allScopes); + } + + private getClientId(scopes: string[]) { + return scopes.reduce((prev, current) => { + if (current.startsWith('VSCODE_CLIENT_ID:')) { + return current.split('VSCODE_CLIENT_ID:')[1]; + } + return prev; + }, undefined) ?? DEFAULT_CLIENT_ID; + } + + private getTenantId(scopes: string[]) { + return scopes.reduce((prev, current) => { + if (current.startsWith('VSCODE_TENANT:')) { + return current.split('VSCODE_TENANT:')[1]; + } + return prev; + }, undefined) ?? DEFAULT_TENANT; + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/telemetryReporter.ts b/patched-vscode/extensions/microsoft-authentication/src/common/telemetryReporter.ts new file mode 100644 index 00000000..25ac2623 --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/telemetryReporter.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import TelemetryReporter, { TelemetryEventProperties } from '@vscode/extension-telemetry'; +import { IExperimentationTelemetry } from 'vscode-tas-client'; + +export const enum MicrosoftAccountType { + AAD = 'aad', + MSA = 'msa', + Unknown = 'unknown' +} + +export class MicrosoftAuthenticationTelemetryReporter implements IExperimentationTelemetry { + private sharedProperties: Record = {}; + protected _telemetryReporter: TelemetryReporter; + constructor(aiKey: string) { + this._telemetryReporter = new TelemetryReporter(aiKey); + } + + get telemetryReporter(): TelemetryReporter { + return this._telemetryReporter; + } + + setSharedProperty(name: string, value: string): void { + this.sharedProperties[name] = value; + } + + postEvent(eventName: string, props: Map): void { + const eventProperties: TelemetryEventProperties = { ...this.sharedProperties, ...Object.fromEntries(props) }; + this._telemetryReporter.sendTelemetryEvent( + eventName, + eventProperties + ); + } + + sendLoginEvent(scopes: readonly string[]): void { + /* __GDPR__ + "login" : { + "owner": "TylerLeonhardt", + "comment": "Used to determine the usage of the Microsoft Auth Provider.", + "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } + } + */ + this._telemetryReporter.sendTelemetryEvent('login', { + // Get rid of guids from telemetry. + scopes: JSON.stringify(this._scrubGuids(scopes)), + }); + } + sendLoginFailedEvent(): void { + /* __GDPR__ + "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } + */ + this._telemetryReporter.sendTelemetryEvent('loginFailed'); + } + sendLogoutEvent(): void { + /* __GDPR__ + "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } + */ + this._telemetryReporter.sendTelemetryEvent('logout'); + } + sendLogoutFailedEvent(): void { + /* __GDPR__ + "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } + */ + this._telemetryReporter.sendTelemetryEvent('logoutFailed'); + } + /** + * Sends an event for an account type available at startup. + * @param scopes The scopes for the session + * @param accountType The account type for the session + * @todo Remove the scopes since we really don't care about them. + */ + sendAccountEvent(scopes: string[], accountType: MicrosoftAccountType): void { + /* __GDPR__ + "login" : { + "owner": "TylerLeonhardt", + "comment": "Used to determine the usage of the Microsoft Auth Provider.", + "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }, + "accountType": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what account types are being used." } + } + */ + this._telemetryReporter.sendTelemetryEvent('account', { + // Get rid of guids from telemetry. + scopes: JSON.stringify(this._scrubGuids(scopes)), + accountType + }); + } + + protected _scrubGuids(scopes: readonly string[]): string[] { + return scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}')); + } +} + +export class MicrosoftSovereignCloudAuthenticationTelemetryReporter extends MicrosoftAuthenticationTelemetryReporter { + override sendLoginEvent(scopes: string[]): void { + /* __GDPR__ + "loginMicrosoftSovereignCloud" : { + "owner": "TylerLeonhardt", + "comment": "Used to determine the usage of the Microsoft Auth Provider.", + "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } + } + */ + this._telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', { + // Get rid of guids from telemetry. + scopes: JSON.stringify(this._scrubGuids(scopes)), + }); + } + override sendLoginFailedEvent(): void { + /* __GDPR__ + "loginMicrosoftSovereignCloudFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } + */ + this._telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed'); + } + override sendLogoutEvent(): void { + /* __GDPR__ + "logoutMicrosoftSovereignCloud" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } + */ + this._telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud'); + } + override sendLogoutFailedEvent(): void { + /* __GDPR__ + "logoutMicrosoftSovereignCloudFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } + */ + this._telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed'); + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts b/patched-vscode/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts new file mode 100644 index 00000000..5c55567d --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { env, Uri } from 'vscode'; +import * as sinon from 'sinon'; +import { UriHandlerLoopbackClient } from '../loopbackClientAndOpener'; +import { UriEventHandler } from '../../UriEventHandler'; + +suite('UriHandlerLoopbackClient', () => { + const redirectUri = 'http://localhost'; + let uriHandler: UriEventHandler; + let client: UriHandlerLoopbackClient; + let envStub: sinon.SinonStubbedInstance; + let callbackUri: Uri; + + setup(async () => { + callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); + envStub = sinon.stub(env); + envStub.openExternal.resolves(true); + envStub.asExternalUri.callThrough(); + uriHandler = new UriEventHandler(); + client = new UriHandlerLoopbackClient(uriHandler, redirectUri); + }); + + teardown(() => { + sinon.restore(); + uriHandler.dispose(); + }); + + suite('openBrowser', () => { + test('should open browser with correct URL', async () => { + const testUrl = 'http://example.com?foo=5'; + + await client.openBrowser(testUrl); + + assert.ok(envStub.asExternalUri.calledOnce); + assert.ok(envStub.openExternal.calledOnce); + + const expectedUri = Uri.parse(testUrl + `&state=${encodeURI(callbackUri.toString(true))}`); + const value = envStub.openExternal.getCalls()[0].args[0]; + assert.strictEqual(value.toString(true), expectedUri.toString(true)); + }); + }); + + suite('getRedirectUri', () => { + test('should return the redirect URI', () => { + const result = client.getRedirectUri(); + assert.strictEqual(result, redirectUri); + }); + }); + + suite('listenForAuthCode', () => { + test('should return auth code from URL', async () => { + const code = '1234'; + const state = '5678'; + const testUrl = Uri.parse(`http://example.com?code=${code}&state=${state}`); + const promise = client.listenForAuthCode(); + uriHandler.handleUri(testUrl); + const result = await promise; + + assert.strictEqual(result.code, code); + assert.strictEqual(result.state, state); + }); + + test('should return auth error from URL', async () => { + const error = 'access_denied'; + const errorDescription = 'reason'; + const errorUri = 'uri'; + const testUrl = Uri.parse(`http://example.com?error=${error}&error_description=${errorDescription}&error_uri=${errorUri}`); + + const promise = client.listenForAuthCode(); + uriHandler.handleUri(testUrl); + const result = await promise; + + assert.strictEqual(result.error, 'access_denied'); + assert.strictEqual(result.error_description, 'reason'); + assert.strictEqual(result.error_uri, 'uri'); + }); + }); +}); diff --git a/patched-vscode/extensions/microsoft-authentication/src/common/test/scopeData.test.ts b/patched-vscode/extensions/microsoft-authentication/src/common/test/scopeData.test.ts new file mode 100644 index 00000000..c0b578d4 --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/common/test/scopeData.test.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ScopeData } from '../scopeData'; + +suite('ScopeData', () => { + test('should include default scopes if not present', () => { + const scopeData = new ScopeData(['custom_scope']); + assert.deepStrictEqual(scopeData.allScopes, ['custom_scope', 'email', 'offline_access', 'openid', 'profile']); + }); + + test('should not duplicate default scopes if already present', () => { + const scopeData = new ScopeData(['openid', 'email', 'profile', 'offline_access']); + assert.deepStrictEqual(scopeData.allScopes, ['email', 'offline_access', 'openid', 'profile']); + }); + + test('should sort the scopes alphabetically', () => { + const scopeData = new ScopeData(['profile', 'email', 'openid', 'offline_access']); + assert.deepStrictEqual(scopeData.allScopes, ['email', 'offline_access', 'openid', 'profile']); + }); + + test('should create a space-separated string of all scopes', () => { + const scopeData = new ScopeData(['custom_scope']); + assert.strictEqual(scopeData.scopeStr, 'custom_scope email offline_access openid profile'); + }); + + test('should filter out internal VS Code scopes for scopesToSend', () => { + const scopeData = new ScopeData(['custom_scope', 'VSCODE_CLIENT_ID:some_id']); + assert.deepStrictEqual(scopeData.scopesToSend, ['custom_scope']); + }); + + test('should use the default client ID if no VSCODE_CLIENT_ID scope is present', () => { + const scopeData = new ScopeData(['custom_scope']); + assert.strictEqual(scopeData.clientId, 'aebc6443-996d-45c2-90f0-388ff96faa56'); + }); + + test('should use the VSCODE_CLIENT_ID scope if present', () => { + const scopeData = new ScopeData(['custom_scope', 'VSCODE_CLIENT_ID:some_id']); + assert.strictEqual(scopeData.clientId, 'some_id'); + }); + + test('should use the default tenant ID if no VSCODE_TENANT scope is present', () => { + const scopeData = new ScopeData(['custom_scope']); + assert.strictEqual(scopeData.tenant, 'organizations'); + }); + + test('should use the VSCODE_TENANT scope if present', () => { + const scopeData = new ScopeData(['custom_scope', 'VSCODE_TENANT:some_tenant']); + assert.strictEqual(scopeData.tenant, 'some_tenant'); + }); +}); diff --git a/patched-vscode/extensions/microsoft-authentication/src/cryptoUtils.ts b/patched-vscode/extensions/microsoft-authentication/src/cryptoUtils.ts index 582dae74..e608a81f 100644 --- a/patched-vscode/extensions/microsoft-authentication/src/cryptoUtils.ts +++ b/patched-vscode/extensions/microsoft-authentication/src/cryptoUtils.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { base64Encode } from './node/buffer'; -import { crypto } from './node/crypto'; export function randomUUID() { return crypto.randomUUID(); diff --git a/patched-vscode/extensions/microsoft-authentication/src/extension.ts b/patched-vscode/extensions/microsoft-authentication/src/extension.ts index 02cfb464..3f9b5d3a 100644 --- a/patched-vscode/extensions/microsoft-authentication/src/extension.ts +++ b/patched-vscode/extensions/microsoft-authentication/src/extension.ts @@ -3,179 +3,83 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; -import { Environment, EnvironmentParameters } from '@azure/ms-rest-azure-env'; -import { AzureActiveDirectoryService, IStoredSession } from './AADHelper'; -import { BetterTokenStorage } from './betterSecretStorage'; -import { UriEventHandler } from './UriEventHandler'; -import TelemetryReporter from '@vscode/extension-telemetry'; - -async function initMicrosoftSovereignCloudAuthProvider(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter, uriHandler: UriEventHandler, tokenStorage: BetterTokenStorage): Promise { - const environment = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('environment'); - let authProviderName: string | undefined; - if (!environment) { - return undefined; +import { commands, env, ExtensionContext, l10n, window, workspace } from 'vscode'; +import * as extensionV1 from './extensionV1'; +import * as extensionV2 from './extensionV2'; +import { createExperimentationService } from './common/experimentation'; +import { MicrosoftAuthenticationTelemetryReporter } from './common/telemetryReporter'; +import { IExperimentationService } from 'vscode-tas-client'; +import Logger from './logger'; + +function shouldUseMsal(expService: IExperimentationService): boolean { + // First check if there is a setting value to allow user to override the default + const inspect = workspace.getConfiguration('microsoft').inspect('useMsal'); + if (inspect?.workspaceFolderValue !== undefined) { + Logger.debug(`Acquired MSAL enablement value from 'workspaceFolderValue'. Value: ${inspect.workspaceFolderValue}`); + return inspect.workspaceFolderValue; } - - if (environment === 'custom') { - const customEnv = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment'); - if (!customEnv) { - const res = await vscode.window.showErrorMessage(vscode.l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), vscode.l10n.t('Open settings')); - if (res) { - await vscode.commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment'); - } - return undefined; - } - try { - Environment.add(customEnv); - } catch (e) { - const res = await vscode.window.showErrorMessage(vscode.l10n.t('Error validating custom environment setting: {0}', e.message), vscode.l10n.t('Open settings')); - if (res) { - await vscode.commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment'); - } - return undefined; - } - authProviderName = customEnv.name; - } else { - authProviderName = environment; + if (inspect?.workspaceValue !== undefined) { + Logger.debug(`Acquired MSAL enablement value from 'workspaceValue'. Value: ${inspect.workspaceValue}`); + return inspect.workspaceValue; } - - const env = Environment.get(authProviderName); - if (!env) { - const res = await vscode.window.showErrorMessage(vscode.l10n.t('The environment `{0}` is not a valid environment.', authProviderName), vscode.l10n.t('Open settings')); - return undefined; + if (inspect?.globalValue !== undefined) { + Logger.debug(`Acquired MSAL enablement value from 'globalValue'. Value: ${inspect.globalValue}`); + return inspect.globalValue; } - const aadService = new AzureActiveDirectoryService( - vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), - context, - uriHandler, - tokenStorage, - telemetryReporter, - env); - await aadService.initialize(); - - const disposable = vscode.authentication.registerAuthenticationProvider('microsoft-sovereign-cloud', authProviderName, { - onDidChangeSessions: aadService.onDidChangeSessions, - getSessions: (scopes: string[]) => aadService.getSessions(scopes), - createSession: async (scopes: string[]) => { - try { - /* __GDPR__ - "login" : { - "owner": "TylerLeonhardt", - "comment": "Used to determine the usage of the Microsoft Sovereign Cloud Auth Provider.", - "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } - } - */ - telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', { - // Get rid of guids from telemetry. - scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), - }); - - return await aadService.createSession(scopes); - } catch (e) { - /* __GDPR__ - "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } - */ - telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed'); - - throw e; - } - }, - removeSession: async (id: string) => { - try { - /* __GDPR__ - "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } - */ - telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud'); - - await aadService.removeSessionById(id); - } catch (e) { - /* __GDPR__ - "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } - */ - telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed'); - } - } - }, { supportsMultipleAccounts: true }); + // Then check if the experiment value + const expValue = expService.getTreatmentVariable('vscode', 'microsoft.useMsal'); + if (expValue !== undefined) { + Logger.debug(`Acquired MSAL enablement value from 'exp'. Value: ${expValue}`); + return expValue; + } - context.subscriptions.push(disposable); - return disposable; + Logger.debug('Acquired MSAL enablement value from default. Value: false'); + // If no setting or experiment value is found, default to false + return false; } +let useMsal: boolean | undefined; -export async function activate(context: vscode.ExtensionContext) { - const aiKey: string = context.extension.packageJSON.aiKey; - const telemetryReporter = new TelemetryReporter(aiKey); - - const uriHandler = new UriEventHandler(); - context.subscriptions.push(uriHandler); - context.subscriptions.push(vscode.window.registerUriHandler(uriHandler)); - const betterSecretStorage = new BetterTokenStorage('microsoft.login.keylist', context); - - const loginService = new AzureActiveDirectoryService( - vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Authentication'), { log: true }), +export async function activate(context: ExtensionContext) { + const mainTelemetryReporter = new MicrosoftAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey); + const expService = await createExperimentationService( context, - uriHandler, - betterSecretStorage, - telemetryReporter, - Environment.AzureCloud); - await loginService.initialize(); - - context.subscriptions.push(vscode.authentication.registerAuthenticationProvider('microsoft', 'Microsoft', { - onDidChangeSessions: loginService.onDidChangeSessions, - getSessions: (scopes: string[]) => loginService.getSessions(scopes), - createSession: async (scopes: string[]) => { - try { - /* __GDPR__ - "login" : { - "owner": "TylerLeonhardt", - "comment": "Used to determine the usage of the Microsoft Auth Provider.", - "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } - } - */ - telemetryReporter.sendTelemetryEvent('login', { - // Get rid of guids from telemetry. - scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), - }); - - return await loginService.createSession(scopes); - } catch (e) { - /* __GDPR__ - "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } - */ - telemetryReporter.sendTelemetryEvent('loginFailed'); - - throw e; - } - }, - removeSession: async (id: string) => { - try { - /* __GDPR__ - "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } - */ - telemetryReporter.sendTelemetryEvent('logout'); - - await loginService.removeSessionById(id); - } catch (e) { - /* __GDPR__ - "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } - */ - telemetryReporter.sendTelemetryEvent('logoutFailed'); - } + mainTelemetryReporter, + env.uriScheme !== 'vscode', // isPreRelease + ); + useMsal = shouldUseMsal(expService); + + context.subscriptions.push(workspace.onDidChangeConfiguration(async e => { + if (!e.affectsConfiguration('microsoft.useMsal') || useMsal === shouldUseMsal(expService)) { + return; } - }, { supportsMultipleAccounts: true })); - - let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage); - context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async e => { - if (e.affectsConfiguration('microsoft-sovereign-cloud')) { - microsoftSovereignCloudAuthProviderDisposable?.dispose(); - microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage); + const reload = l10n.t('Reload'); + const result = await window.showInformationMessage( + 'Reload required', + { + modal: true, + detail: l10n.t('Microsoft Account configuration has been changed.'), + }, + reload + ); + + if (result === reload) { + commands.executeCommand('workbench.action.reloadWindow'); } })); - - return; + // Only activate the new extension if we are not running in a browser environment + if (useMsal && typeof navigator === 'undefined') { + await extensionV2.activate(context, mainTelemetryReporter); + } else { + await extensionV1.activate(context, mainTelemetryReporter.telemetryReporter); + } } -// this method is called when your extension is deactivated -export function deactivate() { } +export function deactivate() { + if (useMsal) { + extensionV2.deactivate(); + } else { + extensionV1.deactivate(); + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/extensionV1.ts b/patched-vscode/extensions/microsoft-authentication/src/extensionV1.ts new file mode 100644 index 00000000..f785adad --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/extensionV1.ts @@ -0,0 +1,178 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Environment, EnvironmentParameters } from '@azure/ms-rest-azure-env'; +import { AzureActiveDirectoryService, IStoredSession } from './AADHelper'; +import { BetterTokenStorage } from './betterSecretStorage'; +import { UriEventHandler } from './UriEventHandler'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import Logger from './logger'; + +async function initMicrosoftSovereignCloudAuthProvider(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter, uriHandler: UriEventHandler, tokenStorage: BetterTokenStorage): Promise { + const environment = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('environment'); + let authProviderName: string | undefined; + if (!environment) { + return undefined; + } + + if (environment === 'custom') { + const customEnv = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment'); + if (!customEnv) { + const res = await vscode.window.showErrorMessage(vscode.l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), vscode.l10n.t('Open settings')); + if (res) { + await vscode.commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment'); + } + return undefined; + } + try { + Environment.add(customEnv); + } catch (e) { + const res = await vscode.window.showErrorMessage(vscode.l10n.t('Error validating custom environment setting: {0}', e.message), vscode.l10n.t('Open settings')); + if (res) { + await vscode.commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment'); + } + return undefined; + } + authProviderName = customEnv.name; + } else { + authProviderName = environment; + } + + const env = Environment.get(authProviderName); + if (!env) { + const res = await vscode.window.showErrorMessage(vscode.l10n.t('The environment `{0}` is not a valid environment.', authProviderName), vscode.l10n.t('Open settings')); + return undefined; + } + + const aadService = new AzureActiveDirectoryService( + vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), + context, + uriHandler, + tokenStorage, + telemetryReporter, + env); + await aadService.initialize(); + + const disposable = vscode.authentication.registerAuthenticationProvider('microsoft-sovereign-cloud', authProviderName, { + onDidChangeSessions: aadService.onDidChangeSessions, + getSessions: (scopes: string[]) => aadService.getSessions(scopes), + createSession: async (scopes: string[]) => { + try { + /* __GDPR__ + "login" : { + "owner": "TylerLeonhardt", + "comment": "Used to determine the usage of the Microsoft Sovereign Cloud Auth Provider.", + "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } + } + */ + telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', { + // Get rid of guids from telemetry. + scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), + }); + + return await aadService.createSession(scopes); + } catch (e) { + /* __GDPR__ + "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } + */ + telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed'); + + throw e; + } + }, + removeSession: async (id: string) => { + try { + /* __GDPR__ + "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } + */ + telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud'); + + await aadService.removeSessionById(id); + } catch (e) { + /* __GDPR__ + "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } + */ + telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed'); + } + } + }, { supportsMultipleAccounts: true }); + + context.subscriptions.push(disposable); + return disposable; +} + +export async function activate(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter) { + const uriHandler = new UriEventHandler(); + context.subscriptions.push(uriHandler); + const betterSecretStorage = new BetterTokenStorage('microsoft.login.keylist', context); + + const loginService = new AzureActiveDirectoryService( + Logger, + context, + uriHandler, + betterSecretStorage, + telemetryReporter, + Environment.AzureCloud); + await loginService.initialize(); + + context.subscriptions.push(vscode.authentication.registerAuthenticationProvider('microsoft', 'Microsoft', { + onDidChangeSessions: loginService.onDidChangeSessions, + getSessions: (scopes: string[], options?: vscode.AuthenticationProviderSessionOptions) => loginService.getSessions(scopes, options?.account), + createSession: async (scopes: string[], options?: vscode.AuthenticationProviderSessionOptions) => { + try { + /* __GDPR__ + "login" : { + "owner": "TylerLeonhardt", + "comment": "Used to determine the usage of the Microsoft Auth Provider.", + "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } + } + */ + telemetryReporter.sendTelemetryEvent('login', { + // Get rid of guids from telemetry. + scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), + }); + + return await loginService.createSession(scopes, options?.account); + } catch (e) { + /* __GDPR__ + "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } + */ + telemetryReporter.sendTelemetryEvent('loginFailed'); + + throw e; + } + }, + removeSession: async (id: string) => { + try { + /* __GDPR__ + "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } + */ + telemetryReporter.sendTelemetryEvent('logout'); + + await loginService.removeSessionById(id); + } catch (e) { + /* __GDPR__ + "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } + */ + telemetryReporter.sendTelemetryEvent('logoutFailed'); + } + } + }, { supportsMultipleAccounts: true })); + + let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage); + + context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration('microsoft-sovereign-cloud')) { + microsoftSovereignCloudAuthProviderDisposable?.dispose(); + microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage); + } + })); + + return; +} + +// this method is called when your extension is deactivated +export function deactivate() { } diff --git a/patched-vscode/extensions/microsoft-authentication/src/extensionV2.ts b/patched-vscode/extensions/microsoft-authentication/src/extensionV2.ts new file mode 100644 index 00000000..9610af37 --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/extensionV2.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Environment, EnvironmentParameters } from '@azure/ms-rest-azure-env'; +import Logger from './logger'; +import { MsalAuthProvider } from './node/authProvider'; +import { UriEventHandler } from './UriEventHandler'; +import { authentication, commands, ExtensionContext, l10n, window, workspace, Disposable } from 'vscode'; +import { MicrosoftAuthenticationTelemetryReporter, MicrosoftSovereignCloudAuthenticationTelemetryReporter } from './common/telemetryReporter'; + +async function initMicrosoftSovereignCloudAuthProvider( + context: ExtensionContext, + uriHandler: UriEventHandler +): Promise { + const environment = workspace.getConfiguration('microsoft-sovereign-cloud').get('environment'); + let authProviderName: string | undefined; + if (!environment) { + return undefined; + } + + if (environment === 'custom') { + const customEnv = workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment'); + if (!customEnv) { + const res = await window.showErrorMessage(l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), l10n.t('Open settings')); + if (res) { + await commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment'); + } + return undefined; + } + try { + Environment.add(customEnv); + } catch (e) { + const res = await window.showErrorMessage(l10n.t('Error validating custom environment setting: {0}', e.message), l10n.t('Open settings')); + if (res) { + await commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment'); + } + return undefined; + } + authProviderName = customEnv.name; + } else { + authProviderName = environment; + } + + const env = Environment.get(authProviderName); + if (!env) { + await window.showErrorMessage(l10n.t('The environment `{0}` is not a valid environment.', authProviderName), l10n.t('Open settings')); + return undefined; + } + + const authProvider = new MsalAuthProvider( + context, + new MicrosoftSovereignCloudAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey), + window.createOutputChannel(l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), + uriHandler, + env + ); + await authProvider.initialize(); + const disposable = authentication.registerAuthenticationProvider( + 'microsoft-sovereign-cloud', + authProviderName, + authProvider, + { supportsMultipleAccounts: true } + ); + context.subscriptions.push(disposable); + return disposable; +} + +export async function activate(context: ExtensionContext, mainTelemetryReporter: MicrosoftAuthenticationTelemetryReporter) { + const uriHandler = new UriEventHandler(); + context.subscriptions.push(uriHandler); + const authProvider = new MsalAuthProvider( + context, + mainTelemetryReporter, + Logger, + uriHandler + ); + await authProvider.initialize(); + context.subscriptions.push(authentication.registerAuthenticationProvider( + 'microsoft', + 'Microsoft', + authProvider, + { supportsMultipleAccounts: true } + )); + + let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler); + + context.subscriptions.push(workspace.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration('microsoft-sovereign-cloud')) { + microsoftSovereignCloudAuthProviderDisposable?.dispose(); + microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler); + } + })); +} + +export function deactivate() { } diff --git a/patched-vscode/extensions/microsoft-authentication/src/node/authProvider.ts b/patched-vscode/extensions/microsoft-authentication/src/node/authProvider.ts new file mode 100644 index 00000000..3925f6f5 --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/node/authProvider.ts @@ -0,0 +1,242 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { AccountInfo, AuthenticationResult, ServerError } from '@azure/msal-node'; +import { AuthenticationGetSessionOptions, AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationSession, AuthenticationSessionAccountInformation, CancellationError, env, EventEmitter, ExtensionContext, l10n, LogOutputChannel, Memento, SecretStorage, Uri, window } from 'vscode'; +import { Environment } from '@azure/ms-rest-azure-env'; +import { CachedPublicClientApplicationManager } from './publicClientCache'; +import { UriHandlerLoopbackClient } from '../common/loopbackClientAndOpener'; +import { UriEventHandler } from '../UriEventHandler'; +import { ICachedPublicClientApplication } from '../common/publicClientCache'; +import { MicrosoftAccountType, MicrosoftAuthenticationTelemetryReporter } from '../common/telemetryReporter'; +import { loopbackTemplate } from './loopbackTemplate'; +import { ScopeData } from '../common/scopeData'; + +const redirectUri = 'https://vscode.dev/redirect'; +const MSA_TID = '9188040d-6c67-4c5b-b112-36a304b66dad'; +const MSA_PASSTHRU_TID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a'; + +export class MsalAuthProvider implements AuthenticationProvider { + + private readonly _disposables: { dispose(): void }[]; + private readonly _publicClientManager: CachedPublicClientApplicationManager; + + /** + * Event to signal a change in authentication sessions for this provider. + */ + private readonly _onDidChangeSessionsEmitter = new EventEmitter(); + + /** + * Event to signal a change in authentication sessions for this provider. + * + * NOTE: This event is handled differently in the Microsoft auth provider than "typical" auth providers. Normally, + * this event would fire when the provider's sessions change... which are tied to a specific list of scopes. However, + * since Microsoft identity doesn't care too much about scopes (you can mint a new token from an existing token), + * we just fire this event whenever the account list changes... so essentially there is one session per account. + * + * This is not quite how the API should be used... but this event really is just for signaling that the account list + * has changed. + */ + onDidChangeSessions = this._onDidChangeSessionsEmitter.event; + + constructor( + context: ExtensionContext, + private readonly _telemetryReporter: MicrosoftAuthenticationTelemetryReporter, + private readonly _logger: LogOutputChannel, + private readonly _uriHandler: UriEventHandler, + private readonly _env: Environment = Environment.AzureCloud + ) { + this._disposables = context.subscriptions; + this._publicClientManager = new CachedPublicClientApplicationManager( + context.globalState, + context.secrets, + this._logger, + (e) => this._handleAccountChange(e) + ); + this._disposables.push(this._publicClientManager); + this._disposables.push(this._onDidChangeSessionsEmitter); + + } + + async initialize(): Promise { + await this._publicClientManager.initialize(); + + // Send telemetry for existing accounts + for (const cachedPca of this._publicClientManager.getAll()) { + for (const account of cachedPca.accounts) { + if (!account.idTokenClaims?.tid) { + continue; + } + const tid = account.idTokenClaims.tid; + const type = tid === MSA_TID || tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD; + this._telemetryReporter.sendAccountEvent([], type); + } + } + } + + /** + * See {@link onDidChangeSessions} for more information on how this is used. + * @param param0 Event that contains the added and removed accounts + */ + private _handleAccountChange({ added, deleted }: { added: AccountInfo[]; deleted: AccountInfo[] }) { + const process = (a: AccountInfo) => ({ + // This shouldn't be needed + accessToken: '1234', + id: a.homeAccountId, + scopes: [], + account: { + id: a.homeAccountId, + label: a.username + }, + idToken: a.idToken, + }); + this._onDidChangeSessionsEmitter.fire({ added: added.map(process), changed: [], removed: deleted.map(process) }); + } + + //#region AuthenticationProvider methods + + async getSessions(scopes: string[] | undefined, options?: AuthenticationGetSessionOptions): Promise { + const scopeData = new ScopeData(scopes); + this._logger.info('[getSessions]', scopes ? scopeData.scopeStr : 'all', 'starting'); + if (!scopes) { + // Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead. + + const allSessions: AuthenticationSession[] = []; + for (const cachedPca of this._publicClientManager.getAll()) { + const sessions = await this.getAllSessionsForPca(cachedPca, scopeData.originalScopes, scopeData.scopesToSend, options?.account); + allSessions.push(...sessions); + } + return allSessions; + } + + const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant); + const sessions = await this.getAllSessionsForPca(cachedPca, scopeData.originalScopes, scopeData.scopesToSend, options?.account); + this._logger.info(`[getSessions] returned ${sessions.length} sessions`); + return sessions; + + } + + async createSession(scopes: readonly string[]): Promise { + const scopeData = new ScopeData(scopes); + // Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead. + + this._logger.info('[createSession]', scopeData.scopeStr, 'starting'); + const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant); + let result: AuthenticationResult; + try { + result = await cachedPca.acquireTokenInteractive({ + openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); }, + scopes: scopeData.scopesToSend, + // The logic for rendering one or the other of these templates is in the + // template itself, so we pass the same one for both. + successTemplate: loopbackTemplate, + errorTemplate: loopbackTemplate + }); + } catch (e) { + if (e instanceof CancellationError) { + const yes = l10n.t('Yes'); + const result = await window.showErrorMessage( + l10n.t('Having trouble logging in?'), + { + modal: true, + detail: l10n.t('Would you like to try a different way to sign in to your Microsoft account? ({0})', 'protocol handler') + }, + yes + ); + if (!result) { + this._telemetryReporter.sendLoginFailedEvent(); + throw e; + } + } + // This error comes from the backend and is likely not due to the user's machine + // failing to open a port or something local that would require us to try the + // URL handler loopback client. + if (e instanceof ServerError) { + this._telemetryReporter.sendLoginFailedEvent(); + throw e; + } + const loopbackClient = new UriHandlerLoopbackClient(this._uriHandler, redirectUri); + try { + result = await cachedPca.acquireTokenInteractive({ + openBrowser: (url: string) => loopbackClient.openBrowser(url), + scopes: scopeData.scopesToSend, + loopbackClient + }); + } catch (e) { + this._telemetryReporter.sendLoginFailedEvent(); + throw e; + } + } + + const session = this.toAuthenticationSession(result, scopeData.originalScopes); + this._telemetryReporter.sendLoginEvent(session.scopes); + this._logger.info('[createSession]', scopeData.scopeStr, 'returned session'); + this._onDidChangeSessionsEmitter.fire({ added: [session], changed: [], removed: [] }); + return session; + } + + async removeSession(sessionId: string): Promise { + this._logger.info('[removeSession]', sessionId, 'starting'); + for (const cachedPca of this._publicClientManager.getAll()) { + const accounts = cachedPca.accounts; + for (const account of accounts) { + if (account.homeAccountId === sessionId) { + this._telemetryReporter.sendLogoutEvent(); + try { + await cachedPca.removeAccount(account); + } catch (e) { + this._telemetryReporter.sendLogoutFailedEvent(); + throw e; + } + this._logger.info('[removeSession]', sessionId, 'removed session'); + return; + } + } + } + this._logger.info('[removeSession]', sessionId, 'session not found'); + } + + //#endregion + + private async getOrCreatePublicClientApplication(clientId: string, tenant: string): Promise { + const authority = new URL(tenant, this._env.activeDirectoryEndpointUrl).toString(); + return await this._publicClientManager.getOrCreate(clientId, authority); + } + + private async getAllSessionsForPca( + cachedPca: ICachedPublicClientApplication, + originalScopes: readonly string[], + scopesToSend: string[], + accountFilter?: AuthenticationSessionAccountInformation + ): Promise { + const accounts = accountFilter + ? cachedPca.accounts.filter(a => a.homeAccountId === accountFilter.id) + : cachedPca.accounts; + const sessions: AuthenticationSession[] = []; + for (const account of accounts) { + try { + const result = await cachedPca.acquireTokenSilent({ account, scopes: scopesToSend, redirectUri }); + sessions.push(this.toAuthenticationSession(result, originalScopes)); + } catch (e) { + // If we can't get a token silently, the account is probably in a bad state so we should skip it + // MSAL will log this already, so we don't need to log it again + continue; + } + } + return sessions; + } + + private toAuthenticationSession(result: AuthenticationResult, scopes: readonly string[]): AuthenticationSession & { idToken: string } { + return { + accessToken: result.accessToken, + idToken: result.idToken, + id: result.account?.homeAccountId ?? result.uniqueId, + account: { + id: result.account?.homeAccountId ?? result.uniqueId, + label: result.account?.username ?? 'Unknown', + }, + scopes + }; + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/node/authServer.ts b/patched-vscode/extensions/microsoft-authentication/src/node/authServer.ts index de08c6fc..2d6a8d03 100644 --- a/patched-vscode/extensions/microsoft-authentication/src/node/authServer.ts +++ b/patched-vscode/extensions/microsoft-authentication/src/node/authServer.ts @@ -110,20 +110,29 @@ export class LoopbackAuthServer implements ILoopbackServer { const code = reqUrl.searchParams.get('code') ?? undefined; const state = reqUrl.searchParams.get('state') ?? undefined; const nonce = (reqUrl.searchParams.get('nonce') ?? '').replace(/ /g, '+'); + const error = reqUrl.searchParams.get('error') ?? undefined; + if (error) { + res.writeHead(302, { location: `/?error=${reqUrl.searchParams.get('error_description')}` }); + res.end(); + deferred.reject(new Error(error)); + break; + } if (!code || !state || !nonce) { res.writeHead(400); res.end(); - return; + break; } if (this.state !== state) { res.writeHead(302, { location: `/?error=${encodeURIComponent('State does not match.')}` }); res.end(); - throw new Error('State does not match.'); + deferred.reject(new Error('State does not match.')); + break; } if (this.nonce !== nonce) { res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` }); res.end(); - throw new Error('Nonce does not match.'); + deferred.reject(new Error('Nonce does not match.')); + break; } deferred.resolve({ code, state }); res.writeHead(302, { location: '/' }); diff --git a/patched-vscode/extensions/microsoft-authentication/src/node/loopbackTemplate.ts b/patched-vscode/extensions/microsoft-authentication/src/node/loopbackTemplate.ts new file mode 100644 index 00000000..60d3cd4d --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/node/loopbackTemplate.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +export const loopbackTemplate = ` + + + + + + Microsoft Account - Sign In + + + + + + + Visual Studio Code + +
+
+ You are signed in now and can close this page. +
+
+ An error occurred while signing in: +
+
+
+ + + + +`; diff --git a/patched-vscode/extensions/microsoft-authentication/src/node/publicClientCache.ts b/patched-vscode/extensions/microsoft-authentication/src/node/publicClientCache.ts new file mode 100644 index 00000000..34bf2c3c --- /dev/null +++ b/patched-vscode/extensions/microsoft-authentication/src/node/publicClientCache.ts @@ -0,0 +1,252 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AccountInfo, AuthenticationResult, Configuration, InteractiveRequest, PublicClientApplication, SilentFlowRequest } from '@azure/msal-node'; +import { SecretStorageCachePlugin } from '../common/cachePlugin'; +import { SecretStorage, LogOutputChannel, Disposable, SecretStorageChangeEvent, EventEmitter, Memento, window, ProgressLocation, l10n } from 'vscode'; +import { MsalLoggerOptions } from '../common/loggerOptions'; +import { ICachedPublicClientApplication, ICachedPublicClientApplicationManager } from '../common/publicClientCache'; +import { raceCancellationAndTimeoutError } from '../common/async'; + +export interface IPublicClientApplicationInfo { + clientId: string; + authority: string; +} + +const _keyPrefix = 'pca:'; + +export class CachedPublicClientApplicationManager implements ICachedPublicClientApplicationManager { + // The key is the clientId and authority stringified + private readonly _pcas = new Map(); + + private _initialized = false; + private _disposable: Disposable; + + constructor( + private readonly _globalMemento: Memento, + private readonly _secretStorage: SecretStorage, + private readonly _logger: LogOutputChannel, + private readonly _accountChangeHandler: (e: { added: AccountInfo[]; deleted: AccountInfo[] }) => void + ) { + this._disposable = _secretStorage.onDidChange(e => this._handleSecretStorageChange(e)); + } + + async initialize() { + this._logger.debug('[initialize] Initializing PublicClientApplicationManager'); + const keys = await this._secretStorage.get('publicClientApplications'); + if (!keys) { + this._initialized = true; + return; + } + + const promises = new Array>(); + try { + for (const key of JSON.parse(keys) as string[]) { + try { + const { clientId, authority } = JSON.parse(key) as IPublicClientApplicationInfo; + // Load the PCA in memory + promises.push(this.getOrCreate(clientId, authority)); + } catch (e) { + // ignore + } + } + } catch (e) { + // data is corrupted + this._logger.error('[initialize] Error initializing PublicClientApplicationManager:', e); + await this._secretStorage.delete('publicClientApplications'); + } + + // TODO: should we do anything for when this fails? + await Promise.allSettled(promises); + this._logger.debug('[initialize] PublicClientApplicationManager initialized'); + this._initialized = true; + } + + dispose() { + this._disposable.dispose(); + Disposable.from(...this._pcas.values()).dispose(); + } + + async getOrCreate(clientId: string, authority: string): Promise { + if (!this._initialized) { + throw new Error('PublicClientApplicationManager not initialized'); + } + + // Use the clientId and authority as the key + const pcasKey = JSON.stringify({ clientId, authority }); + let pca = this._pcas.get(pcasKey); + if (pca) { + this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager cache hit`); + return pca; + } + + this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager cache miss, creating new PCA...`); + pca = new CachedPublicClientApplication(clientId, authority, this._globalMemento, this._secretStorage, this._accountChangeHandler, this._logger); + this._pcas.set(pcasKey, pca); + await pca.initialize(); + await this._storePublicClientApplications(); + this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager PCA created`); + return pca; + } + + getAll(): ICachedPublicClientApplication[] { + if (!this._initialized) { + throw new Error('PublicClientApplicationManager not initialized'); + } + return Array.from(this._pcas.values()); + } + + private async _handleSecretStorageChange(e: SecretStorageChangeEvent) { + if (!e.key.startsWith(_keyPrefix)) { + return; + } + + this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager secret storage change: ${e.key}`); + const result = await this._secretStorage.get(e.key); + const pcasKey = e.key.split(_keyPrefix)[1]; + + // If the cache was deleted, or the PCA has zero accounts left, remove the PCA + if (!result || this._pcas.get(pcasKey)?.accounts.length === 0) { + this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager removing PCA: ${pcasKey}`); + this._pcas.delete(pcasKey); + await this._storePublicClientApplications(); + this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager PCA removed: ${pcasKey}`); + return; + } + + // Load the PCA in memory if it's not already loaded + const { clientId, authority } = JSON.parse(pcasKey) as IPublicClientApplicationInfo; + this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager loading PCA: ${pcasKey}`); + await this.getOrCreate(clientId, authority); + this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager PCA loaded: ${pcasKey}`); + } + + private async _storePublicClientApplications() { + await this._secretStorage.store( + 'publicClientApplications', + JSON.stringify(Array.from(this._pcas.keys())) + ); + } +} + +class CachedPublicClientApplication implements ICachedPublicClientApplication { + private _pca: PublicClientApplication; + + private _accounts: AccountInfo[] = []; + private readonly _disposable: Disposable; + + private readonly _loggerOptions = new MsalLoggerOptions(this._logger); + private readonly _secretStorageCachePlugin = new SecretStorageCachePlugin( + this._secretStorage, + // Include the prefix in the key so we can easily identify it later + `${_keyPrefix}${JSON.stringify({ clientId: this._clientId, authority: this._authority })}` + ); + private readonly _config: Configuration = { + auth: { clientId: this._clientId, authority: this._authority }, + system: { + loggerOptions: { + correlationId: `${this._clientId}] [${this._authority}`, + loggerCallback: (level, message, containsPii) => this._loggerOptions.loggerCallback(level, message, containsPii), + } + }, + cache: { + cachePlugin: this._secretStorageCachePlugin + } + }; + + /** + * We keep track of the last time an account was removed so we can recreate the PCA if we detect that an account was removed. + * This is due to MSAL-node not providing a way to detect when an account is removed from the cache. An internal issue has been + * filed to track this. If MSAL-node ever provides a way to detect this or handle this better in the Persistant Cache Plugin, + * we can remove this logic. + */ + private _lastCreated: Date; + + constructor( + private readonly _clientId: string, + private readonly _authority: string, + private readonly _globalMemento: Memento, + private readonly _secretStorage: SecretStorage, + private readonly _accountChangeHandler: (e: { added: AccountInfo[]; changed: AccountInfo[]; deleted: AccountInfo[] }) => void, + private readonly _logger: LogOutputChannel + ) { + this._pca = new PublicClientApplication(this._config); + this._lastCreated = new Date(); + this._disposable = this._registerOnSecretStorageChanged(); + } + + get accounts(): AccountInfo[] { return this._accounts; } + get clientId(): string { return this._clientId; } + get authority(): string { return this._authority; } + + initialize(): Promise { + return this._update(); + } + + dispose(): void { + this._disposable.dispose(); + } + + async acquireTokenSilent(request: SilentFlowRequest): Promise { + this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}]`); + const result = await this._pca.acquireTokenSilent(request); + if (result.account && !result.fromCache) { + this._accountChangeHandler({ added: [], changed: [result.account], deleted: [] }); + } + return result; + } + + async acquireTokenInteractive(request: InteractiveRequest): Promise { + this._logger.debug(`[acquireTokenInteractive] [${this._clientId}] [${this._authority}] [${request.scopes?.join(' ')}] loopbackClientOverride: ${request.loopbackClient ? 'true' : 'false'}`); + return await window.withProgress( + { + location: ProgressLocation.Notification, + cancellable: true, + title: l10n.t('Signing in to Microsoft...') + }, + (_process, token) => raceCancellationAndTimeoutError( + this._pca.acquireTokenInteractive(request), + token, + 1000 * 60 * 5 + ), // 5 minutes + ); + } + + removeAccount(account: AccountInfo): Promise { + this._globalMemento.update(`lastRemoval:${this._clientId}:${this._authority}`, new Date()); + return this._pca.getTokenCache().removeAccount(account); + } + + private _registerOnSecretStorageChanged() { + return this._secretStorageCachePlugin.onDidChange(() => this._update()); + } + + private async _update() { + const before = this._accounts; + this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update before: ${before.length}`); + // Dates are stored as strings in the memento + const lastRemovalDate = this._globalMemento.get(`lastRemoval:${this._clientId}:${this._authority}`); + if (lastRemovalDate && this._lastCreated && Date.parse(lastRemovalDate) > this._lastCreated.getTime()) { + this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication removal detected... recreating PCA...`); + this._pca = new PublicClientApplication(this._config); + this._lastCreated = new Date(); + } + + const after = await this._pca.getAllAccounts(); + this._accounts = after; + this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update after: ${after.length}`); + + const beforeSet = new Set(before.map(b => b.homeAccountId)); + const afterSet = new Set(after.map(a => a.homeAccountId)); + + const added = after.filter(a => !beforeSet.has(a.homeAccountId)); + const deleted = before.filter(b => !afterSet.has(b.homeAccountId)); + if (added.length > 0 || deleted.length > 0) { + this._accountChangeHandler({ added, changed: [], deleted }); + this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication accounts changed. added: ${added.length}, deleted: ${deleted.length}`); + } + this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update complete`); + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/yarn.lock b/patched-vscode/extensions/microsoft-authentication/yarn.lock index 6f277110..d0d70647 100644 --- a/patched-vscode/extensions/microsoft-authentication/yarn.lock +++ b/patched-vscode/extensions/microsoft-authentication/yarn.lock @@ -7,6 +7,20 @@ resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz#45809f89763a480924e21d3c620cd40866771625" integrity sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw== +"@azure/msal-common@14.14.1": + version "14.14.1" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.14.1.tgz#62e4569518d2c52e7de1f460d40ab919ca66b99b" + integrity sha512-2Q3tqNz/PZLfSr8BvcHZVpRRfSn4MjGSqjj9J+HlBsmbf1Uu4P0WeXnemjTJwwx9KrmplsrN3UkZ/LPOR720rw== + +"@azure/msal-node@^2.13.0": + version "2.13.0" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.13.0.tgz#007ffffa84e4f91f00f816b980740837ce6626fe" + integrity sha512-DhP97ycs7qlCVzzzWGzJiwAFyFj5okno74E4FUZ61oCLfKh4IxA1kxirqzrWuYZWpBe9HVPL6GA4NvmlEOBN5Q== + dependencies: + "@azure/msal-common" "14.14.1" + jsonwebtoken "^9.0.0" + uuid "^8.3.0" + "@microsoft/1ds-core-js@4.0.3", "@microsoft/1ds-core-js@^4.0.3": version "4.0.3" resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz#c8a92c623745a9595e06558a866658480c33bdf9" @@ -153,6 +167,11 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -165,6 +184,13 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + form-data@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" @@ -174,6 +200,74 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" @@ -186,32 +280,39 @@ mime-types@^2.1.12: dependencies: mime-db "1.44.0" -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +semver@^7.5.4: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +tas-client@0.2.33: + version "0.2.33" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.2.33.tgz#451bf114a8a64748030ce4068ab7d079958402e6" + integrity sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg== undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= +vscode-tas-client@^0.1.84: + version "0.1.84" + resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.84.tgz#906bdcfd8c9e1dc04321d6bc0335184f9119968e" + integrity sha512-rUTrUopV+70hvx1hW5ebdw1nd6djxubkLvVxjGdyD/r5v/wcVF41LIfiAtbm5qLZDtQdsMH1IaCuDoluoIa88w== dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" + tas-client "0.2.33" diff --git a/patched-vscode/extensions/notebook-renderers/src/index.ts b/patched-vscode/extensions/notebook-renderers/src/index.ts index 8954017f..8f5fa908 100644 --- a/patched-vscode/extensions/notebook-renderers/src/index.ts +++ b/patched-vscode/extensions/notebook-renderers/src/index.ts @@ -11,7 +11,7 @@ import { formatStackTrace } from './stackTraceHelper'; function clearContainer(container: HTMLElement) { while (container.firstChild) { - container.removeChild(container.firstChild); + container.firstChild.remove(); } } @@ -378,7 +378,7 @@ function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, contentParent = document.createElement('div'); contentParent.appendChild(newContent); while (outputElement.firstChild) { - outputElement.removeChild(outputElement.firstChild); + outputElement.firstChild.remove(); } outputElement.appendChild(contentParent); } @@ -462,7 +462,7 @@ export const activate: ActivationFunction = (ctx) => { border-color: var(--theme-input-focus-border-color); } #container div.output .scrollable { - overflow-y: scroll; + overflow-y: auto; max-height: var(--notebook-cell-output-max-height); } #container div.output .scrollable.scrollbar-visible { diff --git a/patched-vscode/extensions/notebook-renderers/src/textHelper.ts b/patched-vscode/extensions/notebook-renderers/src/textHelper.ts index b49dbb6a..9c080c7f 100644 --- a/patched-vscode/extensions/notebook-renderers/src/textHelper.ts +++ b/patched-vscode/extensions/notebook-renderers/src/textHelper.ts @@ -71,6 +71,11 @@ function generateNestedViewAllElement(outputId: string) { function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number, linkOptions: LinkOptions) { const container = document.createElement('div'); + container.setAttribute('data-vscode-context', JSON.stringify({ + webviewSection: 'text', + outputId: id, + 'preventDefaultContextMenuItems': true + })); const lineCount = buffer.length; if (lineCount <= linesLimit) { @@ -95,6 +100,11 @@ function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number function scrollableArrayOfString(id: string, buffer: string[], linkOptions: LinkOptions) { const element = document.createElement('div'); + element.setAttribute('data-vscode-context', JSON.stringify({ + webviewSection: 'text', + outputId: id, + 'preventDefaultContextMenuItems': true + })); if (buffer.length > softScrollableLineLimit) { element.appendChild(generateNestedViewAllElement(id)); } diff --git a/patched-vscode/extensions/notebook-renderers/yarn.lock b/patched-vscode/extensions/notebook-renderers/yarn.lock index 3cbe531e..00c3e704 100644 --- a/patched-vscode/extensions/notebook-renderers/yarn.lock +++ b/patched-vscode/extensions/notebook-renderers/yarn.lock @@ -408,9 +408,9 @@ word-wrap@~1.2.3: integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== ws@^8.13.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xml-name-validator@^4.0.0: version "4.0.0" diff --git a/patched-vscode/extensions/npm/src/features/packageJSONContribution.ts b/patched-vscode/extensions/npm/src/features/packageJSONContribution.ts index a2f4fabc..999f3966 100644 --- a/patched-vscode/extensions/npm/src/features/packageJSONContribution.ts +++ b/patched-vscode/extensions/npm/src/features/packageJSONContribution.ts @@ -293,7 +293,13 @@ export class PackageJSONContribution implements IJSONContribution { // COREPACK_ENABLE_PROJECT_SPEC makes the npm view command succeed // even if packageManager specified a package manager other than npm. const env = { ...process.env, COREPACK_ENABLE_AUTO_PIN: '0', COREPACK_ENABLE_PROJECT_SPEC: '0' }; - cp.execFile(npmCommandPath, args, { cwd, env }, (error, stdout) => { + let options: cp.ExecFileOptions = { cwd, env }; + let commandPath: string = npmCommandPath; + if (process.platform === 'win32') { + options = { cwd, env, shell: true }; + commandPath = `"${npmCommandPath}"`; + } + cp.execFile(commandPath, args, options, (error, stdout) => { if (!error) { try { const content = JSON.parse(stdout); diff --git a/patched-vscode/extensions/npm/yarn.lock b/patched-vscode/extensions/npm/yarn.lock index a7afc9f8..d1f79c9f 100644 --- a/patched-vscode/extensions/npm/yarn.lock +++ b/patched-vscode/extensions/npm/yarn.lock @@ -38,22 +38,22 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -118,12 +118,12 @@ locate-path@^6.0.0: p-locate "^5.0.0" micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.1" - picomatch "^2.0.5" + braces "^3.0.3" + picomatch "^2.3.1" minimatch@^5.1.6: version "5.1.6" @@ -151,10 +151,10 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -picomatch@^2.0.5: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pify@^4.0.1: version "4.0.1" diff --git a/patched-vscode/extensions/package.json b/patched-vscode/extensions/package.json index 2c83af40..c918c19c 100644 --- a/patched-vscode/extensions/package.json +++ b/patched-vscode/extensions/package.json @@ -4,14 +4,14 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "5.4.5" + "typescript": "^5.5.4" }, "scripts": { "postinstall": "node ./postinstall.mjs" }, "devDependencies": { "@parcel/watcher": "2.1.0", - "esbuild": "0.20.0", + "esbuild": "0.23.0", "vscode-grammar-updater": "^1.1.0" }, "resolutions": { diff --git a/patched-vscode/extensions/sagemaker-extension/.vscodeignore b/patched-vscode/extensions/sagemaker-extension/.vscodeignore deleted file mode 100644 index 56b78554..00000000 --- a/patched-vscode/extensions/sagemaker-extension/.vscodeignore +++ /dev/null @@ -1,12 +0,0 @@ -.vscode/** -.vscode-test/** -out/test/** -out/** -test/** -src/** -tsconfig.json -out/test/** -out/** -cgmanifest.json -yarn.lock -preview-src/** diff --git a/patched-vscode/extensions/sagemaker-extension/README.md b/patched-vscode/extensions/sagemaker-extension/README.md deleted file mode 100644 index cdfdcfae..00000000 --- a/patched-vscode/extensions/sagemaker-extension/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Sagemaker Extension - -The SageMaker Extension alerts users to sign in again before their session expires. It remains active continuously and cannot be disabled. - -Session Management: The extension monitors the SageMaker cookie to ensure seamless session continuity. It proactively alerts users to sign in again before the session expires, enhancing the user experience by preventing unexpected session interruptions. \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extension/extension-browser.webpack.config.js b/patched-vscode/extensions/sagemaker-extension/extension-browser.webpack.config.js deleted file mode 100644 index 68271e0e..00000000 --- a/patched-vscode/extensions/sagemaker-extension/extension-browser.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright Amazon.com Inc. or its affiliates. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, - entry: { - extension: './src/extension.ts' - }, -}); diff --git a/patched-vscode/extensions/sagemaker-extension/extension.webpack.config.js b/patched-vscode/extensions/sagemaker-extension/extension.webpack.config.js deleted file mode 100644 index 59852626..00000000 --- a/patched-vscode/extensions/sagemaker-extension/extension.webpack.config.js +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright Amazon.com Inc. or its affiliates. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, - resolve: { - mainFields: ['module', 'main'] - }, - entry: { - extension: './src/extension.ts', - } -}); diff --git a/patched-vscode/extensions/sagemaker-extension/package.json b/patched-vscode/extensions/sagemaker-extension/package.json deleted file mode 100644 index 88e8cc0e..00000000 --- a/patched-vscode/extensions/sagemaker-extension/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "sagemaker-extension", - "displayName": "Sagemaker Extension", - "description": "Sagemaker Extension", - "extensionKind": [ - "workspace" - ], - "version": "1.0.0", - "publisher": "sagemaker", - "license": "MIT", - "engines": { - "vscode": "^1.70.0" - }, - "main": "./out/extension", - "categories": [ - "Other" - ], - "activationEvents": [ - "*" - ], - "capabilities": { - "virtualWorkspaces": true, - "untrustedWorkspaces": { - "supported": true - } - }, - "contributes": { - "configuration": { - "type": "object", - "title": "Sagemaker Extension", - "properties": {} - }, - "commands": [ - ] - }, - "scripts": { - "compile": "gulp compile-extension:sagemaker-extension", - "watch": "npm run build-preview && gulp watch-extension:sagemaker-extension", - "vscode:prepublish": "npm run build-ext", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-extension ./tsconfig.json" - }, - "dependencies": { - }, - "repository": { - } -} diff --git a/patched-vscode/extensions/sagemaker-extension/src/constant.ts b/patched-vscode/extensions/sagemaker-extension/src/constant.ts deleted file mode 100644 index 3417eefb..00000000 --- a/patched-vscode/extensions/sagemaker-extension/src/constant.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Constants -export const WARNING_TIME_HEADER = 'Session expiring soon'; - -export const WARNING_BUTTON_REMIND_ME_IN_5_MINS = 'Remind me in 5 minutes'; -export const WARNING_BUTTON_SAVE = 'Save'; -export const WARNING_BUTTON_SAVE_AND_RENEW_SESSION = 'Save and renew session'; -export const WARNING_TIME_BUTTONS = { - SSO: [WARNING_BUTTON_REMIND_ME_IN_5_MINS, WARNING_BUTTON_SAVE], - IAM: [WARNING_BUTTON_REMIND_ME_IN_5_MINS, WARNING_BUTTON_SAVE_AND_RENEW_SESSION] -}; - -// Constants for signInWarning -export const SIGN_IN_HEADER = 'Please sign in again'; -export const SIGN_IN_MESSAGE = "You were logged out of your account. Choose 'Sign In' to continue using this workplace."; -export const SIGN_IN_MESSAGE_WHEN_REDIRECT_URL_DOES_NOT_EXIST = "You were logged out of your account. You are not able to\n" + - " perform actions in your workplace at this time. Please start a\n" + - " new session."; -export const SIGN_IN_BUTTON = 'Sign In'; -export const SSO_MESSAGE = 'To renew the session, log out from Studio App via "File" -> "Log Out" and then "Sign out" from AWS IAM Identity Center (successor to AWS SSO) user portal. Do you want to save all changes now?'; -export const IAM_MESSAGE = 'Do you want to renew your session now?' -export enum AUTH_MODE { - SSO = "Sso", - IAM = "Iam" -} -export const FIFTEEN_MINUTES_INTERVAL_MILLIS = 15 * 60 * 1000; -export const FIVE_MINUTES_INTERVAL_MILLIS = 5 * 60 * 1000; - -export const SAGEMAKER_METADATA_PATH = '/opt/ml/metadata/resource-metadata.json'; - -export class SagemakerCookie { - authMode: string - expiryTime: number - ssoExpiryTimestamp: number - studioUserProfileName: string - redirectURL: string - - constructor( - authMode: string, - expiryTime: number, - ssoExpiryTimestamp: number, - studioUserProfileName: string, - redirectURL: string - ) { - this.authMode = authMode; - this.expiryTime = expiryTime; - this.ssoExpiryTimestamp = ssoExpiryTimestamp - this.studioUserProfileName = studioUserProfileName - this.redirectURL = redirectURL - } -}; - -export class SagemakerResourceMetadata { - AppType?: string - DomainId?: string - SpaceName?: string - ResourceArn?: string - ResourceName?: string - AppImageVersion?: string -}; -export function isSSOMode(cookie: SagemakerCookie) { - return (cookie.authMode === AUTH_MODE.SSO) -} - -export function getExpiryTime(cookie: SagemakerCookie): number { - if (AUTH_MODE.SSO === cookie.authMode) { - return cookie.ssoExpiryTimestamp; - } else if (AUTH_MODE.IAM === cookie.authMode) { - return cookie.expiryTime; - } else { - return -1; - } -} \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-extension/src/extension.ts deleted file mode 100644 index b85cec6a..00000000 --- a/patched-vscode/extensions/sagemaker-extension/src/extension.ts +++ /dev/null @@ -1,137 +0,0 @@ -import * as vscode from 'vscode'; -import * as fs from 'fs'; -import { SessionWarning } from "./sessionWarning"; -import { - FIFTEEN_MINUTES_INTERVAL_MILLIS, - FIVE_MINUTES_INTERVAL_MILLIS, - SAGEMAKER_METADATA_PATH, - SIGN_IN_BUTTON, - WARNING_BUTTON_REMIND_ME_IN_5_MINS, - WARNING_BUTTON_SAVE, - WARNING_BUTTON_SAVE_AND_RENEW_SESSION, - SagemakerCookie, - SagemakerResourceMetadata, - getExpiryTime -} from "./constant"; -import * as console from "console"; - - -const PARSE_SAGEMAKER_COOKIE_COMMAND = 'sagemaker.parseCookies'; - -function showWarningDialog() { - vscode.commands.executeCommand(PARSE_SAGEMAKER_COOKIE_COMMAND).then(response => { - - const sagemakerCookie: SagemakerCookie = response as SagemakerCookie - const remainingTime: number = getExpiryTime(sagemakerCookie) - Date.now(); - - if(!(Object.keys(sagemakerCookie).length === 0)) { - if (getExpiryTime(sagemakerCookie) != null && remainingTime > FIFTEEN_MINUTES_INTERVAL_MILLIS) { - // This means cookie has been reset, reinitializing again - initialize(sagemakerCookie); - } else if (getExpiryTime(sagemakerCookie) != null && remainingTime > 0) { - // READ COOKIE again to decide to show this up - - SessionWarning.sessionExpiringWarning(remainingTime, sagemakerCookie) - .then((selection) => { - if (selection === WARNING_BUTTON_REMIND_ME_IN_5_MINS) { - // Trigger the function to show the warning again after 5 minutes. - setTimeout(showWarningDialog, FIVE_MINUTES_INTERVAL_MILLIS); - } else if (selection === WARNING_BUTTON_SAVE) { - saveWorkspace(); - } else if (selection === WARNING_BUTTON_SAVE_AND_RENEW_SESSION) { - saveWorkspace(); - // Trigger the function to make an API call to renew the session. - renewSession(sagemakerCookie); - } - }); - - } else { - // this means expiryTime cookie is either invalid or <0 - signInError(sagemakerCookie); - } - } else { - // no cookie found so assuming its running locally - } - - }); - -} - -function signInError(sagemakerCookie: SagemakerCookie) { - // The session has expired - SessionWarning.signInWarning(sagemakerCookie) - .then((selection) => { - if (selection === SIGN_IN_BUTTON) { - vscode.env.openExternal(vscode.Uri.parse(sagemakerCookie.redirectURL)); - } - }); -} - -function initialize(sagemakerCookie: SagemakerCookie) { - const currentTime = Date.now(); - const timeToExpiry = getExpiryTime(sagemakerCookie) - currentTime; - - if (timeToExpiry <= 0) { - signInError(sagemakerCookie); - } else if (timeToExpiry >= FIFTEEN_MINUTES_INTERVAL_MILLIS) { - const warningTime = timeToExpiry - FIFTEEN_MINUTES_INTERVAL_MILLIS; - setTimeout(() => { - showWarningDialog(); - }, warningTime); - } else { - // If less than or equal to 15 minutes left, set a timer for the remaining time - const warningTime = timeToExpiry % FIVE_MINUTES_INTERVAL_MILLIS; - setTimeout(() => { - showWarningDialog(); - }, warningTime); - } -} - -function saveWorkspace() { - vscode.workspace.saveAll().then(() => { - // TODO: log workspace saved - }); -} -function renewSession(sagemakerCookie: SagemakerCookie) { - // TODO: Log and trigger a Signin - vscode.env.openExternal(vscode.Uri.parse(sagemakerCookie.redirectURL)); - // Trigger the function to show the warning again after 5 minutes again to validate. - setTimeout(showWarningDialog, FIVE_MINUTES_INTERVAL_MILLIS); -} - -function updateStatusItemWithMetadata(context: vscode.ExtensionContext) { - fs.readFile(SAGEMAKER_METADATA_PATH, 'utf-8', (err, data) => { - if (err) { - // fail silently not to block users - } else { - try { - const jsonData = JSON.parse(data) as SagemakerResourceMetadata; - const spaceName = jsonData.SpaceName; - - if (spaceName != null) { - let spaceNameStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); - spaceNameStatusBarItem.text = `Space: ${spaceName}`; - spaceNameStatusBarItem.show(); - context.subscriptions.push(spaceNameStatusBarItem); - } - } catch (jsonError) { - // fail silently not to block users - } - } - }); -} - -export function activate(context: vscode.ExtensionContext) { - - // TODO: log activation of extension - console.log('Activating Sagemaker Extension...'); - - // execute the get cookie command and save the data to cookies - vscode.commands.executeCommand(PARSE_SAGEMAKER_COOKIE_COMMAND).then(r => { - - const sagemakerCookie: SagemakerCookie = r as SagemakerCookie - - initialize(sagemakerCookie); - updateStatusItemWithMetadata(context); - }); -} diff --git a/patched-vscode/extensions/sagemaker-extension/src/sessionWarning.ts b/patched-vscode/extensions/sagemaker-extension/src/sessionWarning.ts deleted file mode 100644 index df7718ec..00000000 --- a/patched-vscode/extensions/sagemaker-extension/src/sessionWarning.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as vscode from "vscode"; -import { - IAM_MESSAGE, - isSSOMode, SagemakerCookie, - SIGN_IN_BUTTON, - SIGN_IN_HEADER, - SIGN_IN_MESSAGE, SIGN_IN_MESSAGE_WHEN_REDIRECT_URL_DOES_NOT_EXIST, SSO_MESSAGE, - WARNING_TIME_BUTTONS, - WARNING_TIME_HEADER -} from "./constant"; - -export class SessionWarning { - - public static sessionExpiringWarning (warningTime: number, cookie: SagemakerCookie): Thenable { - // convert warningTime from ms to minutes; - const warningTimeInMinutes: number = Math.floor(warningTime / 60000); - const detail: string = `Your session will expire in ${warningTimeInMinutes} minutes. If your session expires, you could lose unsaved changes \n ${isSSOMode(cookie) ? SSO_MESSAGE : IAM_MESSAGE}` - const sessionExpiringOptions: vscode.MessageOptions = { - detail: detail, - modal: true - }; - - // Session expiration warning... - if (isSSOMode(cookie)) { - return vscode.window.showWarningMessage(WARNING_TIME_HEADER, sessionExpiringOptions, ...WARNING_TIME_BUTTONS.SSO); - } else { - return vscode.window.showWarningMessage(WARNING_TIME_HEADER, sessionExpiringOptions, ...WARNING_TIME_BUTTONS.IAM); - } - } - - public static signInWarning (cookie: SagemakerCookie): Thenable { - const signInOptions: vscode.MessageOptions = { - detail: cookie.redirectURL ? SIGN_IN_MESSAGE : SIGN_IN_MESSAGE_WHEN_REDIRECT_URL_DOES_NOT_EXIST, - modal: true - }; - - // SignIn warning... - if (cookie.redirectURL) { - return vscode.window.showErrorMessage(SIGN_IN_HEADER, signInOptions, SIGN_IN_BUTTON); - } else { - return vscode.window.showErrorMessage(SIGN_IN_HEADER, signInOptions); - } - } -} \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extension/tsconfig.json b/patched-vscode/extensions/sagemaker-extension/tsconfig.json deleted file mode 100644 index fcd79775..00000000 --- a/patched-vscode/extensions/sagemaker-extension/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": "./out" - }, - "include": [ - "src/**/*", - "../../src/vscode-dts/vscode.d.ts" - ] -} diff --git a/patched-vscode/extensions/sagemaker-extension/yarn.lock b/patched-vscode/extensions/sagemaker-extension/yarn.lock deleted file mode 100644 index fb57ccd1..00000000 --- a/patched-vscode/extensions/sagemaker-extension/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - diff --git a/patched-vscode/extensions/sagemaker-idle-extension/.vscodeignore b/patched-vscode/extensions/sagemaker-idle-extension/.vscodeignore deleted file mode 100644 index 56b78554..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/.vscodeignore +++ /dev/null @@ -1,12 +0,0 @@ -.vscode/** -.vscode-test/** -out/test/** -out/** -test/** -src/** -tsconfig.json -out/test/** -out/** -cgmanifest.json -yarn.lock -preview-src/** diff --git a/patched-vscode/extensions/sagemaker-idle-extension/CONTRIBUTING.md b/patched-vscode/extensions/sagemaker-idle-extension/CONTRIBUTING.md deleted file mode 100644 index 802e38a1..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/CONTRIBUTING.md +++ /dev/null @@ -1,40 +0,0 @@ -# Contributing to the Sagemaker Idle Extension -This guide explains how to make changes to this extension and test them. -## How to Make Changes -Changes to the sagemaker-idle-extension are managed by creating patches using the Quilt tool. Follow these steps: -1. Create a new patch: -``` -quilt new sagemaker-idle-extension-v.patch # Replace with the next version number -``` -2. Make changes and add file -``` -quilt add sagemaker-idle-extension-v.patch -``` -3. Refresh the patch to include your changes: -``` -quilt refresh -``` -4. Update the changes in the patched-vsode folder -5. Commit your changes and submit a pull request from your personal fork. - -## How to Test -1. Launch the Extension with Code Editor in local environment: - 1. Install Prerequisite tools described [here](https://web.archive.org/web/20231012223533/https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for your operating system. - 2. Run sh ./scripts/install.sh - 3. Run yarn watch from within the vscode folder - 4. Open a new terminal and run ./vscode/scripts/code-server.sh --launch - -2. Perform user activity such as file changes, cursor editor movements, and terminal activity -3. Verify `lastActiveTimestamp` is updated in the `.sagemaker-last-active-timestamp` file. -4. Verify that the `/api/idle` endpoint returns the same `lastActiveTimestamp. - -### Test Cases -1. File change Detectcion - - Modify a file and ensure the timestamp updates. - - Delete a file and verify the timestamp updates. -2. Text Editor Movement - - Move the text cursor within a file and check for timestamp updates. - - Switch between files and verify updates. -3. Terminal Activity - - Open a new terminal, run a command, and ensure the timestamp updates. - - Close the terminal and verify updates. diff --git a/patched-vscode/extensions/sagemaker-idle-extension/README.md b/patched-vscode/extensions/sagemaker-idle-extension/README.md deleted file mode 100644 index 1897c974..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Code Editor Idle Extension - -The Code Editor Idle Extension tracks user activity and logs the last active timestamp (in UTC) to a local file. User activities monitored include file changes, text editor selection changes, and terminal interactions. Additionally, it provides an API endpoint `/api/idle` that returns the lastActiveTimestamp. \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-idle-extension/extension-browser.webpack.config.js b/patched-vscode/extensions/sagemaker-idle-extension/extension-browser.webpack.config.js deleted file mode 100644 index 68271e0e..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/extension-browser.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright Amazon.com Inc. or its affiliates. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, - entry: { - extension: './src/extension.ts' - }, -}); diff --git a/patched-vscode/extensions/sagemaker-idle-extension/extension.webpack.config.js b/patched-vscode/extensions/sagemaker-idle-extension/extension.webpack.config.js deleted file mode 100644 index 59852626..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/extension.webpack.config.js +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright Amazon.com Inc. or its affiliates. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, - resolve: { - mainFields: ['module', 'main'] - }, - entry: { - extension: './src/extension.ts', - } -}); diff --git a/patched-vscode/extensions/sagemaker-idle-extension/package.json b/patched-vscode/extensions/sagemaker-idle-extension/package.json deleted file mode 100644 index 771c2155..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "sagemaker-idle-extension", - "displayName": "Sagemaker Idle Extension", - "description": "Expose an API called /idle that returns latest activity timestamp", - "extensionKind": [ - "workspace" - ], - "version": "1.0.0", - "publisher": "sagemaker", - "license": "MIT", - "engines": { - "vscode": "^1.70.0" - }, - "main": "./out/extension", - "categories": [ - "Other" - ], - "activationEvents": [ - "*" - ], - "capabilities": { - "virtualWorkspaces": true, - "untrustedWorkspaces": { - "supported": true - } - }, - "contributes": { - "configuration": { - "type": "object", - "title": "Sagemaker Idle Extension", - "properties": {} - }, - "commands": [] - }, - "scripts": { - "compile": "gulp compile-extension:sagemaker-idle-extension", - "watch": "npm run build-preview && gulp watch-extension:sagemaker-idle-extension", - "vscode:prepublish": "npm run build-ext", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-idle-extension ./tsconfig.json" - }, - "dependencies": {}, - "repository": {} -} diff --git a/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts deleted file mode 100644 index 0fbb35e6..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts +++ /dev/null @@ -1,116 +0,0 @@ -import * as vscode from "vscode"; -import * as fs from "fs"; -import * as path from "path"; - -let idleFilePath: string -let terminalActivityInterval: NodeJS.Timeout | undefined -const LOG_PREFIX = "[sagemaker-idle-extension]" -const CHECK_INTERVAL = 60000; // 60 seconds interval - -export function activate(context: vscode.ExtensionContext) { - initializeIdleFilePath(); - registerEventListeners(context); - startMonitoringTerminalActivity(); -} - -export function deactivate() { - if(terminalActivityInterval) { - clearInterval(terminalActivityInterval) - } -} - -/** - * Initializes the file path where the idle timestamp will be stored. - * It sets the path to a hidden file in the user's home directory. - */ -function initializeIdleFilePath() { - const homeDirectory = process.env.HOME || process.env.USERPROFILE; - if (!homeDirectory) { - console.log(`${LOG_PREFIX} Unable to determine the home directory.`); - return; - } - idleFilePath = path.join(homeDirectory, ".sagemaker-last-active-timestamp"); - - // Set initial lastActivetimestamp - updateLastActivityTimestamp() -} - -/** - * Registers event listeners to monitor user activity within the VSCode editor. - * It listens to document changes, editor focus changes, text selection changes, and terminal events. - * @param context - The context in which the extension is running. - */ -function registerEventListeners(context: vscode.ExtensionContext) { - context.subscriptions.push( - vscode.workspace.onDidChangeTextDocument((_) => { - updateLastActivityTimestamp(); - }), - vscode.window.onDidChangeActiveTextEditor((_) => { - updateLastActivityTimestamp(); - }), - vscode.window.onDidChangeTextEditorSelection((_) => { - updateLastActivityTimestamp(); - }), - vscode.window.onDidOpenTerminal((_) => { - updateLastActivityTimestamp(); - }), - vscode.window.onDidCloseTerminal((_) => { - updateLastActivityTimestamp(); - }) - ); -} - -/** - * Starts monitoring terminal activity by setting an interval to check for activity in the /dev/pts directory. - */ -const startMonitoringTerminalActivity = () => { - terminalActivityInterval = setInterval(checkTerminalActivity, CHECK_INTERVAL); -}; - - -/** - * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. - * - * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. - * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. - * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, - * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification - * times of the files in the /dev/pts directory, we can detect terminal activity. - * - * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function - * updates the last activity timestamp. - */ -const checkTerminalActivity = () => { - fs.readdir("/dev/pts", (err, files) => { - if (err) { - console.error(`${LOG_PREFIX} Error reading /dev/pts directory:`, err); - return; - } - - const now = Date.now(); - const activityDetected = files.some((file) => { - const filePath = path.join("/dev/pts", file); - try { - const stats = fs.statSync(filePath); - const mtime = new Date(stats.mtime).getTime(); - return now - mtime < CHECK_INTERVAL; - } catch (error) { - console.error(`${LOG_PREFIX}}Error reading file stats:`, error); - return false; - } - }); - - if (activityDetected) { - updateLastActivityTimestamp(); - } - }); -}; - - /** - * Updates the last activity timestamp by recording the current timestamp in the idle file and - * refreshing the status bar. The timestamp should be in ISO 8601 format and set to the UTC timezone. - */ -function updateLastActivityTimestamp() { - const timestamp = new Date().toISOString(); - fs.writeFileSync(idleFilePath, timestamp); -} \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-idle-extension/tsconfig.json b/patched-vscode/extensions/sagemaker-idle-extension/tsconfig.json deleted file mode 100644 index 95397494..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": "./out" - }, - "include": [ - "../sagemaker-idle-extension/src/**/*", - "../../src/vscode-dts/vscode.d.ts" - ] -} diff --git a/patched-vscode/extensions/sagemaker-idle-extension/yarn.lock b/patched-vscode/extensions/sagemaker-idle-extension/yarn.lock deleted file mode 100644 index fb57ccd1..00000000 --- a/patched-vscode/extensions/sagemaker-idle-extension/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - diff --git a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/.vscodeignore b/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/.vscodeignore deleted file mode 100644 index 56b78554..00000000 --- a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/.vscodeignore +++ /dev/null @@ -1,12 +0,0 @@ -.vscode/** -.vscode-test/** -out/test/** -out/** -test/** -src/** -tsconfig.json -out/test/** -out/** -cgmanifest.json -yarn.lock -preview-src/** diff --git a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/README.md b/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/README.md deleted file mode 100644 index 70b42171..00000000 --- a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Terminal Crash Mitigation -This extension addresses a critical issue where terminals fail to open. As of August 9, 2024, the root cause remains unidentified. The extension works by monitoring the creation of new terminals and detects if a terminal closes within 1 second of being opened. When this condition is met, it assumes the issue has occurred and attempts to mitigate it by terminating any background terminal processes. However, it will not terminate any terminal processes if there is an active terminal in the UI. \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/extension-browser.webpack.config.js b/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/extension-browser.webpack.config.js deleted file mode 100644 index 68271e0e..00000000 --- a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/extension-browser.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright Amazon.com Inc. or its affiliates. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, - entry: { - extension: './src/extension.ts' - }, -}); diff --git a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/extension.webpack.config.js b/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/extension.webpack.config.js deleted file mode 100644 index 59852626..00000000 --- a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/extension.webpack.config.js +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright Amazon.com Inc. or its affiliates. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, - resolve: { - mainFields: ['module', 'main'] - }, - entry: { - extension: './src/extension.ts', - } -}); diff --git a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/package.json b/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/package.json deleted file mode 100644 index 2523040e..00000000 --- a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "sagemaker-terminal-crash-mitigation", - "displayName": "Sagemaker terminal crash mitigation", - "description": "Mitgate issue where the terminal crashes when trying to open on app startup", - "extensionKind": [ - "workspace" - ], - "version": "1.0.0", - "publisher": "sagemaker", - "license": "MIT", - "engines": { - "vscode": "^1.70.0" - }, - "main": "./out/extension", - "categories": [ - "Other" - ], - "activationEvents": [ - "*" - ], - "capabilities": { - "virtualWorkspaces": true, - "untrustedWorkspaces": { - "supported": true - } - }, - "contributes": { - "configuration": { - "type": "object", - "title": "Sagemaker Idle Extension", - "properties": {} - }, - "commands": [] - }, - "scripts": { - "compile": "gulp compile-extension:sagemaker-terminal-crash-mitigation", - "watch": "npm run build-preview && gulp watch-extension:sagemaker-terminal-crash-mitigation", - "vscode:prepublish": "npm run build-ext", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-terminal-crash-mitigation ./tsconfig.json" - }, - "dependencies": {}, - "repository": {} -} diff --git a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/src/extension.ts b/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/src/extension.ts deleted file mode 100644 index 425c707c..00000000 --- a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/src/extension.ts +++ /dev/null @@ -1,103 +0,0 @@ -import * as vscode from 'vscode'; -import { exec } from 'child_process'; - -const logPrefix = '[sagemaker-terminal-crash-mitigation]'; - -export function activate(_context: vscode.ExtensionContext) { - let lastTerminal: vscode.Terminal | undefined; - let lastProcessId: number | undefined; - let lastOpenedTime: number | undefined; - - /** - * Event listener for when a new terminal is opened. - * Tracks the terminal's process ID and the time it was opened. - */ - vscode.window.onDidOpenTerminal(async terminal => { - lastTerminal = terminal; - lastOpenedTime = Date.now(); - try { - lastProcessId = await terminal.processId; - console.log(`${logPrefix} Terminal opened: PID ${lastProcessId}, Time: ${lastOpenedTime}`); - } catch (error) { - console.error(`${logPrefix} Error getting process ID: ${error}`); - } - }); - - /** - * Event listener for when a terminal is closed. - * Checks if the closed terminal is the one that was last opened, - * and if it closed within 1 second. If no other terminals are active, - * executes a command to kill all bash processes. - */ - vscode.window.onDidCloseTerminal(async terminal => { - if (lastTerminal && lastProcessId && lastOpenedTime) { - try { - const currentProcessId = await terminal.processId; - console.log(`${logPrefix} Terminal closed: PID ${currentProcessId}`); - - if (currentProcessId === lastProcessId) { - const timeElapsed = Date.now() - lastOpenedTime; - console.log(`${logPrefix} Time elapsed since opening: ${timeElapsed}ms`); - - if (timeElapsed < 1000) { - const remainingTerminals = vscode.window.terminals.length; - console.log(`${logPrefix} Number of remaining terminals: ${remainingTerminals}`); - - if (remainingTerminals === 0) { - console.log(`${logPrefix} No other active terminals. Executing kill command.`); - execKillCommand(); - } else { - console.log(`${logPrefix} There are other active terminals. Kill command not executed.`); - } - } else { - console.log(`${logPrefix} Terminal closed after 1 second. No action taken.`); - } - } else { - console.log(`${logPrefix} Closed terminal PID does not match last opened terminal PID. No action taken.`); - } - } catch (error) { - console.error(`${logPrefix} Error getting process ID on close: ${error}`); - } - } - }); -} - - -/** - * Executes the command to kill all bash processes. - * Fetches all bash process IDs and sends a `kill -9` signal to each one. - */ -function execKillCommand() { - exec("ps -eo pid,comm | grep bash | awk '{print $1}'", (error, stdout, stderr) => { - if (error) { - console.error(`${logPrefix} Error fetching bash PIDs: ${error.message}`); - return; - } - if (stderr) { - console.error(`${logPrefix} Error in command output: ${stderr}`); - return; - } - - const pids = stdout.trim().split('\n').filter(pid => pid); - if (pids.length === 0) { - console.log(`${logPrefix} No bash processes found to kill.`); - return; - } - - pids.forEach(pid => { - exec(`kill -9 ${pid}`, (killError, _killStdout, killStderr) => { - if (killError) { - console.error(`${logPrefix} Error killing PID ${pid}: ${killError.message}`); - return; - } - if (killStderr) { - console.error(`${logPrefix} Error output while killing PID ${pid}: ${killStderr}`); - return; - } - console.log(`${logPrefix} Killed bash process with PID ${pid}.`); - }); - }); - }); -} - -export function deactivate() {} diff --git a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/tsconfig.json b/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/tsconfig.json deleted file mode 100644 index aea6249f..00000000 --- a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": "./out" - }, - "include": [ - "../sagemaker-terminal-crash-mitigation/src/**/*", - "../../src/vscode-dts/vscode.d.ts" - ] -} diff --git a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/yarn.lock b/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/yarn.lock deleted file mode 100644 index fb57ccd1..00000000 --- a/patched-vscode/extensions/sagemaker-terminal-crash-mitigation/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - diff --git a/patched-vscode/extensions/shellscript/cgmanifest.json b/patched-vscode/extensions/shellscript/cgmanifest.json index 48f939ec..73c65b96 100644 --- a/patched-vscode/extensions/shellscript/cgmanifest.json +++ b/patched-vscode/extensions/shellscript/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jeff-hykin/better-shell-syntax", "repositoryUrl": "https://github.com/jeff-hykin/better-shell-syntax", - "commitHash": "6d0bc37a6b8023a5fddf75bd2b4eb1e1f962e4c2" + "commitHash": "35020b0bd79a90d3b262b4c13a8bb0b33adc1f45" } }, "license": "MIT", diff --git a/patched-vscode/extensions/shellscript/package.json b/patched-vscode/extensions/shellscript/package.json index 6f9e7072..93333abd 100644 --- a/patched-vscode/extensions/shellscript/package.json +++ b/patched-vscode/extensions/shellscript/package.json @@ -34,6 +34,7 @@ ".bash_profile", ".bash_login", ".ebuild", + ".eclass", ".profile", ".bash_logout", ".xprofile", diff --git a/patched-vscode/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json b/patched-vscode/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json index 7aae970d..255638d7 100644 --- a/patched-vscode/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json +++ b/patched-vscode/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jeff-hykin/better-shell-syntax/commit/6d0bc37a6b8023a5fddf75bd2b4eb1e1f962e4c2", + "version": "https://github.com/jeff-hykin/better-shell-syntax/commit/35020b0bd79a90d3b262b4c13a8bb0b33adc1f45", "name": "Shell Script", "scopeName": "source.shell", "patterns": [ @@ -1547,7 +1547,7 @@ "include": "#subshell_dollar" }, { - "begin": "(? ({ number: t.remoteAddress.port, privacy: t.privacy })); + const ports = [...this.tunnels].map(t => ({ number: t.remoteAddress.port, privacy: t.privacy, protocol: t.protocol })); this.state.process.stdin.write(`${JSON.stringify(ports)}\n`); if (ports.length === 0 && !this.state.cleanupTimeout) { diff --git a/patched-vscode/extensions/typescript-basics/snippets/typescript.code-snippets b/patched-vscode/extensions/typescript-basics/snippets/typescript.code-snippets index 35b2aa17..9ed69579 100644 --- a/patched-vscode/extensions/typescript-basics/snippets/typescript.code-snippets +++ b/patched-vscode/extensions/typescript-basics/snippets/typescript.code-snippets @@ -163,7 +163,7 @@ "For-Of Loop": { "prefix": "forof", "body": [ - "for (const ${1:iterator} of ${2:object}) {", + "for (const ${1:element} of ${2:object}) {", "\t$TM_SELECTED_TEXT$0", "}" ], @@ -172,7 +172,7 @@ "For-Await-Of Loop": { "prefix": "forawaitof", "body": [ - "for await (const ${1:iterator} of ${2:object}) {", + "for await (const ${1:element} of ${2:object}) {", "\t$TM_SELECTED_TEXT$0", "}" ], @@ -266,6 +266,15 @@ ], "description": "Set Timeout Function" }, + "Set Interval Function": { + "prefix": "setinterval", + "body": [ + "setInterval(() => {", + "\t$TM_SELECTED_TEXT$0", + "}, ${1:interval});" + ], + "description": "Set Interval Function" + }, "Region Start": { "prefix": "#region", "body": [ diff --git a/patched-vscode/extensions/typescript-language-features/package.json b/patched-vscode/extensions/typescript-language-features/package.json index 4c8a54d8..e93299d8 100644 --- a/patched-vscode/extensions/typescript-language-features/package.json +++ b/patched-vscode/extensions/typescript-language-features/package.json @@ -1030,6 +1030,22 @@ "markdownDescription": "%typescript.preferences.autoImportFileExcludePatterns%", "scope": "resource" }, + "typescript.preferences.autoImportSpecifierExcludeRegexes": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%typescript.preferences.autoImportSpecifierExcludeRegexes%", + "scope": "resource" + }, + "javascript.preferences.autoImportSpecifierExcludeRegexes": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%typescript.preferences.autoImportSpecifierExcludeRegexes%", + "scope": "resource" + }, "typescript.preferences.preferTypeOnlyAutoImports": { "type": "boolean", "default": false, @@ -1074,6 +1090,146 @@ "description": "%typescript.preferences.renameMatchingJsxTags%", "scope": "language-overridable" }, + "typescript.preferences.organizeImports": { + "type": "object", + "markdownDescription": "%typescript.preferences.organizeImports%", + "properties": { + "caseSensitivity": { + "type": "string", + "enum": [ + "auto", + "caseInsensitive", + "caseSensitive" + ], + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.caseSensitivity.auto%", + "%typescript.preferences.organizeImports.caseSensitivity.insensitive", + "%typescript.preferences.organizeImports.caseSensitivity.sensitive%" + ], + "default": "auto" + }, + "typeOrder": { + "type": "string", + "enum": [ + "auto", + "last", + "inline", + "first" + ], + "default": "auto", + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.typeOrder.auto%", + "%typescript.preferences.organizeImports.typeOrder.last%", + "%typescript.preferences.organizeImports.typeOrder.inline%", + "%typescript.preferences.organizeImports.typeOrder.first%" + ] + }, + "unicodeCollation": { + "type": "string", + "enum": [ + "ordinal", + "unicode" + ], + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.unicodeCollation.ordinal%", + "%typescript.preferences.organizeImports.unicodeCollation.unicode%" + ], + "default": "ordinal" + }, + "locale": { + "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.locale%" + }, + "numericCollation": { + "type": "boolean", + "markdownDescription": "%typescript.preferences.organizeImports.numericCollation%" + }, + "accentCollation":{ + "type": "boolean", + "markdownDescription": "%typescript.preferences.organizeImports.accentCollation%" + }, + "caseFirst": { + "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.caseFirst%", + "enum": [ + "default", + "upper", + "lower" + ], + "default": "default" + } + } + }, + "javascript.preferences.organizeImports": { + "type": "object", + "markdownDescription": "%typescript.preferences.organizeImports%", + "properties": { + "caseSensitivity": { + "type": "string", + "enum": [ + "auto", + "caseInsensitive", + "caseSensitive" + ], + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.caseSensitivity.auto%", + "%typescript.preferences.organizeImports.caseSensitivity.insensitive", + "%typescript.preferences.organizeImports.caseSensitivity.sensitive%" + ], + "default": "auto" + }, + "typeOrder": { + "type": "string", + "enum": [ + "auto", + "last", + "inline", + "first" + ], + "default": "auto", + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.typeOrder.auto%", + "%typescript.preferences.organizeImports.typeOrder.last%", + "%typescript.preferences.organizeImports.typeOrder.inline%", + "%typescript.preferences.organizeImports.typeOrder.first%" + ] + }, + "unicodeCollation": { + "type": "string", + "enum": [ + "ordinal", + "unicode" + ], + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.unicodeCollation.ordinal%", + "%typescript.preferences.organizeImports.unicodeCollation.unicode%" + ], + "default": "ordinal" + }, + "locale": { + "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.locale%" + }, + "numericCollation": { + "type": "boolean", + "markdownDescription": "%typescript.preferences.organizeImports.numericCollation%" + }, + "accentCollation":{ + "type": "boolean", + "markdownDescription": "%typescript.preferences.organizeImports.accentCollation%" + }, + "caseFirst": { + "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.caseFirst%", + "enum": [ + "default", + "upper", + "lower" + ], + "default": "default" + } + } + }, "typescript.updateImportsOnFileMove.enabled": { "type": "string", "enum": [ @@ -1282,13 +1438,13 @@ }, "typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors": { "type": "boolean", - "default": true, + "default": false, "description": "%configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors%", "scope": "window" }, "typescript.tsserver.web.typeAcquisition.enabled": { "type": "boolean", - "default": false, + "default": true, "description": "%configuration.tsserver.web.typeAcquisition.enabled%", "scope": "window" }, @@ -1315,15 +1471,21 @@ "markdownDescription": "%typescript.workspaceSymbols.excludeLibrarySymbols%", "scope": "window" }, + "typescript.tsserver.enableRegionDiagnostics": { + "type": "boolean", + "default": true, + "description": "%typescript.tsserver.enableRegionDiagnostics%", + "scope": "window" + }, "javascript.experimental.updateImportsOnPaste": { - "scope": "resource", + "scope": "window", "type": "boolean", "default": false, "description": "%configuration.updateImportsOnPaste%", "tags": ["experimental"] }, "typescript.experimental.updateImportsOnPaste": { - "scope": "resource", + "scope": "window", "type": "boolean", "default": false, "description": "%configuration.updateImportsOnPaste%", @@ -1472,23 +1634,8 @@ "editor/context": [ { "command": "typescript.goToSourceDefinition", - "when": "tsSupportsSourceDefinition && resourceLangId == typescript", - "group": "navigation@9" - }, - { - "command": "typescript.goToSourceDefinition", - "when": "tsSupportsSourceDefinition && resourceLangId == typescriptreact", - "group": "navigation@9" - }, - { - "command": "typescript.goToSourceDefinition", - "when": "tsSupportsSourceDefinition && resourceLangId == javascript", - "group": "navigation@9" - }, - { - "command": "typescript.goToSourceDefinition", - "when": "tsSupportsSourceDefinition && resourceLangId == javascriptreact", - "group": "navigation@9" + "when": "tsSupportsSourceDefinition && (resourceLangId == typescript || resourceLangId == typescriptreact || resourceLangId == javascript || resourceLangId == javascriptreact)", + "group": "navigation@1.41" } ], "explorer/context": [ diff --git a/patched-vscode/extensions/typescript-language-features/package.nls.json b/patched-vscode/extensions/typescript-language-features/package.nls.json index 4f276905..d2a0ca89 100644 --- a/patched-vscode/extensions/typescript-language-features/package.nls.json +++ b/patched-vscode/extensions/typescript-language-features/package.nls.json @@ -16,6 +16,7 @@ "typescript.tsserver.pluginPaths": "Additional paths to discover TypeScript Language Service plugins.", "typescript.tsserver.pluginPaths.item": "Either an absolute or relative path. Relative path will be resolved against workspace folder(s).", "typescript.tsserver.trace": "Enables tracing of messages sent to the TS server. This trace can be used to diagnose TS Server issues. The trace may contain file paths, source code, and other potentially sensitive information from your project.", + "typescript.tsserver.enableRegionDiagnostics": "Enables region-based diagnostics in TypeScript. Requires using TypeScript 5.6+ in the workspace.", "typescript.validate.enable": "Enable/disable TypeScript validation.", "typescript.format.enable": "Enable/disable default TypeScript formatter.", "javascript.format.enable": "Enable/disable default JavaScript formatter.", @@ -154,6 +155,7 @@ "typescript.preferences.includePackageJsonAutoImports.on": "Always search dependencies.", "typescript.preferences.includePackageJsonAutoImports.off": "Never search dependencies.", "typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Relative paths are resolved relative to the workspace root. Patterns are evaluated using tsconfig.json [`exclude`](https://www.typescriptlang.org/tsconfig#exclude) semantics.", + "typescript.preferences.autoImportSpecifierExcludeRegexes": "Specify regular expressions to exclude auto imports with matching import specifiers. Examples:\n\n- `^node:`\n- `lib/internal` (slashes don't need to be escaped...)\n- `/lib\\/internal/i` (...unless including surrounding slashes for `i` or `u` flags)\n- `^lodash$` (only allow subpath imports from lodash)", "typescript.preferences.preferTypeOnlyAutoImports": "Include the `type` keyword in auto-imports whenever possible. Requires using TypeScript 5.3+ in the workspace.", "typescript.workspaceSymbols.excludeLibrarySymbols": "Exclude symbols that come from library files in Go to Symbol in Workspace results. Requires using TypeScript 5.3+ in the workspace.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", @@ -186,6 +188,21 @@ "typescript.preferences.renameShorthandProperties.deprecationMessage": "The setting 'typescript.preferences.renameShorthandProperties' has been deprecated in favor of 'typescript.preferences.useAliasesForRenames'", "typescript.preferences.useAliasesForRenames": "Enable/disable introducing aliases for object shorthand properties during renames.", "typescript.preferences.renameMatchingJsxTags": "When on a JSX tag, try to rename the matching tag instead of renaming the symbol. Requires using TypeScript 5.1+ in the workspace.", + "typescript.preferences.organizeImports": "Advanced preferences that control how imports are ordered.", + "javascript.preferences.organizeImports": "Advanced preferences that control how imports are ordered.", + "typescript.preferences.organizeImports.caseSensitivity.auto": "Detect case-sensitivity for import sorting.", + "typescript.preferences.organizeImports.caseSensitivity.insensitive": "Sort imports case-insensitively.", + "typescript.preferences.organizeImports.caseSensitivity.sensitive": "Sort imports case-sensitively.", + "typescript.preferences.organizeImports.typeOrder.auto": "Detect where type-only named imports should be sorted.", + "typescript.preferences.organizeImports.typeOrder.last": "Type only named imports are sorted to the end of the import list.", + "typescript.preferences.organizeImports.typeOrder.inline": "Named imports are sorted by name only.", + "typescript.preferences.organizeImports.typeOrder.first": "Type only named imports are sorted to the end of the import list.", + "typescript.preferences.organizeImports.unicodeCollation.ordinal": "Sort imports using the numeric value of each code point.", + "typescript.preferences.organizeImports.unicodeCollation.unicode": "Sort imports using the Unicode code collation.", + "typescript.preferences.organizeImports.locale": "Overrides the locale used for collation. Specify `auto` to use the UI locale. Only applies to `organizeImportsCollation: 'unicode'`.", + "typescript.preferences.organizeImports.caseFirst": "Indicates whether upper-case comes before lower-case. Only applies to `organizeImportsCollation: 'unicode'`.", + "typescript.preferences.organizeImports.numericCollation": "Sort numeric strings by integer value.", + "typescript.preferences.organizeImports.accentCollation": "Compare characters with diacritical marks as unequal to base character.", "typescript.workspaceSymbols.scope": "Controls which files are searched by [Go to Symbol in Workspace](https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name).", "typescript.workspaceSymbols.scope.allOpenProjects": "Search all open JavaScript or TypeScript projects for symbols.", "typescript.workspaceSymbols.scope.currentProject": "Only search for symbols in the current JavaScript or TypeScript project.", @@ -219,7 +236,7 @@ "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors on web even when project wide IntelliSense is enabled. This is always on when project wide IntelliSense is not enabled or available. See `#typescript.tsserver.web.projectWideIntellisense.enabled#`", "configuration.tsserver.web.typeAcquisition.enabled": "Enable/disable package acquisition on the web. This enables IntelliSense for imported packages. Requires `#typescript.tsserver.web.projectWideIntellisense.enabled#`. Currently not supported for Safari.", "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or 'node' if you want VS Code to detect a Node installation.", - "configuration.updateImportsOnPaste": "Automatically update imports when pasting code. Requires TypeScript 5.5+.", + "configuration.updateImportsOnPaste": "Automatically update imports when pasting code. Requires TypeScript 5.6+.", "walkthroughs.nodejsWelcome.title": "Get started with JavaScript and Node.js", "walkthroughs.nodejsWelcome.description": "Make the most of Visual Studio Code's first-class JavaScript experience.", "walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.title": "Install Node.js", diff --git a/patched-vscode/extensions/typescript-language-features/schemas/package.schema.json b/patched-vscode/extensions/typescript-language-features/schemas/package.schema.json index c135ea39..1cd2b1ed 100644 --- a/patched-vscode/extensions/typescript-language-features/schemas/package.schema.json +++ b/patched-vscode/extensions/typescript-language-features/schemas/package.schema.json @@ -1,6 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TypeScript contributions to package.json", "type": "object", "properties": { "contributes": { diff --git a/patched-vscode/extensions/typescript-language-features/src/configuration/configuration.ts b/patched-vscode/extensions/typescript-language-features/src/configuration/configuration.ts index a08ca921..554fd4dd 100644 --- a/patched-vscode/extensions/typescript-language-features/src/configuration/configuration.ts +++ b/patched-vscode/extensions/typescript-language-features/src/configuration/configuration.ts @@ -124,6 +124,7 @@ export interface TypeScriptServiceConfiguration { readonly localNodePath: string | null; readonly globalNodePath: string | null; readonly workspaceSymbolsExcludeLibrarySymbols: boolean; + readonly enableRegionDiagnostics: boolean; } export function areServiceConfigurationsEqual(a: TypeScriptServiceConfiguration, b: TypeScriptServiceConfiguration): boolean { @@ -162,6 +163,7 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu localNodePath: this.readLocalNodePath(configuration), globalNodePath: this.readGlobalNodePath(configuration), workspaceSymbolsExcludeLibrarySymbols: this.readWorkspaceSymbolsExcludeLibrarySymbols(configuration), + enableRegionDiagnostics: this.readEnableRegionDiagnostics(configuration), }; } @@ -261,10 +263,14 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu } private readWebProjectWideIntellisenseSuppressSemanticErrors(configuration: vscode.WorkspaceConfiguration): boolean { - return configuration.get('typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors', true); + return this.readWebTypeAcquisition(configuration) && configuration.get('typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors', false); } private readWebTypeAcquisition(configuration: vscode.WorkspaceConfiguration): boolean { - return configuration.get('typescript.tsserver.web.typeAcquisition.enabled', false); + return configuration.get('typescript.tsserver.web.typeAcquisition.enabled', true); + } + + private readEnableRegionDiagnostics(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('typescript.tsserver.enableRegionDiagnostics', true); } } diff --git a/patched-vscode/extensions/typescript-language-features/src/extension.browser.ts b/patched-vscode/extensions/typescript-language-features/src/extension.browser.ts index 2f6bd212..9ad0867e 100644 --- a/patched-vscode/extensions/typescript-language-features/src/extension.browser.ts +++ b/patched-vscode/extensions/typescript-language-features/src/extension.browser.ts @@ -11,8 +11,7 @@ import { registerBaseCommands } from './commands/index'; import { TypeScriptServiceConfiguration } from './configuration/configuration'; import { BrowserServiceConfigurationProvider } from './configuration/configuration.browser'; import { ExperimentationTelemetryReporter, IExperimentationTelemetryReporter } from './experimentTelemetryReporter'; -import { AutoInstallerFs } from './filesystems/autoInstallerFs'; -import { MemFs } from './filesystems/memFs'; +import { registerAtaSupport } from './filesystems/ata'; import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost'; import { Logger } from './logging/logger'; import RemoteRepositories from './remoteRepositories.browser'; @@ -25,7 +24,7 @@ import { ITypeScriptVersionProvider, TypeScriptVersion, TypeScriptVersionSource import { ActiveJsTsEditorTracker } from './ui/activeJsTsEditorTracker'; import { Disposable } from './utils/dispose'; import { getPackageInfo } from './utils/packageInfo'; -import { isWebAndHasSharedArrayBuffers, supportsReadableByteStreams } from './utils/platform'; +import { isWebAndHasSharedArrayBuffers } from './utils/platform'; class StaticVersionProvider implements ITypeScriptVersionProvider { @@ -62,7 +61,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { new TypeScriptVersion( TypeScriptVersionSource.Bundled, vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(), - API.fromSimpleString('5.4.5'))); + API.fromSimpleString('5.5.4'))); let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined; const packageInfo = getPackageInfo(context); @@ -102,16 +101,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { await startPreloadWorkspaceContentsIfNeeded(context, logger); })); - if (supportsReadableByteStreams()) { - context.subscriptions.push(vscode.workspace.registerFileSystemProvider('vscode-global-typings', new MemFs(), { - isCaseSensitive: true, - isReadonly: false - })); - context.subscriptions.push(vscode.workspace.registerFileSystemProvider('vscode-node-modules', new AutoInstallerFs(), { - isCaseSensitive: true, - isReadonly: false - })); - } + context.subscriptions.push(registerAtaSupport(logger)); return getExtensionApi(onCompletionAccepted.event, pluginManager); } diff --git a/patched-vscode/extensions/typescript-language-features/src/filesystems/ata.ts b/patched-vscode/extensions/typescript-language-features/src/filesystems/ata.ts new file mode 100644 index 00000000..b5e43244 --- /dev/null +++ b/patched-vscode/extensions/typescript-language-features/src/filesystems/ata.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { conditionalRegistration, requireGlobalConfiguration } from '../languageFeatures/util/dependentRegistration'; +import { supportsReadableByteStreams } from '../utils/platform'; +import { AutoInstallerFs } from './autoInstallerFs'; +import { MemFs } from './memFs'; +import { Logger } from '../logging/logger'; + +export function registerAtaSupport(logger: Logger): vscode.Disposable { + if (!supportsReadableByteStreams()) { + return vscode.Disposable.from(); + } + + return conditionalRegistration([ + requireGlobalConfiguration('typescript', 'tsserver.web.typeAcquisition.enabled'), + ], () => { + return vscode.Disposable.from( + // Ata + vscode.workspace.registerFileSystemProvider('vscode-global-typings', new MemFs('global-typings', logger), { + isCaseSensitive: true, + isReadonly: false, + }), + + // Read accesses to node_modules + vscode.workspace.registerFileSystemProvider('vscode-node-modules', new AutoInstallerFs(logger), { + isCaseSensitive: true, + isReadonly: false + })); + }); +} diff --git a/patched-vscode/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts b/patched-vscode/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts index 4e69fce8..d639b7fe 100644 --- a/patched-vscode/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts +++ b/patched-vscode/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts @@ -3,50 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { PackageManager } from '@vscode/ts-package-manager'; +import { basename, join } from 'path'; import * as vscode from 'vscode'; -import { MemFs } from './memFs'; import { URI } from 'vscode-uri'; -import { PackageManager, FileSystem, packagePath } from '@vscode/ts-package-manager'; -import { join, basename, dirname } from 'path'; +import { Disposable } from '../utils/dispose'; +import { MemFs } from './memFs'; +import { Logger } from '../logging/logger'; const TEXT_DECODER = new TextDecoder('utf-8'); const TEXT_ENCODER = new TextEncoder(); -export class AutoInstallerFs implements vscode.FileSystemProvider { - - private readonly memfs = new MemFs(); - private readonly fs: FileSystem; - private readonly projectCache = new Map>(); - private readonly watcher: vscode.FileSystemWatcher; - private readonly _emitter = new vscode.EventEmitter(); - - readonly onDidChangeFile: vscode.Event = this._emitter.event; - - constructor() { - this.watcher = vscode.workspace.createFileSystemWatcher('**/{package.json,package-lock.json,package-lock.kdl}'); - const handler = (uri: URI) => { - const root = dirname(uri.path); - if (this.projectCache.delete(root)) { - (async () => { - const pm = new PackageManager(this.fs); - const opts = await this.getInstallOpts(uri, root); - const proj = await pm.resolveProject(root, opts); - proj.pruneExtraneous(); - // TODO: should this fire on vscode-node-modules instead? - // NB(kmarchan): This should tell TSServer that there's - // been changes inside node_modules and it needs to - // re-evaluate things. - this._emitter.fire([{ - type: vscode.FileChangeType.Changed, - uri: uri.with({ path: join(root, 'node_modules') }) - }]); - })(); - } - }; - this.watcher.onDidChange(handler); - this.watcher.onDidCreate(handler); - this.watcher.onDidDelete(handler); - const memfs = this.memfs; +export class AutoInstallerFs extends Disposable implements vscode.FileSystemProvider { + + private readonly memfs: MemFs; + private readonly packageManager: PackageManager; + private readonly _projectCache = new Map | undefined>(); + + private readonly _emitter = this._register(new vscode.EventEmitter()); + readonly onDidChangeFile = this._emitter.event; + + constructor( + private readonly logger: Logger + ) { + super(); + + const memfs = new MemFs('auto-installer', logger); + this.memfs = memfs; memfs.onDidChangeFile((e) => { this._emitter.fire(e.map(ev => ({ type: ev.type, @@ -54,7 +37,8 @@ export class AutoInstallerFs implements vscode.FileSystemProvider { uri: ev.uri.with({ scheme: 'memfs' }) }))); }); - this.fs = { + + this.packageManager = new PackageManager({ readDirectory(path: string, _extensions?: readonly string[], _exclude?: readonly string[], _include?: readonly string[], _depth?: number): string[] { return memfs.readDirectory(URI.file(path)).map(([name, _]) => name); }, @@ -87,17 +71,17 @@ export class AutoInstallerFs implements vscode.FileSystemProvider { return undefined; } } - }; + }); } watch(resource: vscode.Uri): vscode.Disposable { - const mapped = URI.file(new MappedUri(resource).path); - console.log('watching', mapped); - return this.memfs.watch(mapped); + this.logger.trace(`AutoInstallerFs.watch. Resource: ${resource.toString()}}`); + return this.memfs.watch(resource); } async stat(uri: vscode.Uri): Promise { - // console.log('stat', uri.toString()); + this.logger.trace(`AutoInstallerFs.stat: ${uri}`); + const mapped = new MappedUri(uri); // TODO: case sensitivity configuration @@ -119,7 +103,8 @@ export class AutoInstallerFs implements vscode.FileSystemProvider { } async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { - // console.log('readDirectory', uri.toString()); + this.logger.trace(`AutoInstallerFs.readDirectory: ${uri}`); + const mapped = new MappedUri(uri); await this.ensurePackageContents(mapped); @@ -127,7 +112,8 @@ export class AutoInstallerFs implements vscode.FileSystemProvider { } async readFile(uri: vscode.Uri): Promise { - // console.log('readFile', uri.toString()); + this.logger.trace(`AutoInstallerFs.readFile: ${uri}`); + const mapped = new MappedUri(uri); await this.ensurePackageContents(mapped); @@ -151,8 +137,6 @@ export class AutoInstallerFs implements vscode.FileSystemProvider { } private async ensurePackageContents(incomingUri: MappedUri): Promise { - // console.log('ensurePackageContents', incomingUri.path); - // If we're not looking for something inside node_modules, bail early. if (!incomingUri.path.includes('node_modules')) { throw vscode.FileSystemError.FileNotFound(); @@ -163,34 +147,37 @@ export class AutoInstallerFs implements vscode.FileSystemProvider { throw vscode.FileSystemError.FileNotFound(); } - const root = this.getProjectRoot(incomingUri.path); - - const pkgPath = packagePath(incomingUri.path); - if (!root || this.projectCache.get(root)?.has(pkgPath)) { + const root = await this.getProjectRoot(incomingUri.original); + if (!root) { return; } - const proj = await (new PackageManager(this.fs)).resolveProject(root, await this.getInstallOpts(incomingUri.original, root)); + this.logger.trace(`AutoInstallerFs.ensurePackageContents. Path: ${incomingUri.path}, Root: ${root}`); - const restore = proj.restorePackageAt(incomingUri.path); - try { - await restore; - } catch (e) { - console.error(`failed to restore package at ${incomingUri.path}: `, e); - throw e; - } - if (!this.projectCache.has(root)) { - this.projectCache.set(root, new Set()); + const existingInstall = this._projectCache.get(root); + if (existingInstall) { + this.logger.trace(`AutoInstallerFs.ensurePackageContents. Found ongoing install for: ${root}/node_modules`); + return existingInstall; } - this.projectCache.get(root)!.add(pkgPath); + + const installing = (async () => { + const proj = await this.packageManager.resolveProject(root, await this.getInstallOpts(incomingUri.original, root)); + try { + await proj.restore(); + } catch (e) { + console.error(`failed to restore package at ${incomingUri.path}: `, e); + throw e; + } + })(); + this._projectCache.set(root, installing); + await installing; } private async getInstallOpts(originalUri: URI, root: string) { const vsfs = vscode.workspace.fs; - let pkgJson; - try { - pkgJson = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package.json') }))); - } catch (e) { } + + // We definitely need a package.json to be there. + const pkgJson = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package.json') }))); let kdlLock; try { @@ -209,13 +196,20 @@ export class AutoInstallerFs implements vscode.FileSystemProvider { }; } - private getProjectRoot(path: string): string | undefined { - const pkgPath = path.match(/(^.*)\/node_modules/); - return pkgPath?.[1]; + private async getProjectRoot(incomingUri: URI): Promise { + const vsfs = vscode.workspace.fs; + const pkgPath = incomingUri.path.match(/^(.*?)\/node_modules/); + const ret = pkgPath?.[1]; + if (!ret) { + return; + } + try { + await vsfs.stat(incomingUri.with({ path: join(ret, 'package.json') })); + return ret; + } catch (e) { + return; + } } - - // --- manage file events - } class MappedUri { @@ -227,7 +221,7 @@ class MappedUri { const parts = uri.path.match(/^\/([^\/]+)\/([^\/]*)(?:\/(.+))?$/); if (!parts) { - throw new Error(`Invalid path: ${uri.path}`); + throw new Error(`Invalid uri: ${uri.toString()}, ${uri.path}`); } const scheme = parts[1]; diff --git a/patched-vscode/extensions/typescript-language-features/src/filesystems/memFs.ts b/patched-vscode/extensions/typescript-language-features/src/filesystems/memFs.ts index eeeb60e9..05c4e7c3 100644 --- a/patched-vscode/extensions/typescript-language-features/src/filesystems/memFs.ts +++ b/patched-vscode/extensions/typescript-language-features/src/filesystems/memFs.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import { basename, dirname } from 'path'; +import * as vscode from 'vscode'; +import { Logger } from '../logging/logger'; export class MemFs implements vscode.FileSystemProvider { @@ -14,8 +15,13 @@ export class MemFs implements vscode.FileSystemProvider { 0, ); + constructor( + private readonly id: string, + private readonly logger: Logger, + ) { } + stat(uri: vscode.Uri): vscode.FileStat { - // console.log('stat', uri.toString()); + this.logger.trace(`MemFs.stat ${this.id}. uri: ${uri}`); const entry = this.getEntry(uri); if (!entry) { throw vscode.FileSystemError.FileNotFound(); @@ -25,7 +31,7 @@ export class MemFs implements vscode.FileSystemProvider { } readDirectory(uri: vscode.Uri): [string, vscode.FileType][] { - // console.log('readDirectory', uri.toString()); + this.logger.trace(`MemFs.readDirectory ${this.id}. uri: ${uri}`); const entry = this.getEntry(uri); if (!entry) { @@ -39,7 +45,7 @@ export class MemFs implements vscode.FileSystemProvider { } readFile(uri: vscode.Uri): Uint8Array { - // console.log('readFile', uri.toString()); + this.logger.trace(`MemFs.readFile ${this.id}. uri: ${uri}`); const entry = this.getEntry(uri); if (!entry) { @@ -54,7 +60,7 @@ export class MemFs implements vscode.FileSystemProvider { } writeFile(uri: vscode.Uri, content: Uint8Array, { create, overwrite }: { create: boolean; overwrite: boolean }): void { - // console.log('writeFile', uri.toString()); + this.logger.trace(`MemFs.writeFile ${this.id}. uri: ${uri}`); const dir = this.getParent(uri); @@ -98,7 +104,8 @@ export class MemFs implements vscode.FileSystemProvider { } createDirectory(uri: vscode.Uri): void { - // console.log('createDirectory', uri.toString()); + this.logger.trace(`MemFs.createDirectory ${this.id}. uri: ${uri}`); + const dir = this.getParent(uri); const now = Date.now() / 1000; dir.contents.set(basename(uri.path), new FsDirectoryEntry(new Map(), now, now)); diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/completions.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/completions.ts index 708d7e02..1012083a 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -58,6 +58,7 @@ class MyCompletionItem extends vscode.CompletionItem { private readonly completionContext: CompletionContext, public readonly metadata: any | undefined, client: ITypeScriptServiceClient, + defaultCommitCharacters: readonly string[] | undefined, ) { const label = tsEntry.name || (tsEntry.insertText ?? ''); super(label, MyCompletionItem.convertKind(tsEntry.kind)); @@ -93,7 +94,7 @@ class MyCompletionItem extends vscode.CompletionItem { this.useCodeSnippet = completionContext.completeFunctionCalls && (this.kind === vscode.CompletionItemKind.Function || this.kind === vscode.CompletionItemKind.Method); this.range = this.getRangeFromReplacementSpan(tsEntry, completionContext); - this.commitCharacters = MyCompletionItem.getCommitCharacters(completionContext, tsEntry); + this.commitCharacters = MyCompletionItem.getCommitCharacters(completionContext, tsEntry, defaultCommitCharacters); this.insertText = isSnippet && tsEntry.insertText ? new vscode.SnippetString(tsEntry.insertText) : tsEntry.insertText; this.filterText = tsEntry.filterText || this.getFilterText(completionContext.line, tsEntry.insertText); @@ -500,7 +501,23 @@ class MyCompletionItem extends vscode.CompletionItem { } } - private static getCommitCharacters(context: CompletionContext, entry: Proto.CompletionEntry): string[] | undefined { + private static getCommitCharacters( + context: CompletionContext, + entry: Proto.CompletionEntry, + defaultCommitCharacters: readonly string[] | undefined, + ): string[] | undefined { + // @ts-expect-error until TS 5.6 + let commitCharacters = (entry.commitCharacters as string[] | undefined) ?? (defaultCommitCharacters ? Array.from(defaultCommitCharacters) : undefined); + if (commitCharacters) { + if (context.enableCallCompletions + && !context.isNewIdentifierLocation + && entry.kind !== PConst.Kind.warning + && entry.kind !== PConst.Kind.string) { + commitCharacters.push('('); + } + return commitCharacters; + } + if (entry.kind === PConst.Kind.warning || entry.kind === PConst.Kind.string) { // Ambient JS word based suggestion, strings return undefined; } @@ -509,7 +526,7 @@ class MyCompletionItem extends vscode.CompletionItem { return undefined; } - const commitCharacters: string[] = ['.', ',', ';']; + commitCharacters = ['.', ',', ';']; if (context.enableCallCompletions) { commitCharacters.push('('); } @@ -735,52 +752,40 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< triggerKind: typeConverters.CompletionTriggerKind.toProtocolCompletionTriggerKind(context.triggerKind), }; - let isNewIdentifierLocation = true; - let isIncomplete = false; - let isMemberCompletion = false; let dotAccessorContext: DotAccessorContext | undefined; - let entries: ReadonlyArray; - let metadata: any | undefined; let response: ServerResponse.Response | undefined; let duration: number | undefined; let optionalReplacementRange: vscode.Range | undefined; - if (this.client.apiVersion.gte(API.v300)) { - const startTime = Date.now(); - try { - response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token)); - } finally { - duration = Date.now() - startTime; - } - if (response.type !== 'response' || !response.body) { - this.logCompletionsTelemetry(duration, response); - return undefined; - } - isNewIdentifierLocation = response.body.isNewIdentifierLocation; - isMemberCompletion = response.body.isMemberCompletion; - if (isMemberCompletion) { - const dotMatch = line.text.slice(0, position.character).match(/\??\.\s*$/) || undefined; - if (dotMatch) { - const range = new vscode.Range(position.translate({ characterDelta: -dotMatch[0].length }), position); - const text = document.getText(range); - dotAccessorContext = { range, text }; - } - } - isIncomplete = !!response.body.isIncomplete || (response.metadata as any)?.isIncomplete; - entries = response.body.entries; - metadata = response.metadata; + const startTime = Date.now(); + try { + response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token)); + } finally { + duration = Date.now() - startTime; + } - if (response.body.optionalReplacementSpan) { - optionalReplacementRange = typeConverters.Range.fromTextSpan(response.body.optionalReplacementSpan); - } - } else { - const response = await this.client.interruptGetErr(() => this.client.execute('completions', args, token)); - if (response.type !== 'response' || !response.body) { - return undefined; + if (response.type !== 'response' || !response.body) { + this.logCompletionsTelemetry(duration, response); + return undefined; + } + const isNewIdentifierLocation = response.body.isNewIdentifierLocation; + const isMemberCompletion = response.body.isMemberCompletion; + if (isMemberCompletion) { + const dotMatch = line.text.slice(0, position.character).match(/\??\.\s*$/) || undefined; + if (dotMatch) { + const range = new vscode.Range(position.translate({ characterDelta: -dotMatch[0].length }), position); + const text = document.getText(range); + dotAccessorContext = { range, text }; } + } + const isIncomplete = !!response.body.isIncomplete || (response.metadata as any)?.isIncomplete; + const entries = response.body.entries; + const metadata = response.metadata; + // @ts-expect-error until TS 5.6 + const defaultCommitCharacters: readonly string[] | undefined = Object.freeze(response.body.defaultCommitCharacters); - entries = response.body; - metadata = response.metadata; + if (response.body.optionalReplacementSpan) { + optionalReplacementRange = typeConverters.Range.fromTextSpan(response.body.optionalReplacementSpan); } const completionContext: CompletionContext = { @@ -799,7 +804,14 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< const items: MyCompletionItem[] = []; for (const entry of entries) { if (!shouldExcludeCompletionEntry(entry, completionConfiguration)) { - const item = new MyCompletionItem(position, document, entry, completionContext, metadata, this.client); + const item = new MyCompletionItem( + position, + document, + entry, + completionContext, + metadata, + this.client, + defaultCommitCharacters); item.command = { command: ApplyCompletionCommand.ID, title: '', @@ -856,11 +868,11 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< private getTsTriggerCharacter(context: vscode.CompletionContext): Proto.CompletionsTriggerCharacter | undefined { switch (context.triggerCharacter) { - case '@': { // Workaround for https://github.com/microsoft/TypeScript/issues/27321 - return this.client.apiVersion.gte(API.v310) && this.client.apiVersion.lt(API.v320) ? undefined : '@'; + case '@': { + return '@'; } - case '#': { // Workaround for https://github.com/microsoft/TypeScript/issues/36367 - return this.client.apiVersion.lt(API.v381) ? undefined : '#'; + case '#': { + return '#'; } case ' ': { return this.client.apiVersion.gte(API.v430) ? ' ' : undefined; diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/copyPaste.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/copyPaste.ts index 643c77ac..3e2e61a8 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/copyPaste.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/copyPaste.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { DocumentSelector } from '../configuration/documentSelector'; import * as typeConverters from '../typeConverters'; import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; -import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration'; +import { conditionalRegistration, requireGlobalConfiguration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration'; import protocol from '../tsServer/protocol/protocol'; import { API } from '../tsServer/api'; import { LanguageDescription } from '../configuration/languageDescription'; @@ -38,6 +38,8 @@ class CopyMetadata { } } +const settingId = 'experimental.updateImportsOnPaste'; + class DocumentPasteProvider implements vscode.DocumentPasteEditProvider { static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'jsts', 'pasteWithImports'); @@ -61,7 +63,7 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider { token: vscode.CancellationToken, ): Promise { const config = vscode.workspace.getConfiguration(this._modeId, document.uri); - if (!config.get('experimental.updateImportsOnPaste')) { + if (!config.get(settingId, false)) { return; } @@ -93,14 +95,18 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider { } } - const response = await this._client.execute('getPasteEdits', { + if (copiedFrom?.file === file) { + return; + } + + const response = await this._client.interruptGetErr(() => this._client.execute('getPasteEdits', { file, // TODO: only supports a single paste for now pastedText: [text], pasteLocations: ranges.map(typeConverters.Range.toTextSpan), copiedFrom - }, token); - if (response.type !== 'response' || !response.body || token.isCancellationRequested) { + }, token)); + if (response.type !== 'response' || !response.body?.edits.length || token.isCancellationRequested) { return; } @@ -126,7 +132,8 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider { export function register(selector: DocumentSelector, language: LanguageDescription, client: ITypeScriptServiceClient) { return conditionalRegistration([ requireSomeCapability(client, ClientCapability.Semantic), - requireMinVersion(client, API.v550), + requireMinVersion(client, API.v560), + requireGlobalConfiguration(language.id, settingId), ], () => { return vscode.languages.registerDocumentPasteEditProvider(selector.semantic, new DocumentPasteProvider(language.id, client), { providedPasteEditKinds: [DocumentPasteProvider.kind], diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts index 190e6a99..032b2467 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts @@ -11,6 +11,8 @@ import { ResourceMap } from '../utils/resourceMap'; import { TelemetryReporter } from '../logging/telemetry'; import { TypeScriptServiceConfiguration } from '../configuration/configuration'; import { equals } from '../utils/objects'; +// @ts-expect-error until ts 5.6 +import { DiagnosticPerformanceData as TsDiagnosticPerformanceData } from '../tsServer/protocol/protocol'; function diagnosticsEquals(a: vscode.Diagnostic, b: vscode.Diagnostic): boolean { if (a === b) { @@ -34,6 +36,7 @@ export const enum DiagnosticKind { Syntax, Semantic, Suggestion, + RegionSemantic, } class FileDiagnostics { @@ -48,7 +51,8 @@ class FileDiagnostics { public updateDiagnostics( language: DiagnosticLanguage, kind: DiagnosticKind, - diagnostics: ReadonlyArray + diagnostics: ReadonlyArray, + ranges: ReadonlyArray | undefined ): boolean { if (language !== this.language) { this._diagnostics.clear(); @@ -61,6 +65,9 @@ class FileDiagnostics { return false; } + if (kind === DiagnosticKind.RegionSemantic) { + return this.updateRegionDiagnostics(diagnostics, ranges!); + } this._diagnostics.set(kind, diagnostics); return true; } @@ -83,6 +90,23 @@ class FileDiagnostics { } } + /** + * @param ranges The ranges whose diagnostics were updated. + */ + private updateRegionDiagnostics( + diagnostics: ReadonlyArray, + ranges: ReadonlyArray): boolean { + if (!this._diagnostics.get(DiagnosticKind.Semantic)) { + this._diagnostics.set(DiagnosticKind.Semantic, diagnostics); + return true; + } + const oldDiagnostics = this._diagnostics.get(DiagnosticKind.Semantic)!; + const newDiagnostics = oldDiagnostics.filter(diag => !ranges.some(range => diag.range.intersection(range))); + newDiagnostics.push(...diagnostics); + this._diagnostics.set(DiagnosticKind.Semantic, newDiagnostics); + return true; + } + private getSuggestionDiagnostics(settings: DiagnosticSettings) { const enableSuggestions = settings.getEnableSuggestions(this.language); return this.get(DiagnosticKind.Suggestion).filter(x => { @@ -151,6 +175,10 @@ class DiagnosticSettings { } } +interface DiagnosticPerformanceData extends TsDiagnosticPerformanceData { + fileLineCount?: number; +} + class DiagnosticsTelemetryManager extends Disposable { private readonly _diagnosticCodesMap = new Map(); @@ -172,6 +200,37 @@ class DiagnosticsTelemetryManager extends Disposable { this._registerTelemetryEventEmitter(); } + public logDiagnosticsPerformanceTelemetry(performanceData: DiagnosticPerformanceData[]): void { + for (const data of performanceData) { + /* __GDPR__ + "diagnostics.performance" : { + "owner": "mjbvz", + "syntaxDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "semanticDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "suggestionDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "regionSemanticDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "fileLineCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "${include}": [ + "${TypeScriptCommonProperties}" + ] + } + */ + this._telemetryReporter.logTelemetry('diagnostics.performance', + { + // @ts-expect-error until ts 5.6 + syntaxDiagDuration: data.syntaxDiag, + // @ts-expect-error until ts 5.6 + semanticDiagDuration: data.semanticDiag, + // @ts-expect-error until ts 5.6 + suggestionDiagDuration: data.suggestionDiag, + // @ts-expect-error until ts 5.6 + regionSemanticDiagDuration: data.regionSemanticDiag, + fileLineCount: data.fileLineCount, + }, + ); + } + } + private _updateAllDiagnosticCodesAfterTimeout() { clearTimeout(this._timeout); this._timeout = setTimeout(() => this._updateDiagnosticCodes(), 5000); @@ -235,6 +294,8 @@ export class DiagnosticsManager extends Disposable { private readonly _updateDelay = 50; + private readonly _diagnosticsTelemetryManager: DiagnosticsTelemetryManager | undefined; + constructor( owner: string, configuration: TypeScriptServiceConfiguration, @@ -248,7 +309,7 @@ export class DiagnosticsManager extends Disposable { this._currentDiagnostics = this._register(vscode.languages.createDiagnosticCollection(owner)); // Here we are selecting only 1 user out of 1000 to send telemetry diagnostics if (Math.random() * 1000 <= 1 || configuration.enableDiagnosticsTelemetry) { - this._register(new DiagnosticsTelemetryManager(telemetryReporter, this._currentDiagnostics)); + this._diagnosticsTelemetryManager = this._register(new DiagnosticsTelemetryManager(telemetryReporter, this._currentDiagnostics)); } } @@ -284,15 +345,16 @@ export class DiagnosticsManager extends Disposable { file: vscode.Uri, language: DiagnosticLanguage, kind: DiagnosticKind, - diagnostics: ReadonlyArray + diagnostics: ReadonlyArray, + ranges: ReadonlyArray | undefined, ): void { let didUpdate = false; const entry = this._diagnostics.get(file); if (entry) { - didUpdate = entry.updateDiagnostics(language, kind, diagnostics); + didUpdate = entry.updateDiagnostics(language, kind, diagnostics, ranges); } else if (diagnostics.length) { const fileDiagnostics = new FileDiagnostics(file, language); - fileDiagnostics.updateDiagnostics(language, kind, diagnostics); + fileDiagnostics.updateDiagnostics(language, kind, diagnostics, ranges); this._diagnostics.set(file, fileDiagnostics); didUpdate = true; } @@ -326,6 +388,10 @@ export class DiagnosticsManager extends Disposable { return this._currentDiagnostics.get(file) || []; } + public logDiagnosticsPerformanceTelemetry(performanceData: DiagnosticPerformanceData[]): void { + this._diagnosticsTelemetryManager?.logDiagnosticsPerformanceTelemetry(performanceData); + } + private scheduleDiagnosticsUpdate(file: vscode.Uri) { if (!this._pendingUpdates.has(file)) { this._pendingUpdates.set(file, setTimeout(() => this.updateCurrentDiagnostics(file), this._updateDelay)); diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index f96f8945..6e1ae905 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -5,12 +5,12 @@ import * as path from 'path'; import * as vscode from 'vscode'; -import type * as Proto from '../tsServer/protocol/protocol'; +import * as fileSchemes from '../configuration/fileSchemes'; +import { isTypeScriptDocument } from '../configuration/languageIds'; import { API } from '../tsServer/api'; +import type * as Proto from '../tsServer/protocol/protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; import { Disposable } from '../utils/dispose'; -import * as fileSchemes from '../configuration/fileSchemes'; -import { isTypeScriptDocument } from '../configuration/languageIds'; import { equals } from '../utils/objects'; import { ResourceMap } from '../utils/resourceMap'; @@ -191,7 +191,8 @@ export default class FileConfigurationManager extends Disposable { includeCompletionsWithClassMemberSnippets: config.get('suggest.classMemberSnippets.enabled', true), includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true), autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri), - // @ts-expect-error until 5.3 #56090 + // @ts-expect-error until 5.6 + autoImportSpecifierExcludeRegexes: preferencesConfig.get('autoImportSpecifierExcludeRegexes'), preferTypeOnlyAutoImports: preferencesConfig.get('preferTypeOnlyAutoImports', false), useLabelDetailsInCompletionEntries: true, allowIncompleteCompletions: true, @@ -200,6 +201,7 @@ export default class FileConfigurationManager extends Disposable { interactiveInlayHints: true, includeCompletionsForModuleExports: config.get('suggest.autoImports'), ...getInlayHintsPreferences(config), + ...this.getOrganizeImportsPreferences(preferencesConfig), }; return preferences; @@ -209,7 +211,7 @@ export default class FileConfigurationManager extends Disposable { switch (config.get('quoteStyle')) { case 'single': return 'single'; case 'double': return 'double'; - default: return this.client.apiVersion.gte(API.v333) ? 'auto' : undefined; + default: return 'auto'; } } @@ -228,6 +230,23 @@ export default class FileConfigurationManager extends Disposable { wildcardPrefix + '**' + path.sep + p; }); } + + private getOrganizeImportsPreferences(config: vscode.WorkspaceConfiguration): Proto.UserPreferences { + return { + // More specific settings + organizeImportsAccentCollation: config.get('organizeImports.accentCollation'), + organizeImportsCaseFirst: withDefaultAsUndefined(config.get<'default' | 'upper' | 'lower'>('organizeImports.caseFirst', 'default'), 'default'), + organizeImportsCollation: config.get<'ordinal' | 'unicode'>('organizeImports.collation'), + organizeImportsIgnoreCase: withDefaultAsUndefined(config.get<'auto' | 'caseInsensitive' | 'caseSensitive'>('organizeImports.caseSensitivity'), 'auto'), + organizeImportsLocale: config.get('organizeImports.locale'), + organizeImportsNumericCollation: config.get('organizeImports.numericCollation'), + organizeImportsTypeOrder: withDefaultAsUndefined(config.get<'auto' | 'last' | 'inline' | 'first'>('organizeImports.typeOrder', 'auto'), 'auto'), + }; + } +} + +function withDefaultAsUndefined(value: T, def: O): Exclude | undefined { + return value === def ? undefined : value as Exclude; } export class InlayHintSettingNames { diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/fixAll.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/fixAll.ts index 69043921..09ab205d 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/fixAll.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/fixAll.ts @@ -5,7 +5,6 @@ import * as vscode from 'vscode'; import { DocumentSelector } from '../configuration/documentSelector'; -import { API } from '../tsServer/api'; import * as errorCodes from '../tsServer/protocol/errorCodes'; import * as fixNames from '../tsServer/protocol/fixNames'; import type * as Proto from '../tsServer/protocol/protocol'; @@ -13,7 +12,7 @@ import * as typeConverters from '../typeConverters'; import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; import { DiagnosticsManager } from './diagnostics'; import FileConfigurationManager from './fileConfigurationManager'; -import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; interface AutoFix { @@ -250,7 +249,6 @@ export function register( diagnosticsManager: DiagnosticsManager, ) { return conditionalRegistration([ - requireMinVersion(client, API.v300), requireSomeCapability(client, ClientCapability.Semantic), ], () => { const provider = new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager); diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/refactor.ts index 8dc06235..0364b7fa 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -541,11 +541,12 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { + const response = await this.interruptGetErrIfNeeded(context, () => { const file = this.client.toOpenTsFilePath(document); if (!file) { return undefined; } + this.formattingOptionsManager.ensureConfigurationForDocument(document, token); const args: Proto.GetApplicableRefactorsRequestArgs = { @@ -595,6 +596,17 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider(context: vscode.CodeActionContext, f: () => R): R { + // Only interrupt diagnostics computation when code actions are explicitly + // (such as using the refactor command or a keybinding). This is a clear + // user action so we want to return results as quickly as possible. + if (context.triggerKind === vscode.CodeActionTriggerKind.Invoke) { + return this.client.interruptGetErr(f); + } else { + return f(); + } + } + public async resolveCodeAction( codeAction: TsCodeAction, token: vscode.CancellationToken, diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/rename.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/rename.ts index 19dc5b93..5c86190a 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/rename.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/rename.ts @@ -36,10 +36,6 @@ class TypeScriptRenameProvider implements vscode.RenameProvider { position: vscode.Position, token: vscode.CancellationToken ): Promise { - if (this.client.apiVersion.lt(API.v310)) { - return undefined; - } - const response = await this.execRename(document, position, token); if (!response) { return undefined; diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/semanticTokens.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/semanticTokens.ts index 48c9af7a..7f8d60d3 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/semanticTokens.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/semanticTokens.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { DocumentSelector } from '../configuration/documentSelector'; import * as Proto from '../tsServer/protocol/protocol'; -import { API } from '../tsServer/api'; import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; -import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration'; -import { DocumentSelector } from '../configuration/documentSelector'; +import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; // as we don't do deltas, for performance reasons, don't compute semantic tokens for documents above that limit const CONTENT_LENGTH_LIMIT = 100000; @@ -18,7 +17,6 @@ export function register( client: ITypeScriptServiceClient, ) { return conditionalRegistration([ - requireMinVersion(client, API.v370), requireSomeCapability(client, ClientCapability.Semantic), ], () => { const provider = new DocumentSemanticTokensProvider(client); diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/smartSelect.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/smartSelect.ts index 80887d65..fa6a6096 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/smartSelect.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/smartSelect.ts @@ -5,14 +5,11 @@ import * as vscode from 'vscode'; import { DocumentSelector } from '../configuration/documentSelector'; -import { API } from '../tsServer/api'; import type * as Proto from '../tsServer/protocol/protocol'; import * as typeConverters from '../typeConverters'; import { ITypeScriptServiceClient } from '../typescriptService'; -import { conditionalRegistration, requireMinVersion } from './util/dependentRegistration'; class SmartSelection implements vscode.SelectionRangeProvider { - public static readonly minVersion = API.v350; public constructor( private readonly client: ITypeScriptServiceClient @@ -53,9 +50,5 @@ export function register( selector: DocumentSelector, client: ITypeScriptServiceClient, ) { - return conditionalRegistration([ - requireMinVersion(client, SmartSelection.minVersion), - ], () => { - return vscode.languages.registerSelectionRangeProvider(selector.syntax, new SmartSelection(client)); - }); + return vscode.languages.registerSelectionRangeProvider(selector.syntax, new SmartSelection(client)); } diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts index 45ac08e1..6b47feb3 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts @@ -4,17 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import type * as Proto from '../tsServer/protocol/protocol'; -import { API } from '../tsServer/api'; -import { ITypeScriptServiceClient } from '../typescriptService'; -import { Condition, conditionalRegistration, requireMinVersion } from './util/dependentRegistration'; -import { Disposable } from '../utils/dispose'; import { DocumentSelector } from '../configuration/documentSelector'; import { LanguageDescription } from '../configuration/languageDescription'; +import type * as Proto from '../tsServer/protocol/protocol'; import * as typeConverters from '../typeConverters'; +import { ITypeScriptServiceClient } from '../typescriptService'; +import { Disposable } from '../utils/dispose'; +import { Condition, conditionalRegistration } from './util/dependentRegistration'; class TagClosing extends Disposable { - public static readonly minVersion = API.v300; private _disposed = false; private _timeout: NodeJS.Timeout | undefined = undefined; @@ -167,7 +165,6 @@ export function register( client: ITypeScriptServiceClient, ) { return conditionalRegistration([ - requireMinVersion(client, TagClosing.minVersion), requireActiveDocumentSetting(selector.syntax, language) ], () => new TagClosing(client)); } diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/updatePathsOnRename.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/updatePathsOnRename.ts index 91ca9612..bdaa1fc6 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/updatePathsOnRename.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/updatePathsOnRename.ts @@ -7,7 +7,6 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as fileSchemes from '../configuration/fileSchemes'; import { doesResourceLookLikeATypeScriptFile } from '../configuration/languageDescription'; -import { API } from '../tsServer/api'; import type * as Proto from '../tsServer/protocol/protocol'; import * as typeConverters from '../typeConverters'; import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; @@ -15,7 +14,7 @@ import { Delayer } from '../utils/async'; import { nulToken } from '../utils/cancellation'; import { Disposable } from '../utils/dispose'; import FileConfigurationManager from './fileConfigurationManager'; -import { conditionalRegistration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration'; +import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; const updateImportsOnFileMoveName = 'updateImportsOnFileMove.enabled'; @@ -43,7 +42,6 @@ interface RenameAction { } class UpdateImportsOnFileRenameHandler extends Disposable { - public static readonly minVersion = API.v300; private readonly _delayer = new Delayer(50); private readonly _pendingRenames = new Set(); @@ -289,7 +287,6 @@ export function register( handles: (uri: vscode.Uri) => Promise, ) { return conditionalRegistration([ - requireMinVersion(client, UpdateImportsOnFileRenameHandler.minVersion), requireSomeCapability(client, ClientCapability.Semantic), ], () => { return new UpdateImportsOnFileRenameHandler(client, fileConfigurationManager, handles); diff --git a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts index fe26dd64..af1a7e60 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts @@ -15,24 +15,6 @@ export interface IFilePathToResourceConverter { toResource(filepath: string): vscode.Uri; } -function replaceLinks(text: string): string { - return text - // Http(s) links - .replace(/\{@(link|linkplain|linkcode) (https?:\/\/[^ |}]+?)(?:[| ]([^{}\n]+?))?\}/gi, (_, tag: string, link: string, text?: string) => { - switch (tag) { - case 'linkcode': - return `[\`${text ? text.trim() : link}\`](${link})`; - - default: - return `[${text ? text.trim() : link}](${link})`; - } - }); -} - -function processInlineTags(text: string): string { - return replaceLinks(text); -} - function getTagBodyText( tag: Proto.JSDocTagInfo, filePathConverter: IFilePathToResourceConverter, @@ -67,18 +49,19 @@ function getTagBodyText( case 'author': { // fix obsucated email address, #80898 const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/); - if (emailMatch === null) { return text; } else { return `${emailMatch[1]} ${emailMatch[2]}`; } } - case 'default': + case 'default': { return makeCodeblock(text); + } + default: { + return text; + } } - - return processInlineTags(text); } function getTagDocumentation( @@ -98,11 +81,10 @@ function getTagDocumentation( if (!doc) { return label; } - return label + (doc.match(/\r\n|\n/g) ? ' \n' + processInlineTags(doc) : ` \u2014 ${processInlineTags(doc)}`); + return label + (doc.match(/\r\n|\n/g) ? ' \n' + doc : ` \u2014 ${doc}`); } break; } - case 'return': case 'returns': { // For return(s), we require a non-empty body @@ -147,7 +129,7 @@ export function asPlainTextWithLinks( parts: readonly Proto.SymbolDisplayPart[] | string, filePathConverter: IFilePathToResourceConverter, ): string { - return processInlineTags(convertLinkTags(parts, filePathConverter)); + return convertLinkTags(parts, filePathConverter); } /** @@ -187,10 +169,10 @@ function convertLinkTags( if (text) { if (/^https?:/.test(text)) { const parts = text.split(' '); - if (parts.length === 1) { + if (parts.length === 1 && !currentLink.linkcode) { out.push(`<${parts[0]}>`); - } else if (parts.length > 1) { - const linkText = parts.slice(1).join(' '); + } else { + const linkText = parts.length > 1 ? parts.slice(1).join(' ') : parts[0]; out.push(`[${currentLink.linkcode ? '`' + escapeMarkdownSyntaxTokensForCode(linkText) + '`' : linkText}](${parts[0]})`); } } else { @@ -224,7 +206,7 @@ function convertLinkTags( break; } } - return processInlineTags(out.join('')); + return out.join(''); } function escapeMarkdownSyntaxTokensForCode(text: string): string { diff --git a/patched-vscode/extensions/typescript-language-features/src/languageProvider.ts b/patched-vscode/extensions/typescript-language-features/src/languageProvider.ts index a1927409..7b955916 100644 --- a/patched-vscode/extensions/typescript-language-features/src/languageProvider.ts +++ b/patched-vscode/extensions/typescript-language-features/src/languageProvider.ts @@ -138,7 +138,11 @@ export default class LanguageProvider extends Disposable { this.client.bufferSyncSupport.requestAllDiagnostics(); } - public diagnosticsReceived(diagnosticsKind: DiagnosticKind, file: vscode.Uri, diagnostics: (vscode.Diagnostic & { reportUnnecessary: any; reportDeprecated: any })[]): void { + public diagnosticsReceived( + diagnosticsKind: DiagnosticKind, + file: vscode.Uri, + diagnostics: (vscode.Diagnostic & { reportUnnecessary: any; reportDeprecated: any })[], + ranges: vscode.Range[] | undefined): void { if (diagnosticsKind !== DiagnosticKind.Syntax && !this.client.hasCapabilityForResource(file, ClientCapability.Semantic)) { return; } @@ -175,7 +179,7 @@ export default class LanguageProvider extends Disposable { } } return true; - })); + }), ranges); } public configFileDiagnosticsReceived(file: vscode.Uri, diagnostics: vscode.Diagnostic[]): void { diff --git a/patched-vscode/extensions/typescript-language-features/src/test/unit/textRendering.test.ts b/patched-vscode/extensions/typescript-language-features/src/test/unit/textRendering.test.ts index b13f682f..c2a8bafb 100644 --- a/patched-vscode/extensions/typescript-language-features/src/test/unit/textRendering.test.ts +++ b/patched-vscode/extensions/typescript-language-features/src/test/unit/textRendering.test.ts @@ -28,17 +28,19 @@ suite('typescript.previewer', () => { test('Should parse url jsdoc @link', () => { assert.strictEqual( documentationToMarkdown( - 'x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z', + // 'x {@link http://www.example.com/foo} y {@link https://api.jquery.com/bind/#bind-eventType-eventData-handler} z', + [{ "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "https://api.jquery.com/bind/#bind-eventType-eventData-handler", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], [], noopToResource, undefined ).value, - 'x [http://www.example.com/foo](http://www.example.com/foo) y [https://api.jquery.com/bind/#bind-eventType-eventData-handler](https://api.jquery.com/bind/#bind-eventType-eventData-handler) z'); + 'x y z'); }); test('Should parse url jsdoc @link with text', () => { assert.strictEqual( documentationToMarkdown( - 'x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z', + // 'x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z', + [{ "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo abc xyz", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/bar b a z", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], [], noopToResource, undefined ).value, @@ -48,11 +50,12 @@ suite('typescript.previewer', () => { test('Should treat @linkcode jsdocs links as monospace', () => { assert.strictEqual( documentationToMarkdown( - 'x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z', + // 'x {@linkcode http://www.example.com/foo} y {@linkplain http://www.example.com/bar} z', + [{ "text": "x ", "kind": "text" }, { "text": "{@linkcode ", "kind": "link" }, { "text": "http://www.example.com/foo", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@linkplain ", "kind": "link" }, { "text": "http://www.example.com/bar", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], [], noopToResource, undefined ).value, - 'x [`http://www.example.com/foo`](http://www.example.com/foo) y [http://www.example.com/bar](http://www.example.com/bar) z'); + 'x [`http://www.example.com/foo`](http://www.example.com/foo) y z'); }); test('Should parse url jsdoc @link in param tag', () => { @@ -60,22 +63,13 @@ suite('typescript.previewer', () => { tagsToMarkdown([ { name: 'param', - text: 'a x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z' + // a x {@link http://www.example.com/foo abc xyz} y {@link http://www.example.com/bar|b a z} z + text: [{ "text": "a", "kind": "parameterName" }, { "text": " ", "kind": "space" }, { "text": "x ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/foo abc xyz", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " y ", "kind": "text" }, { "text": "{@link ", "kind": "link" }, { "text": "http://www.example.com/bar b a z", "kind": "linkText" }, { "text": "}", "kind": "link" }, { "text": " z", "kind": "text" }], } ], noopToResource), '*@param* `a` — x [abc xyz](http://www.example.com/foo) y [b a z](http://www.example.com/bar) z'); }); - test('Should ignore unclosed jsdocs @link', () => { - assert.strictEqual( - documentationToMarkdown( - 'x {@link http://www.example.com/foo y {@link http://www.example.com/bar bar} z', - [], - noopToResource, undefined - ).value, - 'x {@link http://www.example.com/foo y [bar](http://www.example.com/bar) z'); - }); - test('Should support non-ascii characters in parameter name (#90108)', () => { assert.strictEqual( tagsToMarkdown([ diff --git a/patched-vscode/extensions/typescript-language-features/src/tsServer/api.ts b/patched-vscode/extensions/typescript-language-features/src/tsServer/api.ts index 4f26db47..b7081098 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsServer/api.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsServer/api.ts @@ -13,16 +13,7 @@ export class API { } public static readonly defaultVersion = API.fromSimpleString('1.0.0'); - public static readonly v300 = API.fromSimpleString('3.0.0'); - public static readonly v310 = API.fromSimpleString('3.1.0'); - public static readonly v314 = API.fromSimpleString('3.1.4'); - public static readonly v320 = API.fromSimpleString('3.2.0'); - public static readonly v333 = API.fromSimpleString('3.3.3'); - public static readonly v340 = API.fromSimpleString('3.4.0'); - public static readonly v350 = API.fromSimpleString('3.5.0'); - public static readonly v370 = API.fromSimpleString('3.7.0'); public static readonly v380 = API.fromSimpleString('3.8.0'); - public static readonly v381 = API.fromSimpleString('3.8.1'); public static readonly v390 = API.fromSimpleString('3.9.0'); public static readonly v400 = API.fromSimpleString('4.0.0'); public static readonly v401 = API.fromSimpleString('4.0.1'); @@ -38,6 +29,7 @@ export class API { public static readonly v544 = API.fromSimpleString('5.4.4'); public static readonly v540 = API.fromSimpleString('5.4.0'); public static readonly v550 = API.fromSimpleString('5.5.0'); + public static readonly v560 = API.fromSimpleString('5.6.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString); diff --git a/patched-vscode/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/patched-vscode/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index 87c71598..356c1703 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -78,24 +78,14 @@ class BufferSynchronizer { } public open(resource: vscode.Uri, args: Proto.OpenRequestArgs) { - if (this.supportsBatching) { - this.updatePending(resource, new OpenOperation(args, args.scriptKindName)); - } else { - this.client.executeWithoutWaitingForResponse('open', args); - } + this.updatePending(resource, new OpenOperation(args, args.scriptKindName)); } /** * @return Was the buffer open? */ public close(resource: vscode.Uri, filepath: string, scriptKind: ScriptKind | undefined): boolean { - if (this.supportsBatching) { - return this.updatePending(resource, new CloseOperation(filepath, scriptKind)); - } else { - const args: Proto.FileRequestArgs = { file: filepath }; - this.client.executeWithoutWaitingForResponse('close', args); - return true; - } + return this.updatePending(resource, new CloseOperation(filepath, scriptKind)); } public change(resource: vscode.Uri, filepath: string, events: readonly vscode.TextDocumentContentChangeEvent[]) { @@ -103,24 +93,14 @@ class BufferSynchronizer { return; } - if (this.supportsBatching) { - this.updatePending(resource, new ChangeOperation({ - fileName: filepath, - textChanges: events.map((change): Proto.CodeEdit => ({ - newText: change.text, - start: typeConverters.Position.toLocation(change.range.start), - end: typeConverters.Position.toLocation(change.range.end), - })).reverse(), // Send the edits end-of-document to start-of-document order - })); - } else { - for (const { range, text } of events) { - const args: Proto.ChangeRequestArgs = { - insertString: text, - ...typeConverters.Range.toFormattingRequestArgs(filepath, range) - }; - this.client.executeWithoutWaitingForResponse('change', args); - } - } + this.updatePending(resource, new ChangeOperation({ + fileName: filepath, + textChanges: events.map((change): Proto.CodeEdit => ({ + newText: change.text, + start: typeConverters.Position.toLocation(change.range.start), + end: typeConverters.Position.toLocation(change.range.end), + })).reverse(), // Send the edits end-of-document to start-of-document order + })); } public reset(): void { @@ -136,12 +116,6 @@ class BufferSynchronizer { } private flush() { - if (!this.supportsBatching) { - // We've already eagerly synchronized - this._pending.clear(); - return; - } - if (this._pending.size > 0) { const closedFiles: string[] = []; const openFiles: Proto.OpenRequestArgs[] = []; @@ -158,10 +132,6 @@ class BufferSynchronizer { } } - private get supportsBatching(): boolean { - return this.client.apiVersion.gte(API.v340); - } - private updatePending(resource: vscode.Uri, op: BufferOperation): boolean { switch (op.type) { case BufferOperationType.Close: { @@ -275,12 +245,12 @@ class SyncedBufferMap extends ResourceMap { } class PendingDiagnostics extends ResourceMap { - public getOrderedFileSet(): ResourceMap { + public getOrderedFileSet(): ResourceMap { const orderedResources = Array.from(this.entries()) .sort((a, b) => a.value - b.value) .map(entry => entry.resource); - const map = new ResourceMap(this._normalizePath, this.config); + const map = new ResourceMap(this._normalizePath, this.config); for (const resource of orderedResources) { map.set(resource, undefined); } @@ -292,7 +262,7 @@ class GetErrRequest { public static executeGetErrRequest( client: ITypeScriptServiceClient, - files: ResourceMap, + files: ResourceMap, onDone: () => void ) { return new GetErrRequest(client, files, onDone); @@ -303,7 +273,7 @@ class GetErrRequest { private constructor( private readonly client: ITypeScriptServiceClient, - public readonly files: ResourceMap, + public readonly files: ResourceMap, onDone: () => void ) { if (!this.isErrorReportingEnabled()) { @@ -313,19 +283,39 @@ class GetErrRequest { } const supportsSyntaxGetErr = this.client.apiVersion.gte(API.v440); - const allFiles = coalesce(Array.from(files.entries()) - .filter(entry => supportsSyntaxGetErr || client.hasCapabilityForResource(entry.resource, ClientCapability.Semantic)) + const fileEntries = Array.from(files.entries()).filter(entry => supportsSyntaxGetErr || client.hasCapabilityForResource(entry.resource, ClientCapability.Semantic)); + const allFiles = coalesce(fileEntries .map(entry => client.toTsFilePath(entry.resource))); if (!allFiles.length) { this._done = true; setImmediate(onDone); } else { - const request = this.areProjectDiagnosticsEnabled() + let request; + if (this.areProjectDiagnosticsEnabled()) { // Note that geterrForProject is almost certainly not the api we want here as it ends up computing far // too many diagnostics - ? client.executeAsync('geterrForProject', { delay: 0, file: allFiles[0] }, this._token.token) - : client.executeAsync('geterr', { delay: 0, files: allFiles }, this._token.token); + request = client.executeAsync('geterrForProject', { delay: 0, file: allFiles[0] }, this._token.token); + } + else { + let requestFiles; + if (this.areRegionDiagnosticsEnabled()) { + requestFiles = coalesce(fileEntries + .map(entry => { + const file = client.toTsFilePath(entry.resource); + const ranges = entry.value; + if (file && ranges) { + return typeConverters.Range.toFileRangesRequestArgs(file, ranges); + } + + return file; + })); + } + else { + requestFiles = allFiles; + } + request = client.executeAsync('geterr', { delay: 0, files: requestFiles }, this._token.token); + } request.finally(() => { if (this._done) { @@ -350,6 +340,10 @@ class GetErrRequest { return this.client.configuration.enableProjectDiagnostics && this.client.capabilities.has(ClientCapability.Semantic); } + private areRegionDiagnosticsEnabled() { + return this.client.configuration.enableRegionDiagnostics && this.client.apiVersion.gte(API.v560); + } + public cancel(): any { if (!this._done) { this._token.cancel(); @@ -638,6 +632,10 @@ export default class BufferSyncSupport extends Disposable { this.synchronizer.beforeCommand(command); } + public lineCount(resource: vscode.Uri): number | undefined { + return this.syncedBuffers.get(resource)?.lineCount; + } + private onDidCloseTextDocument(document: vscode.TextDocument): void { this.closeResource(document.uri); } @@ -722,7 +720,9 @@ export default class BufferSyncSupport extends Disposable { // Add all open TS buffers to the geterr request. They might be visible for (const buffer of this.syncedBuffers.values()) { - orderedFileSet.set(buffer.resource, undefined); + const editors = vscode.window.visibleTextEditors.filter(editor => editor.document.uri.toString() === buffer.resource.toString()); + const visibleRanges = editors.flatMap(editor => editor.visibleRanges); + orderedFileSet.set(buffer.resource, visibleRanges.length ? visibleRanges : undefined); } for (const { resource } of orderedFileSet.entries()) { diff --git a/patched-vscode/extensions/typescript-language-features/src/tsServer/protocol/protocol.const.ts b/patched-vscode/extensions/typescript-language-features/src/tsServer/protocol/protocol.const.ts index 4f02ed29..ed4806e6 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsServer/protocol/protocol.const.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsServer/protocol/protocol.const.ts @@ -78,6 +78,7 @@ export enum EventName { syntaxDiag = 'syntaxDiag', semanticDiag = 'semanticDiag', suggestionDiag = 'suggestionDiag', + regionSemanticDiag = 'regionSemanticDiag', configFileDiag = 'configFileDiag', telemetry = 'telemetry', projectLanguageServiceState = 'projectLanguageServiceState', @@ -91,6 +92,7 @@ export enum EventName { createFileWatcher = 'createFileWatcher', createDirectoryWatcher = 'createDirectoryWatcher', closeFileWatcher = 'closeFileWatcher', + requestCompleted = 'requestCompleted', } export enum OrganizeImportsMode { diff --git a/patched-vscode/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts b/patched-vscode/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts index 900d66f3..747e7c22 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts @@ -19,70 +19,5 @@ declare module '../../../../node_modules/typescript/lib/typescript' { interface Response { readonly _serverType?: ServerType; } - - //#region MapCode - export interface MapCodeRequestArgs extends FileRequestArgs { - /** - * The files and changes to try and apply/map. - */ - mapping: MapCodeRequestDocumentMapping; - } - - export interface MapCodeRequestDocumentMapping { - /** - * The specific code to map/insert/replace in the file. - */ - contents: string[]; - - /** - * Areas of "focus" to inform the code mapper with. For example, cursor - * location, current selection, viewport, etc. Nested arrays denote - * priority: toplevel arrays are more important than inner arrays, and - * inner array priorities are based on items within that array. Items - * earlier in the arrays have higher priority. - */ - focusLocations?: TextSpan[][]; - } - - export interface MapCodeRequest extends FileRequest { - command: 'mapCode'; - arguments: MapCodeRequestArgs; - } - - export interface MapCodeResponse extends Response { - body: FileCodeEdits[] - } - //#endregion - - //#region Paste - export interface GetPasteEditsRequest extends Request { - command: 'getPasteEdits'; - arguments: GetPasteEditsRequestArgs; - } - - export interface GetPasteEditsRequestArgs extends FileRequestArgs { - /** The text that gets pasted in a file. */ - pastedText: string[]; - /** Locations of where the `pastedText` gets added in a file. If the length of the `pastedText` and `pastedLocations` are not the same, - * then the `pastedText` is combined into one and added at all the `pastedLocations`. - */ - pasteLocations: TextSpan[]; - /** The source location of each `pastedText`. If present, the length of `spans` must be equal to the length of `pastedText`. */ - copiedFrom?: { - file: string; - spans: TextSpan[]; - }; - } - - export interface GetPasteEditsResponse extends Response { - body: PasteEditsAction; - } - export interface PasteEditsAction { - edits: FileCodeEdits[]; - fixId?: {}; - } - //#endregion } } - - diff --git a/patched-vscode/extensions/typescript-language-features/src/tsServer/server.ts b/patched-vscode/extensions/typescript-language-features/src/tsServer/server.ts index 883aa683..09529503 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsServer/server.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsServer/server.ts @@ -166,6 +166,10 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer { this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, callback); callback.onSuccess(undefined); } + // @ts-expect-error until ts 5.6 + if ((event as Proto.RequestCompletedEvent).body.performanceData) { + this._onEvent.fire(event); + } } else { this._tracer.traceEvent(this._serverId, event); this._onEvent.fire(event); diff --git a/patched-vscode/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts b/patched-vscode/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts index 71daf1fb..5adf1866 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts @@ -40,7 +40,7 @@ export class WorkerServerProcessFactory implements TsServerProcessFactory { version: TypeScriptVersion, args: readonly string[], kind: TsServerProcessKind, - _configuration: TypeScriptServiceConfiguration, + configuration: TypeScriptServiceConfiguration, _versionManager: TypeScriptVersionManager, _nodeVersionManager: NodeVersionManager, tsServerLog: TsServerLog | undefined, @@ -50,10 +50,10 @@ export class WorkerServerProcessFactory implements TsServerProcessFactory { ...args, // Explicitly give TS Server its path so it can load local resources '--executingFilePath', tsServerPath, + // Enable/disable web type acquisition + (configuration.webTypeAcquisitionEnabled && supportsReadableByteStreams() ? '--experimentalTypeAcquisition' : '--disableAutomaticTypingAcquisition'), ]; - if (_configuration.webTypeAcquisitionEnabled && supportsReadableByteStreams()) { - launchArgs.push('--experimentalTypeAcquisition'); - } + return new WorkerServerProcess(kind, tsServerPath, this._extensionUri, launchArgs, tsServerLog, this._logger); } } diff --git a/patched-vscode/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/patched-vscode/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index d03c7ea5..7dbde90f 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -278,8 +278,7 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { } const childProcess = execPath ? - child_process.spawn(JSON.stringify(execPath), [...execArgv, tsServerPath, ...runtimeArgs], { - shell: true, + child_process.spawn(execPath, [...execArgv, tsServerPath, ...runtimeArgs], { windowsHide: true, cwd: undefined, env, diff --git a/patched-vscode/extensions/typescript-language-features/src/tsServer/spawner.ts b/patched-vscode/extensions/typescript-language-features/src/tsServer/spawner.ts index 543140db..162fdf6d 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -116,12 +116,9 @@ export class TypeScriptServerSpawner { return CompositeServerType.Single; case SyntaxServerConfiguration.Auto: - if (version.apiVersion?.gte(API.v340)) { - return version.apiVersion?.gte(API.v400) - ? CompositeServerType.DynamicSeparateSyntax - : CompositeServerType.SeparateSyntax; - } - return CompositeServerType.Single; + return version.apiVersion?.gte(API.v400) + ? CompositeServerType.DynamicSeparateSyntax + : CompositeServerType.SeparateSyntax; } } @@ -234,7 +231,7 @@ export class TypeScriptServerSpawner { tsServerLog = { type: 'file', uri: logFilePath }; args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel)); - args.push('--logFile', `"${logFilePath.fsPath}"`); + args.push('--logFile', logFilePath.fsPath); } } } diff --git a/patched-vscode/extensions/typescript-language-features/src/tsconfig.ts b/patched-vscode/extensions/typescript-language-features/src/tsconfig.ts index 04f08a12..e85c715e 100644 --- a/patched-vscode/extensions/typescript-language-features/src/tsconfig.ts +++ b/patched-vscode/extensions/typescript-language-features/src/tsconfig.ts @@ -76,6 +76,10 @@ function inferredProjectConfigSnippet( config: TypeScriptServiceConfiguration ) { const baseConfig = inferredProjectCompilerOptions(version, projectType, config); + if (projectType === ProjectType.TypeScript) { + delete baseConfig.allowImportingTsExtensions; + } + const compilerOptions = Object.keys(baseConfig).map(key => `"${key}": ${JSON.stringify(baseConfig[key])}`); return new vscode.SnippetString(`{ "compilerOptions": { diff --git a/patched-vscode/extensions/typescript-language-features/src/typeConverters.ts b/patched-vscode/extensions/typescript-language-features/src/typeConverters.ts index 58babe2b..067a1ff3 100644 --- a/patched-vscode/extensions/typescript-language-features/src/typeConverters.ts +++ b/patched-vscode/extensions/typescript-language-features/src/typeConverters.ts @@ -26,14 +26,24 @@ export namespace Range { Math.max(0, start.line - 1), Math.max(start.offset - 1, 0), Math.max(0, end.line - 1), Math.max(0, end.offset - 1)); - export const toFileRangeRequestArgs = (file: string, range: vscode.Range): Proto.FileRangeRequestArgs => ({ - file, + // @ts-expect-error until ts 5.6 + export const toFileRange = (range: vscode.Range): Proto.FileRange => ({ startLine: range.start.line + 1, startOffset: range.start.character + 1, endLine: range.end.line + 1, endOffset: range.end.character + 1 }); + export const toFileRangeRequestArgs = (file: string, range: vscode.Range): Proto.FileRangeRequestArgs => ({ + file, + ...toFileRange(range) + }); + // @ts-expect-error until ts 5.6 + export const toFileRangesRequestArgs = (file: string, ranges: vscode.Range[]): Proto.FileRangesRequestArgs => ({ + file, + ranges: ranges.map(toFileRange) + }); + export const toFormattingRequestArgs = (file: string, range: vscode.Range): Proto.FormatRequestArgs => ({ file, line: range.start.line + 1, diff --git a/patched-vscode/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts b/patched-vscode/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts index da651e71..c44bfc3a 100644 --- a/patched-vscode/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts +++ b/patched-vscode/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts @@ -90,8 +90,8 @@ export default class TypeScriptServiceClientHost extends Disposable { services, allModeIds)); - this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => { - this.diagnosticsReceived(kind, resource, diagnostics); + this.client.onDiagnosticsReceived(({ kind, resource, diagnostics, spans }) => { + this.diagnosticsReceived(kind, resource, diagnostics, spans); }, null, this._disposables); this.client.onConfigDiagnosticsReceived(diag => this.configFileDiagnosticsReceived(diag), null, this._disposables); @@ -236,14 +236,16 @@ export default class TypeScriptServiceClientHost extends Disposable { private async diagnosticsReceived( kind: DiagnosticKind, resource: vscode.Uri, - diagnostics: Proto.Diagnostic[] + diagnostics: Proto.Diagnostic[], + spans: Proto.TextSpan[] | undefined, ): Promise { const language = await this.findLanguage(resource); if (language) { language.diagnosticsReceived( kind, resource, - this.createMarkerDatas(diagnostics, language.diagnosticSource)); + this.createMarkerDatas(diagnostics, language.diagnosticSource), + spans?.map(span => typeConverters.Range.fromTextSpan(span))); } } diff --git a/patched-vscode/extensions/typescript-language-features/src/typescriptServiceClient.ts b/patched-vscode/extensions/typescript-language-features/src/typescriptServiceClient.ts index 24742f99..2d3ea9ee 100644 --- a/patched-vscode/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/patched-vscode/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -30,6 +30,7 @@ import { TypeScriptVersionManager } from './tsServer/versionManager'; import { ITypeScriptVersionProvider, TypeScriptVersion } from './tsServer/versionProvider'; import { ClientCapabilities, ClientCapability, ExecConfig, ITypeScriptServiceClient, ServerResponse, TypeScriptRequests } from './typescriptService'; import { Disposable, DisposableStore, disposeAll } from './utils/dispose'; +import { hash } from './utils/hash'; import { isWeb, isWebAndHasSharedArrayBuffers } from './utils/platform'; @@ -37,6 +38,7 @@ export interface TsDiagnostics { readonly kind: DiagnosticKind; readonly resource: vscode.Uri; readonly diagnostics: Proto.Diagnostic[]; + readonly spans?: Proto.TextSpan[]; } interface ToCancelOnResourceChanged { @@ -140,7 +142,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType constructor( private readonly context: vscode.ExtensionContext, - onCaseInsenitiveFileSystem: boolean, + onCaseInsensitiveFileSystem: boolean, services: { pluginManager: PluginManager; logDirectoryProvider: ILogDirectoryProvider; @@ -190,7 +192,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.restartTsServer(); })); - this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsenitiveFileSystem); + this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsensitiveFileSystem); this.onReady(() => { this.bufferSyncSupport.listen(); }); this.bufferSyncSupport.onDelete(resource => { @@ -231,7 +233,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.apiVersion.fullVersionString; }); - this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsenitiveFileSystem); + this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsensitiveFileSystem); this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this._nodeVersionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory); this._register(this.pluginManager.onDidUpdateConfig(update => { @@ -423,17 +425,31 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.serverState = new ServerState.Running(handle, apiVersion, undefined, true); this.lastStart = Date.now(); + + /* __GDPR__FRAGMENT__ + "TypeScriptServerEnvCommonProperties" : { + "hasGlobalPlugins": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "globalPluginNameHashes": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + const typeScriptServerEnvCommonProperties = { + hasGlobalPlugins: this.pluginManager.plugins.length > 0, + globalPluginNameHashes: JSON.stringify(this.pluginManager.plugins.map(plugin => hash(plugin.name))), + }; + /* __GDPR__ "tsserver.spawned" : { "owner": "mjbvz", "${include}": [ - "${TypeScriptCommonProperties}" + "${TypeScriptCommonProperties}", + "${TypeScriptServerEnvCommonProperties}" ], "localTypeScriptVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "typeScriptVersionSource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ this.logTelemetry('tsserver.spawned', { + ...typeScriptServerEnvCommonProperties, localTypeScriptVersion: this.versionProvider.localVersion ? this.versionProvider.localVersion.displayName : '', typeScriptVersionSource: version.source, }); @@ -458,11 +474,14 @@ export default class TypeScriptServiceClient extends Disposable implements IType "tsserver.error" : { "owner": "mjbvz", "${include}": [ - "${TypeScriptCommonProperties}" + "${TypeScriptCommonProperties}", + "${TypeScriptServerEnvCommonProperties}" ] } */ - this.logTelemetry('tsserver.error'); + this.logTelemetry('tsserver.error', { + ...typeScriptServerEnvCommonProperties + }); this.serviceExited(false, apiVersion); }); @@ -475,14 +494,19 @@ export default class TypeScriptServiceClient extends Disposable implements IType /* __GDPR__ "tsserver.exitWithCode" : { "owner": "mjbvz", - "code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "${include}": [ - "${TypeScriptCommonProperties}" - ] + "${TypeScriptCommonProperties}", + "${TypeScriptServerEnvCommonProperties}" + ], + "code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } } */ - this.logTelemetry('tsserver.exitWithCode', { code: code ?? undefined, signal: signal ?? undefined }); + this.logTelemetry('tsserver.exitWithCode', { + ...typeScriptServerEnvCommonProperties, + code: code ?? undefined, + signal: signal ?? undefined, + }); if (this.token !== mytoken) { // this is coming from an old process @@ -672,7 +696,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType if (!this._isPromptingAfterCrash) { if (this.pluginManager.plugins.length) { prompt = vscode.window.showWarningMessage( - vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList), reportIssueItem); + vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList)); } else { prompt = vscode.window.showWarningMessage( vscode.l10n.t("The JS/TS language service crashed."), @@ -947,7 +971,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType switch (event.event) { case EventName.syntaxDiag: case EventName.semanticDiag: - case EventName.suggestionDiag: { + case EventName.suggestionDiag: + case EventName.regionSemanticDiag: { // This event also roughly signals that projects have been loaded successfully (since the TS server is synchronous) this.loadingIndicator.reset(); @@ -956,19 +981,21 @@ export default class TypeScriptServiceClient extends Disposable implements IType this._onDiagnosticsReceived.fire({ kind: getDiagnosticsKind(event), resource: this.toResource(diagnosticEvent.body.file), - diagnostics: diagnosticEvent.body.diagnostics + diagnostics: diagnosticEvent.body.diagnostics, + // @ts-expect-error until ts 5.6 + spans: diagnosticEvent.body.spans, }); } - break; + return; } case EventName.configFileDiag: this._onConfigDiagnosticsReceived.fire(event as Proto.ConfigFileDiagnosticEvent); - break; + return; case EventName.telemetry: { const body = (event as Proto.TelemetryEvent).body; this.dispatchTelemetryEvent(body); - break; + return; } case EventName.projectLanguageServiceState: { const body = (event as Proto.ProjectLanguageServiceStateEvent).body!; @@ -976,7 +1003,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.serverState.updateLanguageServiceEnabled(body.languageServiceEnabled); } this._onProjectLanguageServiceStateChanged.fire(body); - break; + return; } case EventName.projectsUpdatedInBackground: { this.loadingIndicator.reset(); @@ -984,56 +1011,84 @@ export default class TypeScriptServiceClient extends Disposable implements IType const body = (event as Proto.ProjectsUpdatedInBackgroundEvent).body; const resources = body.openFiles.map(file => this.toResource(file)); this.bufferSyncSupport.getErr(resources); - break; + return; } case EventName.beginInstallTypes: this._onDidBeginInstallTypings.fire((event as Proto.BeginInstallTypesEvent).body); - break; + return; case EventName.endInstallTypes: this._onDidEndInstallTypings.fire((event as Proto.EndInstallTypesEvent).body); - break; + return; case EventName.typesInstallerInitializationFailed: this._onTypesInstallerInitializationFailed.fire((event as Proto.TypesInstallerInitializationFailedEvent).body); - break; + return; case EventName.surveyReady: this._onSurveyReady.fire((event as Proto.SurveyReadyEvent).body); - break; + return; case EventName.projectLoadingStart: this.loadingIndicator.startedLoadingProject((event as Proto.ProjectLoadingStartEvent).body.projectName); - break; + return; case EventName.projectLoadingFinish: this.loadingIndicator.finishedLoadingProject((event as Proto.ProjectLoadingFinishEvent).body.projectName); - break; + return; + + case EventName.createDirectoryWatcher: { + const path = (event.body as Proto.CreateDirectoryWatcherEventBody).path; + if (path.startsWith(inMemoryResourcePrefix)) { + return; + } - case EventName.createDirectoryWatcher: this.createFileSystemWatcher( (event.body as Proto.CreateDirectoryWatcherEventBody).id, new vscode.RelativePattern( - vscode.Uri.file((event.body as Proto.CreateDirectoryWatcherEventBody).path), + vscode.Uri.file(path), (event.body as Proto.CreateDirectoryWatcherEventBody).recursive ? '**' : '*' ), (event.body as Proto.CreateDirectoryWatcherEventBody).ignoreUpdate ); - break; + return; + } + case EventName.createFileWatcher: { + const path = (event.body as Proto.CreateFileWatcherEventBody).path; + if (path.startsWith(inMemoryResourcePrefix)) { + return; + } - case EventName.createFileWatcher: this.createFileSystemWatcher( (event.body as Proto.CreateFileWatcherEventBody).id, new vscode.RelativePattern( - vscode.Uri.file((event.body as Proto.CreateFileWatcherEventBody).path), + vscode.Uri.file(path), '*' ) ); - break; - + return; + } case EventName.closeFileWatcher: this.closeFileSystemWatcher(event.body.id); - break; + return; + + case EventName.requestCompleted: { + // @ts-expect-error until ts 5.6 + const diagnosticsDuration = (event.body as Proto.RequestCompletedEventBody).performanceData?.diagnosticsDuration; + if (diagnosticsDuration) { + this.diagnosticsManager.logDiagnosticsPerformanceTelemetry( + // @ts-expect-error until ts 5.6 + diagnosticsDuration.map(fileData => { + const resource = this.toResource(fileData.file); + return { + ...fileData, + fileLineCount: this.bufferSyncSupport.lineCount(resource), + }; + }) + ); + } + return; + } } } @@ -1117,13 +1172,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.watches.set(id, disposable); } - private closeFileSystemWatcher( - id: number, - ) { + private closeFileSystemWatcher(id: number) { const existing = this.watches.get(id); - if (existing) { - existing.dispose(); - } + existing?.dispose(); } private dispatchTelemetryEvent(telemetryData: Proto.TelemetryEventBody): void { @@ -1157,6 +1208,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType break; } } + + // Add plugin data here if (telemetryData.telemetryEventName === 'projectInfo') { if (this.serverState.type === ServerState.Type.Running) { this.serverState.updateTsserverVersion(properties['version']); @@ -1179,9 +1232,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } private configurePlugin(pluginName: string, configuration: {}): any { - if (this.apiVersion.gte(API.v314)) { - this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration }); - } + this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration }); } } @@ -1261,6 +1312,7 @@ function getDiagnosticsKind(event: Proto.Event) { case 'syntaxDiag': return DiagnosticKind.Syntax; case 'semanticDiag': return DiagnosticKind.Semantic; case 'suggestionDiag': return DiagnosticKind.Suggestion; + case 'regionSemanticDiag': return DiagnosticKind.RegionSemantic; } throw new Error('Unknown dignostics kind'); } diff --git a/patched-vscode/extensions/typescript-language-features/src/ui/activeJsTsEditorTracker.ts b/patched-vscode/extensions/typescript-language-features/src/ui/activeJsTsEditorTracker.ts index 3da1f5a8..c3cae632 100644 --- a/patched-vscode/extensions/typescript-language-features/src/ui/activeJsTsEditorTracker.ts +++ b/patched-vscode/extensions/typescript-language-features/src/ui/activeJsTsEditorTracker.ts @@ -28,6 +28,7 @@ export class ActiveJsTsEditorTracker extends Disposable { this._register(vscode.window.onDidChangeActiveTextEditor(_ => this.update())); this._register(vscode.window.onDidChangeVisibleTextEditors(_ => this.update())); + this._register(vscode.window.tabGroups.onDidChangeTabGroups(_ => this.update())); this.update(); } diff --git a/patched-vscode/extensions/typescript-language-features/src/ui/intellisenseStatus.ts b/patched-vscode/extensions/typescript-language-features/src/ui/intellisenseStatus.ts index 1a6ea63f..e26e2b37 100644 --- a/patched-vscode/extensions/typescript-language-features/src/ui/intellisenseStatus.ts +++ b/patched-vscode/extensions/typescript-language-features/src/ui/intellisenseStatus.ts @@ -43,6 +43,8 @@ namespace IntellisenseState { export type State = typeof None | Pending | Resolved | typeof SyntaxOnly; } +type CreateOrOpenConfigCommandArgs = [root: vscode.Uri, projectType: ProjectType]; + export class IntellisenseStatus extends Disposable { public readonly openOpenConfigCommandId = '_typescript.openConfig'; @@ -62,7 +64,7 @@ export class IntellisenseStatus extends Disposable { commandManager.register({ id: this.openOpenConfigCommandId, - execute: async (root: vscode.Uri, projectType: ProjectType) => { + execute: async (...[root, projectType]: CreateOrOpenConfigCommandArgs) => { if (this._state.type === IntellisenseState.Type.Resolved) { await openProjectConfigOrPromptToCreate(projectType, this._client, root, this._state.configFile); } else if (this._state.type === IntellisenseState.Type.Pending) { @@ -72,7 +74,7 @@ export class IntellisenseStatus extends Disposable { }); commandManager.register({ id: this.createOrOpenConfigCommandId, - execute: async (root: vscode.Uri, projectType: ProjectType) => { + execute: async (...[root, projectType]: CreateOrOpenConfigCommandArgs) => { await openOrCreateConfig(this._client.apiVersion, projectType, root, this._client.configuration); }, }); @@ -182,7 +184,7 @@ export class IntellisenseStatus extends Disposable { title: this._state.projectType === ProjectType.TypeScript ? vscode.l10n.t("Configure tsconfig") : vscode.l10n.t("Configure jsconfig"), - arguments: [rootPath], + arguments: [rootPath, this._state.projectType] satisfies CreateOrOpenConfigCommandArgs, }; } else { statusItem.text = vscode.workspace.asRelativePath(this._state.configFile); @@ -190,7 +192,7 @@ export class IntellisenseStatus extends Disposable { statusItem.command = { command: this.openOpenConfigCommandId, title: vscode.l10n.t("Open config file"), - arguments: [rootPath], + arguments: [rootPath, this._state.projectType] satisfies CreateOrOpenConfigCommandArgs, }; } break; diff --git a/patched-vscode/extensions/typescript-language-features/src/ui/managedFileContext.ts b/patched-vscode/extensions/typescript-language-features/src/ui/managedFileContext.ts index 0b929f85..1da4588a 100644 --- a/patched-vscode/extensions/typescript-language-features/src/ui/managedFileContext.ts +++ b/patched-vscode/extensions/typescript-language-features/src/ui/managedFileContext.ts @@ -10,7 +10,7 @@ import { isSupportedLanguageMode } from '../configuration/languageIds'; import { Disposable } from '../utils/dispose'; import { ActiveJsTsEditorTracker } from './activeJsTsEditorTracker'; -/**E +/** * When clause context set when the current file is managed by vscode's built-in typescript extension. */ export default class ManagedFileContextManager extends Disposable { diff --git a/patched-vscode/extensions/typescript-language-features/src/utils/async.ts b/patched-vscode/extensions/typescript-language-features/src/utils/async.ts index db92754f..9523d7fe 100644 --- a/patched-vscode/extensions/typescript-language-features/src/utils/async.ts +++ b/patched-vscode/extensions/typescript-language-features/src/utils/async.ts @@ -70,3 +70,94 @@ export function setImmediate(callback: (...args: any[]) => void, ...args: any[]) return { dispose: () => clearTimeout(handle) }; } } + + +/** + * A helper to prevent accumulation of sequential async tasks. + * + * Imagine a mail man with the sole task of delivering letters. As soon as + * a letter submitted for delivery, he drives to the destination, delivers it + * and returns to his base. Imagine that during the trip, N more letters were submitted. + * When the mail man returns, he picks those N letters and delivers them all in a + * single trip. Even though N+1 submissions occurred, only 2 deliveries were made. + * + * The throttler implements this via the queue() method, by providing it a task + * factory. Following the example: + * + * const throttler = new Throttler(); + * const letters = []; + * + * function deliver() { + * const lettersToDeliver = letters; + * letters = []; + * return makeTheTrip(lettersToDeliver); + * } + * + * function onLetterReceived(l) { + * letters.push(l); + * throttler.queue(deliver); + * } + */ +export class Throttler { + + private activePromise: Promise | null; + private queuedPromise: Promise | null; + private queuedPromiseFactory: ITask> | null; + + private isDisposed = false; + + constructor() { + this.activePromise = null; + this.queuedPromise = null; + this.queuedPromiseFactory = null; + } + + queue(promiseFactory: ITask>): Promise { + if (this.isDisposed) { + return Promise.reject(new Error('Throttler is disposed')); + } + + if (this.activePromise) { + this.queuedPromiseFactory = promiseFactory; + + if (!this.queuedPromise) { + const onComplete = () => { + this.queuedPromise = null; + + if (this.isDisposed) { + return; + } + + const result = this.queue(this.queuedPromiseFactory!); + this.queuedPromiseFactory = null; + + return result; + }; + + this.queuedPromise = new Promise(resolve => { + this.activePromise!.then(onComplete, onComplete).then(resolve); + }); + } + + return new Promise((resolve, reject) => { + this.queuedPromise!.then(resolve, reject); + }); + } + + this.activePromise = promiseFactory(); + + return new Promise((resolve, reject) => { + this.activePromise!.then((result: T) => { + this.activePromise = null; + resolve(result); + }, (err: unknown) => { + this.activePromise = null; + reject(err); + }); + }); + } + + dispose(): void { + this.isDisposed = true; + } +} diff --git a/patched-vscode/extensions/typescript-language-features/src/utils/hash.ts b/patched-vscode/extensions/typescript-language-features/src/utils/hash.ts new file mode 100644 index 00000000..b0098089 --- /dev/null +++ b/patched-vscode/extensions/typescript-language-features/src/utils/hash.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Return a hash value for an object. + */ +export function hash(obj: any, hashVal = 0): number { + switch (typeof obj) { + case 'object': + if (obj === null) { + return numberHash(349, hashVal); + } else if (Array.isArray(obj)) { + return arrayHash(obj, hashVal); + } + return objectHash(obj, hashVal); + case 'string': + return stringHash(obj, hashVal); + case 'boolean': + return booleanHash(obj, hashVal); + case 'number': + return numberHash(obj, hashVal); + case 'undefined': + return 937 * 31; + default: + return numberHash(obj, 617); + } +} + +function numberHash(val: number, initialHashVal: number): number { + return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32 +} + +function booleanHash(b: boolean, initialHashVal: number): number { + return numberHash(b ? 433 : 863, initialHashVal); +} + +function stringHash(s: string, hashVal: number) { + hashVal = numberHash(149417, hashVal); + for (let i = 0, length = s.length; i < length; i++) { + hashVal = numberHash(s.charCodeAt(i), hashVal); + } + return hashVal; +} + +function arrayHash(arr: any[], initialHashVal: number): number { + initialHashVal = numberHash(104579, initialHashVal); + return arr.reduce((hashVal, item) => hash(item, hashVal), initialHashVal); +} + +function objectHash(obj: any, initialHashVal: number): number { + initialHashVal = numberHash(181387, initialHashVal); + return Object.keys(obj).sort().reduce((hashVal, key) => { + hashVal = stringHash(key, hashVal); + return hash(obj[key], hashVal); + }, initialHashVal); +} diff --git a/patched-vscode/extensions/typescript-language-features/web/src/fileWatcherManager.ts b/patched-vscode/extensions/typescript-language-features/web/src/fileWatcherManager.ts index 5bbce244..6ae4472e 100644 --- a/patched-vscode/extensions/typescript-language-features/web/src/fileWatcherManager.ts +++ b/patched-vscode/extensions/typescript-language-features/web/src/fileWatcherManager.ts @@ -53,9 +53,9 @@ export class FileWatcherManager { this.watchFiles.set(path, { callback, pollingInterval, options }); const watchIds = [++this.watchId]; this.watchPort.postMessage({ type: 'watchFile', uri: uri, id: watchIds[0] }); - if (this.enabledExperimentalTypeAcquisition && looksLikeNodeModules(path)) { + if (this.enabledExperimentalTypeAcquisition && looksLikeNodeModules(path) && uri.scheme !== 'vscode-global-typings') { watchIds.push(++this.watchId); - this.watchPort.postMessage({ type: 'watchFile', uri: mapUri(uri, 'vscode-node-modules'), id: watchIds[1] }); + this.watchPort.postMessage({ type: 'watchFile', uri: mapUri(uri, 'vscode-global-typings'), id: watchIds[1] }); } return { close: () => { diff --git a/patched-vscode/extensions/typescript-language-features/web/src/serverHost.ts b/patched-vscode/extensions/typescript-language-features/web/src/serverHost.ts index f2f9ca95..dedec859 100644 --- a/patched-vscode/extensions/typescript-language-features/web/src/serverHost.ts +++ b/patched-vscode/extensions/typescript-language-features/web/src/serverHost.ts @@ -11,6 +11,7 @@ import { FileWatcherManager } from './fileWatcherManager'; import { Logger } from './logging'; import { PathMapper, looksLikeNodeModules, mapUri } from './pathMapper'; import { findArgument, hasArgument } from './util/args'; +import { URI } from 'vscode-uri'; type ServerHostWithImport = ts.server.ServerHost & { importPlugin(root: string, moduleName: string): Promise }; @@ -338,13 +339,24 @@ function createServerHost( // For module resolution only. `node_modules` is also automatically mapped // as if all node_modules-like paths are symlinked. function realpath(path: string): string { + if (path.startsWith('/^/')) { + // In memory file. No mapping needed + return path; + } + const isNm = looksLikeNodeModules(path) && !path.startsWith('/vscode-global-typings/'); // skip paths without .. or ./ or /. And things that look like node_modules if (!isNm && !path.match(/\.\.|\/\.|\.\//)) { return path; } - let uri = pathMapper.toResource(path); + let uri: URI; + try { + uri = pathMapper.toResource(path); + } catch { + return path; + } + if (isNm) { uri = mapUri(uri, 'vscode-node-modules'); } diff --git a/patched-vscode/extensions/typescript-language-features/web/src/typingsInstaller/typingsInstaller.ts b/patched-vscode/extensions/typescript-language-features/web/src/typingsInstaller/typingsInstaller.ts index 7c40993d..1f7790dc 100644 --- a/patched-vscode/extensions/typescript-language-features/web/src/typingsInstaller/typingsInstaller.ts +++ b/patched-vscode/extensions/typescript-language-features/web/src/typingsInstaller/typingsInstaller.ts @@ -70,10 +70,12 @@ export class WebTypingsInstallerClient implements ts.server.ITypingsInstaller { break; case 'event::beginInstallTypes': case 'event::endInstallTypes': + // TODO(@zkat): maybe do something with this? + case 'action::watchTypingLocations': // Don't care. break; default: - throw new Error(`unexpected response: ${response}`); + throw new Error(`unexpected response: ${JSON.stringify(response)}`); } } diff --git a/patched-vscode/extensions/vscode-api-tests/package.json b/patched-vscode/extensions/vscode-api-tests/package.json index 868f7cfd..8a1032fe 100644 --- a/patched-vscode/extensions/vscode-api-tests/package.json +++ b/patched-vscode/extensions/vscode-api-tests/package.json @@ -7,13 +7,14 @@ "enabledApiProposals": [ "activeComment", "authSession", - "defaultChatParticipant", "chatParticipantPrivate", + "chatProvider", "chatVariableResolver", - "contribViewsRemote", "contribStatusBarItems", + "contribViewsRemote", "createFileSystemWatcher", "customEditorMove", + "defaultChatParticipant", "diffCommand", "documentFiltersExclusive", "documentPaste", @@ -27,6 +28,8 @@ "findTextInFiles", "fsChunks", "interactive", + "languageStatusText", + "lmTools", "mappedEditsProvider", "notebookCellExecutionState", "notebookDeprecated", @@ -35,25 +38,23 @@ "notebookMime", "portsAttributes", "quickPickSortByLabel", - "languageStatusText", "resolvers", "scmActionButton", "scmSelectedProvider", "scmTextDocument", "scmValidation", "taskPresentationGroup", + "telemetry", "terminalDataWriteEvent", "terminalDimensions", - "terminalShellIntegration", - "tunnels", "testObserver", "textSearchProvider", "timeline", "tokenInformation", "treeViewActiveItem", "treeViewReveal", - "workspaceTrust", - "telemetry" + "tunnels", + "workspaceTrust" ], "private": true, "activationEvents": [], @@ -63,6 +64,11 @@ }, "icon": "media/icon.png", "contributes": { + "languageModels": [ + { + "vendor": "test-lm-vendor" + } + ], "chatParticipants": [ { "id": "api-test.participant", @@ -105,6 +111,13 @@ "farboo.get": { "type": "string", "default": "get-prop" + }, + "integration-test.http.proxy": { + "type": "string" + }, + "integration-test.http.proxyAuth": { + "type": "string", + "default": "get-prop" } } }, @@ -242,7 +255,10 @@ }, "devDependencies": { "@types/mocha": "^9.1.1", - "@types/node": "20.x" + "@types/node": "20.x", + "@types/node-forge": "^1.3.11", + "node-forge": "^1.3.1", + "straightforward": "^4.2.2" }, "repository": { "type": "git", diff --git a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index ca72f39f..2b1110c2 100644 --- a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import { ChatContext, ChatRequest, ChatResult, ChatVariableLevel, Disposable, Event, EventEmitter, chat, commands } from 'vscode'; -import { DeferredPromise, asPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils'; +import { DeferredPromise, asPromise, assertNoRpc, closeAllEditors, delay, disposeAll } from '../utils'; suite('chat', () => { @@ -36,7 +36,6 @@ suite('chat', () => { const participant = chat.createChatParticipant(id, (request, context, _progress, _token) => { emitter.fire({ request, context }); }); - participant.isDefault = true; disposables.push(participant); return emitter.event; } @@ -89,7 +88,6 @@ suite('chat', () => { const participant = chat.createChatParticipant('api-test.participant', (_request, _context, _progress, _token) => { return { metadata: { key: 'value' } }; }); - participant.isDefault = true; participant.followupProvider = { provideFollowups(result, _context, _token) { deferred.complete(result); @@ -123,4 +121,33 @@ suite('chat', () => { const request3 = await asPromise(onRequest2); assert.strictEqual(request3.context.history.length, 2); // request + response = 2 }); + + test('title provider is called for first request', async () => { + let calls = 0; + const deferred = new DeferredPromise(); + const participant = chat.createChatParticipant('api-test.participant', (_request, _context, _progress, _token) => { + return { metadata: { key: 'value' } }; + }); + participant.titleProvider = { + provideChatTitle(_context, _token) { + calls++; + deferred.complete(); + return 'title'; + } + }; + disposables.push(participant); + + await commands.executeCommand('workbench.action.chat.newChat'); + commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); + + // Wait for title provider to be called once + await deferred.p; + assert.strictEqual(calls, 1); + + commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); + await delay(500); + + // Title provider was not called again + assert.strictEqual(calls, 1); + }); }); diff --git a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts new file mode 100644 index 00000000..178119a1 --- /dev/null +++ b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; + + +suite('lm', function () { + + let disposables: vscode.Disposable[] = []; + + setup(function () { + disposables = []; + }); + + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + disposeAll(disposables); + }); + + + test('lm request and stream', async function () { + + let p: vscode.Progress | undefined; + const defer = new DeferredPromise(); + + disposables.push(vscode.lm.registerChatModelProvider('test-lm', { + async provideLanguageModelResponse(_messages, _options, _extensionId, progress, _token) { + p = progress; + return defer.p; + }, + async provideTokenCount(_text, _token) { + return 1; + }, + }, { + name: 'test-lm', + version: '1.0.0', + family: 'test', + vendor: 'test-lm-vendor', + maxInputTokens: 100, + maxOutputTokens: 100, + })); + + const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); + assert.strictEqual(models.length, 1); + + const request = await models[0].sendRequest([vscode.LanguageModelChatMessage.User('Hello')]); + + // assert we have a request immediately + assert.ok(request); + assert.ok(p); + assert.strictEqual(defer.isSettled, false); + + let streamDone = false; + let responseText = ''; + + const pp = (async () => { + for await (const chunk of request.text) { + responseText += chunk; + } + streamDone = true; + })(); + + assert.strictEqual(responseText, ''); + assert.strictEqual(streamDone, false); + + p.report({ index: 0, part: 'Hello' }); + defer.complete(); + + await pp; + await new Promise(r => setTimeout(r, 1000)); + + assert.strictEqual(streamDone, true); + assert.strictEqual(responseText, 'Hello'); + }); + + test('lm request fail', async function () { + + disposables.push(vscode.lm.registerChatModelProvider('test-lm', { + async provideLanguageModelResponse(_messages, _options, _extensionId, _progress, _token) { + throw new Error('BAD'); + }, + async provideTokenCount(_text, _token) { + return 1; + }, + }, { + name: 'test-lm', + version: '1.0.0', + family: 'test', + vendor: 'test-lm-vendor', + maxInputTokens: 100, + maxOutputTokens: 100, + })); + + const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); + assert.strictEqual(models.length, 1); + + try { + await models[0].sendRequest([vscode.LanguageModelChatMessage.User('Hello')]); + assert.ok(false, 'EXPECTED error'); + } catch (error) { + assert.ok(error instanceof Error); + } + }); + + test('lm stream fail', async function () { + + const defer = new DeferredPromise(); + + disposables.push(vscode.lm.registerChatModelProvider('test-lm', { + async provideLanguageModelResponse(_messages, _options, _extensionId, _progress, _token) { + return defer.p; + }, + async provideTokenCount(_text, _token) { + return 1; + }, + }, { + name: 'test-lm', + version: '1.0.0', + family: 'test', + vendor: 'test-lm-vendor', + maxInputTokens: 100, + maxOutputTokens: 100, + })); + + const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); + assert.strictEqual(models.length, 1); + + const res = await models[0].sendRequest([vscode.LanguageModelChatMessage.User('Hello')]); + assert.ok(res); + + const result = (async () => { + for await (const _chunk of res.text) { + + } + })(); + + defer.error(new Error('STREAM FAIL')); + + try { + await result; + assert.ok(false, 'EXPECTED error'); + } catch (error) { + assert.ok(error); + // assert.ok(error instanceof Error); // todo@jrieken requires one more insiders + } + }); +}); diff --git a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts index f9d8d6a8..e4489090 100644 --- a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts +++ b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts @@ -18,6 +18,10 @@ async function openRandomNotebookDocument() { return vscode.workspace.openNotebookDocument(uri); } +async function openUntitledNotebookDocument(data?: vscode.NotebookData) { + return vscode.workspace.openNotebookDocument('notebookCoreTest', data); +} + export async function saveAllFilesAndCloseAll() { await saveAllEditors(); await closeAllEditors(); @@ -147,6 +151,7 @@ const apiTestSerializer: vscode.NotebookSerializer = { teardown(async function () { disposeAll(testDisposables); testDisposables.length = 0; + await revertAllDirty(); await saveAllFilesAndCloseAll(); }); @@ -188,6 +193,27 @@ const apiTestSerializer: vscode.NotebookSerializer = { assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); }); + test('Opening an utitled notebook without content will only open the editor when shown.', async function () { + const document = await openUntitledNotebookDocument(); + + assert.strictEqual(vscode.window.activeNotebookEditor, undefined); + + // opening a cell-uri opens a notebook editor + await vscode.window.showNotebookDocument(document); + + assert.strictEqual(!!vscode.window.activeNotebookEditor, true); + assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); + }); + + test('Opening an untitled notebook with content will open a dirty document.', async function () { + const language = 'python'; + const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', language); + const data = new vscode.NotebookData([cell]); + const doc = await vscode.workspace.openNotebookDocument('jupyter-notebook', data); + + assert.strictEqual(doc.isDirty, true); + }); + test('Cannot open notebook from cell-uri with vscode.open-command', async function () { const document = await openRandomNotebookDocument(); diff --git a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts new file mode 100644 index 00000000..ccda85b4 --- /dev/null +++ b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/proxy.test.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as https from 'https'; +import 'mocha'; +import { assertNoRpc, delay } from '../utils'; +import { pki } from 'node-forge'; +import { AddressInfo } from 'net'; +import { resetCaches } from '@vscode/proxy-agent'; +import * as vscode from 'vscode'; +import { middleware, Straightforward } from 'straightforward'; + +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('vscode API - network proxy support', () => { + + teardown(async function () { + assertNoRpc(); + }); + + test('custom root certificate', async () => { + const keys = pki.rsa.generateKeyPair(2048); + const cert = pki.createCertificate(); + cert.publicKey = keys.publicKey; + cert.serialNumber = '01'; + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); + const attrs = [{ + name: 'commonName', + value: 'localhost-proxy-test' + }]; + cert.setSubject(attrs); + cert.setIssuer(attrs); + cert.sign(keys.privateKey); + const certPEM = pki.certificateToPem(cert); + const privateKeyPEM = pki.privateKeyToPem(keys.privateKey); + + let resolvePort: (port: number) => void; + let rejectPort: (err: any) => void; + const port = new Promise((resolve, reject) => { + resolvePort = resolve; + rejectPort = reject; + }); + const server = https.createServer({ + key: privateKeyPEM, + cert: certPEM, + }, (_req, res) => { + res.end(); + }).listen(0, '127.0.0.1', () => { + const address = server.address(); + resolvePort((address as AddressInfo).port); + }).on('error', err => { + rejectPort(err); + }); + + // Using https.globalAgent because it is shared with proxyResolver.ts and mutable. + (https.globalAgent as any).testCertificates = [certPEM]; + resetCaches(); + + try { + const portNumber = await port; + await new Promise((resolve, reject) => { + https.get(`https://127.0.0.1:${portNumber}`, { servername: 'localhost-proxy-test' }, res => { + if (res.statusCode === 200) { + resolve(); + } else { + reject(new Error(`Unexpected status code: ${res.statusCode}`)); + } + }) + .on('error', reject); + }); + } finally { + delete (https.globalAgent as any).testCertificates; + resetCaches(); + server.close(); + } + }); + + test('basic auth', async () => { + const url = 'https://example.com'; // Need to use non-local URL because local URLs are excepted from proxying. + const user = 'testuser'; + const pass = 'testpassword'; + + const sf = new Straightforward(); + let authEnabled = false; + const auth = middleware.auth({ user, pass }); + sf.onConnect.use(async (context, next) => { + if (authEnabled) { + return auth(context, next); + } + next(); + }); + sf.onConnect.use(({ clientSocket }) => { + // Shortcircuit the request. + if (authEnabled) { + clientSocket.end('HTTP/1.1 204\r\n\r\n'); + } else { + clientSocket.end('HTTP/1.1 418\r\n\r\n'); + } + }); + const proxyListen = sf.listen(0); + + try { + await proxyListen; + const proxyPort = (sf.server.address() as AddressInfo).port; + + await vscode.workspace.getConfiguration().update('integration-test.http.proxy', `PROXY 127.0.0.1:${proxyPort}`, vscode.ConfigurationTarget.Global); + await delay(1000); // Wait for the configuration change to propagate. + await new Promise((resolve, reject) => { + https.get(url, res => { + if (res.statusCode === 418) { + resolve(); + } else { + reject(new Error(`Unexpected status code (expected 418): ${res.statusCode}`)); + } + }) + .on('error', reject); + }); + + authEnabled = true; + await new Promise((resolve, reject) => { + https.get(url, res => { + if (res.statusCode === 407) { + resolve(); + } else { + reject(new Error(`Unexpected status code (expected 407): ${res.statusCode}`)); + } + }) + .on('error', reject); + }); + + await vscode.workspace.getConfiguration().update('integration-test.http.proxyAuth', `${user}:${pass}`, vscode.ConfigurationTarget.Global); + await delay(1000); // Wait for the configuration change to propagate. + await new Promise((resolve, reject) => { + https.get(url, res => { + if (res.statusCode === 204) { + resolve(); + } else { + reject(new Error(`Unexpected status code (expected 204): ${res.statusCode}`)); + } + }) + .on('error', reject); + }); + } finally { + sf.close(); + await vscode.workspace.getConfiguration().update('integration-test.http.proxy', undefined, vscode.ConfigurationTarget.Global); + await vscode.workspace.getConfiguration().update('integration-test.http.proxyAuth', undefined, vscode.ConfigurationTarget.Global); + } + }); +}); diff --git a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/terminal.shellIntegration.test.ts b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/terminal.shellIntegration.test.ts index a5a83c8b..ac6287c2 100644 --- a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/terminal.shellIntegration.test.ts +++ b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/terminal.shellIntegration.test.ts @@ -10,7 +10,8 @@ import { assertNoRpc } from '../utils'; // Terminal integration tests are disabled on web https://github.com/microsoft/vscode/issues/92826 // Windows images will often not have functional shell integration -(env.uiKind === UIKind.Web || platform() === 'win32' ? suite.skip : suite)('vscode API - Terminal.shellIntegration', () => { +// TODO: Linux https://github.com/microsoft/vscode/issues/221399 +(env.uiKind === UIKind.Web || platform() === 'win32' || platform() === 'linux' ? suite.skip : suite)('vscode API - Terminal.shellIntegration', () => { const disposables: Disposable[] = []; suiteSetup(async () => { diff --git a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 2eb11576..e5d9af57 100644 --- a/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/patched-vscode/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -619,6 +619,7 @@ suite('vscode API - workspace', () => { test('findFiles2, exclude', () => { return vscode.workspace.findFiles2('**/image.png', { exclude: '**/sub/**' }).then((res) => { + res.forEach(r => console.log(r.toString())); assert.strictEqual(res.length, 1); }); }); diff --git a/patched-vscode/extensions/vscode-api-tests/yarn.lock b/patched-vscode/extensions/vscode-api-tests/yarn.lock index 484fa0c5..9999a40a 100644 --- a/patched-vscode/extensions/vscode-api-tests/yarn.lock +++ b/patched-vscode/extensions/vscode-api-tests/yarn.lock @@ -7,6 +7,20 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== +"@types/node-forge@^1.3.11": + version "1.3.11" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "20.14.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.6.tgz#f3c19ffc98c2220e18de259bb172dd4d892a6075" + integrity sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw== + dependencies: + undici-types "~5.26.4" + "@types/node@20.x": version "20.11.24" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.24.tgz#cc207511104694e84e9fb17f9a0c4c42d4517792" @@ -14,7 +28,138 @@ dependencies: undici-types "~5.26.4" +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +straightforward@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/straightforward/-/straightforward-4.2.2.tgz#a7d99b313dec5c04b0c637c7b8684dd44dc9167c" + integrity sha512-MxfuNnyTP4RPjadI3DkYIcNIp0DMXeDmAXY4/6QivU8lLIPGUqaS5VsEkaQ2QC+FICzc7QTb/lJPRIhGRKVuMA== + dependencies: + debug "^4.3.4" + yargs "^17.6.2" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.6.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" diff --git a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-1550_yaml.json b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-1550_yaml.json index dac84162..28483b62 100644 --- a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-1550_yaml.json +++ b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-1550_yaml.json @@ -279,4 +279,4 @@ "light_modern": "string.unquoted.plain.out.yaml: #0000FF" } } -] \ No newline at end of file +] diff --git a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-4008_yaml.json b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-4008_yaml.json index e1aa82e9..c02f7f46 100644 --- a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-4008_yaml.json +++ b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-4008_yaml.json @@ -335,4 +335,4 @@ "light_modern": "constant.numeric: #098658" } } -] \ No newline at end of file +] diff --git a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-6303_yaml.json b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-6303_yaml.json index 2066b677..efef65e1 100644 --- a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-6303_yaml.json +++ b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/issue-6303_yaml.json @@ -391,4 +391,4 @@ "light_modern": "string.unquoted.plain.out.yaml: #0000FF" } } -] \ No newline at end of file +] diff --git a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_less.json b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_less.json index 856f0135..3381f644 100644 --- a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_less.json +++ b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_less.json @@ -686,7 +686,7 @@ } }, { - "c": " 5px", + "c": " ", "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less", "r": { "dark_plus": "default: #D4D4D4", @@ -699,6 +699,34 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "5", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "px", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, { "c": ")", "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less punctuation.definition.group.end.less", diff --git a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test_less.json b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test_less.json index 08d8b9f4..a66224dd 100644 --- a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test_less.json +++ b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test_less.json @@ -897,16 +897,16 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.less meta.property-value.less", + "t": "source.css.less meta.property-list.less meta.property-value.less variable.other.readwrite.less", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "dark_plus": "source.css variable: #9CDCFE", + "light_plus": "source.css variable: #E50000", + "dark_vs": "source.css variable: #9CDCFE", + "light_vs": "source.css variable: #E50000", + "hc_black": "source.css variable: #D4D4D4", + "dark_modern": "source.css variable: #9CDCFE", + "hc_light": "source.css variable: #264F78", + "light_modern": "source.css variable: #E50000" } }, { diff --git a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test_yaml.json b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test_yaml.json index 407cc7c7..78a12871 100644 --- a/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test_yaml.json +++ b/patched-vscode/extensions/vscode-colorize-tests/test/colorize-results/test_yaml.json @@ -1441,4 +1441,4 @@ "light_modern": "string.unquoted.plain.out.yaml: #0000FF" } } -] \ No newline at end of file +] diff --git a/patched-vscode/extensions/vscode-test-resolver/src/extension.ts b/patched-vscode/extensions/vscode-test-resolver/src/extension.ts index 8e12e622..2fab3ec3 100644 --- a/patched-vscode/extensions/vscode-test-resolver/src/extension.ts +++ b/patched-vscode/extensions/vscode-test-resolver/src/extension.ts @@ -164,8 +164,8 @@ export function activate(context: vscode.ExtensionContext) { const serverCommandPath = path.join(vscodePath, 'scripts', serverCommand); outputChannel.appendLine(`Launching server: "${serverCommandPath}" ${commandArgs.join(' ')}`); - - extHostProcess = cp.spawn(serverCommandPath, commandArgs, { env, cwd: vscodePath }); + const shell = (process.platform === 'win32'); + extHostProcess = cp.spawn(serverCommandPath, commandArgs, { env, cwd: vscodePath, shell }); } else { const extensionToInstall = process.env['TESTRESOLVER_INSTALL_BUILTIN_EXTENSION']; if (extensionToInstall) { @@ -182,8 +182,8 @@ export function activate(context: vscode.ExtensionContext) { outputChannel.appendLine(`Using server build at ${serverLocation}`); outputChannel.appendLine(`Server arguments ${commandArgs.join(' ')}`); - - extHostProcess = cp.spawn(path.join(serverLocation, 'bin', serverCommand), commandArgs, { env, cwd: serverLocation }); + const shell = (process.platform === 'win32'); + extHostProcess = cp.spawn(path.join(serverLocation, 'bin', serverCommand), commandArgs, { env, cwd: serverLocation, shell }); } extHostProcess.stdout!.on('data', (data: Buffer) => processOutput(data.toString())); extHostProcess.stderr!.on('data', (data: Buffer) => processOutput(data.toString())); diff --git a/patched-vscode/extensions/yaml/cgmanifest.json b/patched-vscode/extensions/yaml/cgmanifest.json index e6c3ca15..80ba8ae5 100644 --- a/patched-vscode/extensions/yaml/cgmanifest.json +++ b/patched-vscode/extensions/yaml/cgmanifest.json @@ -34,4 +34,4 @@ } ], "version": 1 -} \ No newline at end of file +} diff --git a/patched-vscode/extensions/yaml/syntaxes/yaml.tmLanguage.json b/patched-vscode/extensions/yaml/syntaxes/yaml.tmLanguage.json index 447df713..c98b9811 100644 --- a/patched-vscode/extensions/yaml/syntaxes/yaml.tmLanguage.json +++ b/patched-vscode/extensions/yaml/syntaxes/yaml.tmLanguage.json @@ -618,4 +618,4 @@ ] } } -} \ No newline at end of file +} diff --git a/patched-vscode/extensions/yarn.lock b/patched-vscode/extensions/yarn.lock index fa4595ff..962d9f4b 100644 --- a/patched-vscode/extensions/yarn.lock +++ b/patched-vscode/extensions/yarn.lock @@ -2,120 +2,125 @@ # yarn lockfile v1 -"@esbuild/aix-ppc64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz#509621cca4e67caf0d18561a0c56f8b70237472f" - integrity sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw== - -"@esbuild/android-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz#109a6fdc4a2783fc26193d2687827045d8fef5ab" - integrity sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q== - -"@esbuild/android-arm@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.0.tgz#1397a2c54c476c4799f9b9073550ede496c94ba5" - integrity sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g== - -"@esbuild/android-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.0.tgz#2b615abefb50dc0a70ac313971102f4ce2fdb3ca" - integrity sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ== - -"@esbuild/darwin-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz#5c122ed799eb0c35b9d571097f77254964c276a2" - integrity sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ== - -"@esbuild/darwin-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz#9561d277002ba8caf1524f209de2b22e93d170c1" - integrity sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw== - -"@esbuild/freebsd-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz#84178986a3138e8500d17cc380044868176dd821" - integrity sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ== - -"@esbuild/freebsd-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz#3f9ce53344af2f08d178551cd475629147324a83" - integrity sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ== - -"@esbuild/linux-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz#24efa685515689df4ecbc13031fa0a9dda910a11" - integrity sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw== - -"@esbuild/linux-arm@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz#6b586a488e02e9b073a75a957f2952b3b6e87b4c" - integrity sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg== - -"@esbuild/linux-ia32@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz#84ce7864f762708dcebc1b123898a397dea13624" - integrity sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w== - -"@esbuild/linux-loong64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz#1922f571f4cae1958e3ad29439c563f7d4fd9037" - integrity sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw== - -"@esbuild/linux-mips64el@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz#7ca1bd9df3f874d18dbf46af009aebdb881188fe" - integrity sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ== - -"@esbuild/linux-ppc64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz#8f95baf05f9486343bceeb683703875d698708a4" - integrity sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw== - -"@esbuild/linux-riscv64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz#ca63b921d5fe315e28610deb0c195e79b1a262ca" - integrity sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA== - -"@esbuild/linux-s390x@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz#cb3d069f47dc202f785c997175f2307531371ef8" - integrity sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ== - -"@esbuild/linux-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz#ac617e0dc14e9758d3d7efd70288c14122557dc7" - integrity sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg== - -"@esbuild/netbsd-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz#6cc778567f1513da6e08060e0aeb41f82eb0f53c" - integrity sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ== - -"@esbuild/openbsd-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz#76848bcf76b4372574fb4d06cd0ed1fb29ec0fbe" - integrity sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA== - -"@esbuild/sunos-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz#ea4cd0639bf294ad51bc08ffbb2dac297e9b4706" - integrity sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g== - -"@esbuild/win32-arm64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz#a5c171e4a7f7e4e8be0e9947a65812c1535a7cf0" - integrity sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ== - -"@esbuild/win32-ia32@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz#f8ac5650c412d33ea62d7551e0caf82da52b7f85" - integrity sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg== - -"@esbuild/win32-x64@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz#2efddf82828aac85e64cef62482af61c29561bee" - integrity sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg== +"@esbuild/aix-ppc64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz#145b74d5e4a5223489cabdc238d8dad902df5259" + integrity sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ== + +"@esbuild/android-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz#453bbe079fc8d364d4c5545069e8260228559832" + integrity sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ== + +"@esbuild/android-arm@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.0.tgz#26c806853aa4a4f7e683e519cd9d68e201ebcf99" + integrity sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g== + +"@esbuild/android-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.0.tgz#1e51af9a6ac1f7143769f7ee58df5b274ed202e6" + integrity sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ== + +"@esbuild/darwin-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz#d996187a606c9534173ebd78c58098a44dd7ef9e" + integrity sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow== + +"@esbuild/darwin-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz#30c8f28a7ef4e32fe46501434ebe6b0912e9e86c" + integrity sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ== + +"@esbuild/freebsd-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz#30f4fcec8167c08a6e8af9fc14b66152232e7fb4" + integrity sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw== + +"@esbuild/freebsd-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz#1003a6668fe1f5d4439e6813e5b09a92981bc79d" + integrity sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ== + +"@esbuild/linux-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz#3b9a56abfb1410bb6c9138790f062587df3e6e3a" + integrity sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw== + +"@esbuild/linux-arm@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz#237a8548e3da2c48cd79ae339a588f03d1889aad" + integrity sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw== + +"@esbuild/linux-ia32@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz#4269cd19cb2de5de03a7ccfc8855dde3d284a238" + integrity sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA== + +"@esbuild/linux-loong64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz#82b568f5658a52580827cc891cb69d2cb4f86280" + integrity sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A== + +"@esbuild/linux-mips64el@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz#9a57386c926262ae9861c929a6023ed9d43f73e5" + integrity sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w== + +"@esbuild/linux-ppc64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz#f3a79fd636ba0c82285d227eb20ed8e31b4444f6" + integrity sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw== + +"@esbuild/linux-riscv64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz#f9d2ef8356ce6ce140f76029680558126b74c780" + integrity sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw== + +"@esbuild/linux-s390x@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz#45390f12e802201f38a0229e216a6aed4351dfe8" + integrity sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg== + +"@esbuild/linux-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz#c8409761996e3f6db29abcf9b05bee8d7d80e910" + integrity sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ== + +"@esbuild/netbsd-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz#ba70db0114380d5f6cfb9003f1d378ce989cd65c" + integrity sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw== + +"@esbuild/openbsd-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz#72fc55f0b189f7a882e3cf23f332370d69dfd5db" + integrity sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ== + +"@esbuild/openbsd-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz#b6ae7a0911c18fe30da3db1d6d17a497a550e5d8" + integrity sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg== + +"@esbuild/sunos-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz#58f0d5e55b9b21a086bfafaa29f62a3eb3470ad8" + integrity sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA== + +"@esbuild/win32-arm64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz#b858b2432edfad62e945d5c7c9e5ddd0f528ca6d" + integrity sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ== + +"@esbuild/win32-ia32@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz#167ef6ca22a476c6c0c014a58b4f43ae4b80dec7" + integrity sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA== + +"@esbuild/win32-x64@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz#db44a6a08520b5f25bbe409f34a59f2d4bcc7ced" + integrity sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g== "@parcel/watcher@2.1.0": version "2.1.0" @@ -128,11 +133,11 @@ node-gyp-build "^4.3.0" braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" coffeescript@1.12.7: version "1.12.7" @@ -146,44 +151,45 @@ cson-parser@^4.0.9: dependencies: coffeescript "1.12.7" -esbuild@0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.0.tgz#a7170b63447286cd2ff1f01579f09970e6965da4" - integrity sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA== +esbuild@0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.0.tgz#de06002d48424d9fdb7eb52dbe8e95927f852599" + integrity sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA== optionalDependencies: - "@esbuild/aix-ppc64" "0.20.0" - "@esbuild/android-arm" "0.20.0" - "@esbuild/android-arm64" "0.20.0" - "@esbuild/android-x64" "0.20.0" - "@esbuild/darwin-arm64" "0.20.0" - "@esbuild/darwin-x64" "0.20.0" - "@esbuild/freebsd-arm64" "0.20.0" - "@esbuild/freebsd-x64" "0.20.0" - "@esbuild/linux-arm" "0.20.0" - "@esbuild/linux-arm64" "0.20.0" - "@esbuild/linux-ia32" "0.20.0" - "@esbuild/linux-loong64" "0.20.0" - "@esbuild/linux-mips64el" "0.20.0" - "@esbuild/linux-ppc64" "0.20.0" - "@esbuild/linux-riscv64" "0.20.0" - "@esbuild/linux-s390x" "0.20.0" - "@esbuild/linux-x64" "0.20.0" - "@esbuild/netbsd-x64" "0.20.0" - "@esbuild/openbsd-x64" "0.20.0" - "@esbuild/sunos-x64" "0.20.0" - "@esbuild/win32-arm64" "0.20.0" - "@esbuild/win32-ia32" "0.20.0" - "@esbuild/win32-x64" "0.20.0" + "@esbuild/aix-ppc64" "0.23.0" + "@esbuild/android-arm" "0.23.0" + "@esbuild/android-arm64" "0.23.0" + "@esbuild/android-x64" "0.23.0" + "@esbuild/darwin-arm64" "0.23.0" + "@esbuild/darwin-x64" "0.23.0" + "@esbuild/freebsd-arm64" "0.23.0" + "@esbuild/freebsd-x64" "0.23.0" + "@esbuild/linux-arm" "0.23.0" + "@esbuild/linux-arm64" "0.23.0" + "@esbuild/linux-ia32" "0.23.0" + "@esbuild/linux-loong64" "0.23.0" + "@esbuild/linux-mips64el" "0.23.0" + "@esbuild/linux-ppc64" "0.23.0" + "@esbuild/linux-riscv64" "0.23.0" + "@esbuild/linux-s390x" "0.23.0" + "@esbuild/linux-x64" "0.23.0" + "@esbuild/netbsd-x64" "0.23.0" + "@esbuild/openbsd-arm64" "0.23.0" + "@esbuild/openbsd-x64" "0.23.0" + "@esbuild/sunos-x64" "0.23.0" + "@esbuild/win32-arm64" "0.23.0" + "@esbuild/win32-ia32" "0.23.0" + "@esbuild/win32-x64" "0.23.0" fast-plist@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -234,10 +240,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -typescript@5.4.5: - version "5.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" - integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +typescript@^5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== vscode-grammar-updater@^1.1.0: version "1.1.0" diff --git a/patched-vscode/migrate.mjs b/patched-vscode/migrate.mjs new file mode 100644 index 00000000..95b0e3d6 --- /dev/null +++ b/patched-vscode/migrate.mjs @@ -0,0 +1,364 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +// ***************************************************************** +// * * +// * AMD-TO-ESM MIGRATION SCRIPT * +// * * +// ***************************************************************** + +import { readFileSync, writeFileSync, unlinkSync } from 'node:fs'; +import { join, extname, dirname, relative } from 'node:path'; +import { preProcessFile } from 'typescript'; +import { existsSync, mkdirSync, readdirSync, statSync } from 'fs'; +import { fileURLToPath } from 'node:url'; + +// @ts-expect-error +import watch from './build/lib/watch/index.js'; + +const enableWatching = !process.argv.includes('--disable-watch'); +const enableInPlace = process.argv.includes('--enable-in-place'); +const esmToAmd = process.argv.includes('--enable-esm-to-amd'); +const amdToEsm = !esmToAmd; + +const srcFolder = fileURLToPath(new URL('src', import.meta.url)); +const dstFolder = fileURLToPath(new URL(enableInPlace ? 'src' : 'src2', import.meta.url)); + +const binaryFileExtensions = new Set([ + '.svg', '.ttf', '.png', '.sh', '.html', '.json', '.zsh', '.scpt', '.mp3', '.fish', '.ps1', '.psm1', '.md', '.txt', '.zip', '.pdf', '.qwoff', '.jxs', '.tst', '.wuff', '.less', '.utf16le', '.snap', '.actual', '.tsx', '.scm' +]); + +function migrate() { + console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`); + console.log(`STARTING ${amdToEsm ? 'AMD->ESM' : 'ESM->AMD'} MIGRATION of ${enableInPlace ? 'src in-place' : 'src to src2'}.`); + + // installing watcher quickly to avoid missing early events + const watchSrc = enableWatching ? watch('src/**', { base: 'src', readDelay: 200 }) : undefined; + + /** @type {string[]} */ + const files = []; + readdir(srcFolder, files); + + for (const filePath of files) { + const fileContents = readFileSync(filePath); + migrateOne(filePath, fileContents); + } + + if (amdToEsm) { + writeFileSync(join(dstFolder, 'package.json'), `{"type": "module"}`); + } else { + unlinkSync(join(dstFolder, 'package.json')); + } + + if (!enableInPlace) { + writeFileSync(join(dstFolder, '.gitignore'), `*`); + } + + console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`); + console.log(`COMPLETED ${amdToEsm ? 'AMD->ESM' : 'ESM->AMD'} MIGRATION of ${enableInPlace ? 'src in-place' : 'src to src2'}. You can now launch yarn watch-esm or yarn watch-client-esm`); + if (amdToEsm) { + console.log(`Make sure to set the environment variable VSCODE_BUILD_ESM to a string of value 'true' if you want to build VS Code`); + } + + if (watchSrc) { + console.log(`WATCHING src for changes...`); + + watchSrc.on('data', (e) => { + migrateOne(e.path, e.contents); + console.log(`Handled change event for ${e.path}.`); + }); + } +} + +/** + * @param filePath + * @param fileContents + */ +function migrateOne(filePath, fileContents) { + const fileExtension = extname(filePath); + + if (fileExtension === '.ts') { + migrateTS(filePath, fileContents.toString()); + } else if (filePath.endsWith('tsconfig.base.json')) { + const opts = JSON.parse(fileContents.toString()); + if (amdToEsm) { + opts.compilerOptions.module = 'es2022'; + opts.compilerOptions.allowSyntheticDefaultImports = true; + } else { + opts.compilerOptions.module = 'amd'; + delete opts.compilerOptions.allowSyntheticDefaultImports; + } + writeDestFile(filePath, JSON.stringify(opts, null, '\t')); + } else if (fileExtension === '.js' || fileExtension === '.cjs' || fileExtension === '.mjs' || fileExtension === '.css' || binaryFileExtensions.has(fileExtension)) { + writeDestFile(filePath, fileContents); + } else { + console.log(`ignoring ${filePath}`); + } +} + +/** + * @param fileContents + * @typedef {{pos:number;end:number;}} Import + * @return + */ +function discoverImports(fileContents) { + const info = preProcessFile(fileContents); + const search = /export .* from ['"]([^'"]+)['"]/g; + /** typedef {Import[]} */ + let result = []; + do { + const m = search.exec(fileContents); + if (!m) { + break; + } + const end = m.index + m[0].length - 2; + const pos = end - m[1].length; + result.push({ pos, end }); + } while (true); + + result = result.concat(info.importedFiles); + + result.sort((a, b) => { + return a.pos - b.pos; + }); + for (let i = 1; i < result.length; i++) { + const prev = result[i - 1]; + const curr = result[i]; + if (prev.pos === curr.pos) { + result.splice(i, 1); + i--; + } + } + return result; +} + +/** + * @param filePath + * @param fileContents + */ +function migrateTS(filePath, fileContents) { + if (filePath.endsWith('.d.ts')) { + return writeDestFile(filePath, fileContents); + } + + const imports = discoverImports(fileContents); + /** @type {Replacement[]} */ + const replacements = []; + for (let i = imports.length - 1; i >= 0; i--) { + const pos = imports[i].pos + 1; + const end = imports[i].end + 1; + const importedFilename = fileContents.substring(pos, end); + + /** @type {string|undefined} */ + let importedFilepath = undefined; + if (amdToEsm) { + if (/^vs\/css!/.test(importedFilename)) { + importedFilepath = importedFilename.substr('vs/css!'.length) + '.css'; + } else { + importedFilepath = importedFilename; + } + } else { + if (importedFilename.endsWith('.css')) { + importedFilepath = `vs/css!${importedFilename.substr(0, importedFilename.length - 4)}`; + } else if (importedFilename.endsWith('.js')) { + importedFilepath = importedFilename.substr(0, importedFilename.length - 3); + } + } + + if (typeof importedFilepath !== 'string') { + continue; + } + + /** @type {boolean} */ + let isRelativeImport; + if (amdToEsm) { + if (/(^\.\/)|(^\.\.\/)/.test(importedFilepath)) { + importedFilepath = join(dirname(filePath), importedFilepath); + isRelativeImport = true; + } else if (/^vs\//.test(importedFilepath)) { + importedFilepath = join(srcFolder, importedFilepath); + isRelativeImport = true; + } else { + importedFilepath = importedFilepath; + isRelativeImport = false; + } + } else { + importedFilepath = importedFilepath; + isRelativeImport = false; + } + + /** @type {string} */ + let replacementImport; + + if (isRelativeImport) { + replacementImport = generateRelativeImport(filePath, importedFilepath); + } else { + replacementImport = importedFilepath; + } + + replacements.push({ pos, end, text: replacementImport }); + } + + fileContents = applyReplacements(fileContents, replacements); + + writeDestFile(filePath, fileContents); +} + +/** + * @param filePath + * @param importedFilepath + */ +function generateRelativeImport(filePath, importedFilepath) { + /** @type {string} */ + let relativePath; + // See https://github.com/microsoft/TypeScript/issues/16577#issuecomment-754941937 + if (!importedFilepath.endsWith('.css') && !importedFilepath.endsWith('.cjs')) { + importedFilepath = `${importedFilepath}.js`; + } + relativePath = relative(dirname(filePath), `${importedFilepath}`); + relativePath = relativePath.replace(/\\/g, '/'); + if (!/(^\.\/)|(^\.\.\/)/.test(relativePath)) { + relativePath = './' + relativePath; + } + return relativePath; +} + +/** @typedef {{pos:number;end:number;text:string;}} Replacement */ + +/** + * @param str + * @param replacements + */ +function applyReplacements(str, replacements) { + replacements.sort((a, b) => { + return a.pos - b.pos; + }); + + /** @type {string[]} */ + const result = []; + let lastEnd = 0; + for (const replacement of replacements) { + const { pos, end, text } = replacement; + result.push(str.substring(lastEnd, pos)); + result.push(text); + lastEnd = end; + } + result.push(str.substring(lastEnd, str.length)); + return result.join(''); +} + +/** + * @param srcFilePath + * @param fileContents + */ +function writeDestFile(srcFilePath, fileContents) { + const destFilePath = srcFilePath.replace(srcFolder, dstFolder); + ensureDir(dirname(destFilePath)); + + if (/(\.ts$)|(\.js$)|(\.html$)/.test(destFilePath)) { + fileContents = toggleComments(fileContents); + } + + /** @type {Buffer | undefined} */ + let existingFileContents = undefined; + try { + existingFileContents = readFileSync(destFilePath); + } catch (err) { } + if (!buffersAreEqual(existingFileContents, fileContents)) { + writeFileSync(destFilePath, fileContents); + } + + /** + * @param fileContents + */ + function toggleComments(fileContents) { + const lines = String(fileContents).split(/\r\n|\r|\n/); + let mode = 0; + let didChange = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (mode === 0) { + if (amdToEsm ? /\/\/ ESM-comment-begin/.test(line) : /\/\/ ESM-uncomment-begin/.test(line)) { + mode = 1; + continue; + } + if (amdToEsm ? /\/\/ ESM-uncomment-begin/.test(line) : /\/\/ ESM-comment-begin/.test(line)) { + mode = 2; + continue; + } + continue; + } + + if (mode === 1) { + if (amdToEsm ? /\/\/ ESM-comment-end/.test(line) : /\/\/ ESM-uncomment-end/.test(line)) { + mode = 0; + continue; + } + didChange = true; + lines[i] = line.replace(/^\s*/, (match) => match + '// '); + continue; + } + + if (mode === 2) { + if (amdToEsm ? /\/\/ ESM-uncomment-end/.test(line) : /\/\/ ESM-comment-end/.test(line)) { + mode = 0; + continue; + } + didChange = true; + lines[i] = line.replace(/^(\s*)\/\/ ?/, function (_, indent) { + return indent; + }); + } + } + + if (didChange) { + return lines.join('\n'); + } + return fileContents; + } +} + +/** + * @param existingFileContents + * @param fileContents + */ +function buffersAreEqual(existingFileContents, fileContents) { + if (!existingFileContents) { + return false; + } + if (typeof fileContents === 'string') { + fileContents = Buffer.from(fileContents); + } + return existingFileContents.equals(fileContents); +} + +const ensureDirCache = new Set(); +function ensureDir(dirPath) { + if (ensureDirCache.has(dirPath)) { + return; + } + ensureDirCache.add(dirPath); + ensureDir(dirname(dirPath)); + if (!existsSync(dirPath)) { + mkdirSync(dirPath); + } +} + +function readdir(dirPath, result) { + const entries = readdirSync(dirPath); + for (const entry of entries) { + const entryPath = join(dirPath, entry); + const stat = statSync(entryPath); + if (stat.isDirectory()) { + readdir(join(dirPath, entry), result); + } else { + result.push(entryPath); + } + } +} + +migrate(); diff --git a/patched-vscode/package.json b/patched-vscode/package.json index 804d8a60..13fc4f4c 100644 --- a/patched-vscode/package.json +++ b/patched-vscode/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.90.1", - "distro": "4729d9dae7d87c18fbe0614f875a60672e2b8603", + "version": "1.93.1", + "distro": "2560b0e8d341a0b6734d28ef71b08e1920ff2501", "author": { "name": "Microsoft Corporation" }, @@ -11,13 +11,17 @@ "scripts": { "test": "echo Please run any of the test scripts from the scripts folder.", "test-browser": "npx playwright install && node test/unit/browser/index.js", + "test-browser-esm": "npx playwright install && node test/unit/browser/index.esm.js", "test-browser-no-install": "node test/unit/browser/index.js", + "test-browser-esm-no-install": "node test/unit/browser/index.esm.js", "test-node": "mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit", + "test-node-esm": "mocha test/unit/node/index.mjs --delay --ui=tdd --timeout=5000 --exit", "test-extension": "vscode-test", "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", "compile": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js compile", "watch": "npm-run-all -lp watch-client watch-extensions", + "watch-esm": "npm-run-all -lp watch-client-esm watch-extensions", "watchd": "deemon yarn watch", "watch-webd": "deemon yarn watch-web", "kill-watchd": "deemon --kill yarn watch", @@ -25,6 +29,7 @@ "restart-watchd": "deemon --restart yarn watch", "restart-watch-webd": "deemon --restart yarn watch-web", "watch-client": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-client", + "watch-client-esm": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-client-esm", "watch-clientd": "deemon yarn watch-client", "kill-watch-clientd": "deemon --kill yarn watch-client", "watch-extensions": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media", @@ -52,7 +57,7 @@ "watch-cli": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-cli", "eslint": "node build/eslint", "stylelint": "node build/stylelint", - "playwright-install": "node build/azure-pipelines/common/installPlaywright.js", + "playwright-install": "yarn playwright install", "compile-build": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js compile-build", "compile-extensions-build": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js compile-extensions-build", "minify-vscode": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode", @@ -73,46 +78,47 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", - "@vscode/proxy-agent": "^0.19.0", + "@vscode/proxy-agent": "^0.23.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/sqlite3": "5.1.6-vscode", "@vscode/sudo-prompt": "9.3.1", + "@vscode/tree-sitter-wasm": "^0.0.2", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-image": "0.9.0-beta.17", - "@xterm/addon-search": "0.16.0-beta.17", - "@xterm/addon-serialize": "0.14.0-beta.17", - "@xterm/addon-unicode11": "0.9.0-beta.17", - "@xterm/addon-webgl": "0.19.0-beta.17", - "@xterm/headless": "5.6.0-beta.17", - "@xterm/xterm": "5.6.0-beta.17", - "graceful-fs": "4.2.11", + "@xterm/addon-clipboard": "0.2.0-beta.35", + "@xterm/addon-image": "0.9.0-beta.52", + "@xterm/addon-search": "0.16.0-beta.52", + "@xterm/addon-serialize": "0.14.0-beta.52", + "@xterm/addon-unicode11": "0.9.0-beta.52", + "@xterm/addon-webgl": "0.19.0-beta.52", + "@xterm/headless": "5.6.0-beta.52", + "@xterm/xterm": "5.6.0-beta.52", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", - "jschardet": "3.0.0", - "kerberos": "^2.0.1", + "jschardet": "3.1.3", + "kerberos": "2.1.1", "minimist": "^1.2.6", "native-is-elevated": "0.7.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta11", + "node-pty": "1.1.0-beta21", + "open": "^8.4.2", "tas-client-umd": "0.2.0", "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.0.0", + "vscode-textmate": "9.1.0", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, "devDependencies": { - "@playwright/test": "^1.40.1", + "@playwright/test": "^1.46.1", "@swc/core": "1.3.62", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", - "@types/graceful-fs": "4.1.2", "@types/gulp-svgmin": "^1.2.1", "@types/http-proxy-agent": "^2.0.1", "@types/kerberos": "^1.1.2", @@ -136,8 +142,8 @@ "@vscode/l10n-dev": "0.0.35", "@vscode/telemetry-extractor": "^1.10.2", "@vscode/test-cli": "^0.0.6", - "@vscode/test-electron": "^2.3.8", - "@vscode/test-web": "^0.0.50", + "@vscode/test-electron": "^2.4.0", + "@vscode/test-web": "^0.0.56", "@vscode/v8-heap-parser": "^0.1.0", "@vscode/vscode-perf": "^0.0.14", "ansi-colors": "^3.2.3", @@ -149,7 +155,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "29.4.0", + "electron": "30.4.0", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", @@ -208,7 +214,8 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.5.0-dev.20240521", + "tslib": "^2.6.3", + "typescript": "^5.7.0-dev.20240826", "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", "webpack": "^5.91.0", diff --git a/patched-vscode/product.json b/patched-vscode/product.json index 124efc77..908be250 100644 --- a/patched-vscode/product.json +++ b/patched-vscode/product.json @@ -1,6 +1,6 @@ { - "nameShort": "SageMaker Code Editor", - "nameLong": "SageMaker Code Editor", + "nameShort": "Code - OSS", + "nameLong": "Code - OSS", "applicationName": "code-oss", "dataFolderName": ".vscode-oss", "win32MutexName": "vscodeoss", @@ -32,17 +32,53 @@ "urlProtocol": "code-oss", "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/ef65ac1ba57f57f2a3961bfe94aa20481caca4c6/out/vs/workbench/contrib/webview/browser/pre/", "builtInExtensions": [ - ], - "extensionsGallery": { - "serviceUrl": "https://open-vsx.org/vscode/gallery", - "itemUrl": "https://open-vsx.org/vscode/item", - "resourceUrlTemplate": "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}", - "controlUrl": "", - "recommendationsUrl": "", - "nlsBaseUrl": "", - "publisherUrl": "" - }, - "linkProtectionTrustedDomains": [ - "https://open-vsx.org" + { + "name": "ms-vscode.js-debug-companion", + "version": "1.1.3", + "sha256": "7380a890787452f14b2db7835dfa94de538caf358ebc263f9d46dd68ac52de93", + "repo": "https://github.com/microsoft/vscode-js-debug-companion", + "metadata": { + "id": "99cb0b7f-7354-4278-b8da-6cc79972169d", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } + }, + { + "name": "ms-vscode.js-debug", + "version": "1.93.0", + "sha256": "9339cb8e6b77f554df54d79e71f533279cb76b0f9b04c207f633bfd507442b6a", + "repo": "https://github.com/microsoft/vscode-js-debug", + "metadata": { + "id": "25629058-ddac-4e17-abba-74678e126c5d", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } + }, + { + "name": "ms-vscode.vscode-js-profile-table", + "version": "1.0.9", + "sha256": "3b62ee4276a2bbea3fe230f94b1d5edd915b05966090ea56f882e1e0ab53e1a6", + "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", + "metadata": { + "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } + } ] } diff --git a/patched-vscode/remote/.yarnrc b/patched-vscode/remote/.yarnrc index 4c99388e..748c8d4a 100644 --- a/patched-vscode/remote/.yarnrc +++ b/patched-vscode/remote/.yarnrc @@ -1,5 +1,5 @@ disturl "https://nodejs.org/dist" -target "20.11.1" -ms_build_id "275039" +target "20.15.1" +ms_build_id "287145" runtime "node" build_from_source "true" diff --git a/patched-vscode/remote/package.json b/patched-vscode/remote/package.json index b84f5136..2fa234e6 100644 --- a/patched-vscode/remote/package.json +++ b/patched-vscode/remote/package.json @@ -8,32 +8,33 @@ "@parcel/watcher": "2.1.0", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.19.0", + "@vscode/proxy-agent": "^0.23.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", + "@vscode/tree-sitter-wasm": "^0.0.2", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-image": "0.9.0-beta.17", - "@xterm/addon-search": "0.16.0-beta.17", - "@xterm/addon-serialize": "0.14.0-beta.17", - "@xterm/addon-unicode11": "0.9.0-beta.17", - "@xterm/addon-webgl": "0.19.0-beta.17", - "@xterm/headless": "5.6.0-beta.17", - "@xterm/xterm": "5.6.0-beta.17", + "@xterm/addon-clipboard": "0.2.0-beta.35", + "@xterm/addon-image": "0.9.0-beta.52", + "@xterm/addon-search": "0.16.0-beta.52", + "@xterm/addon-serialize": "0.14.0-beta.52", + "@xterm/addon-unicode11": "0.9.0-beta.52", + "@xterm/addon-webgl": "0.19.0-beta.52", + "@xterm/headless": "5.6.0-beta.52", + "@xterm/xterm": "5.6.0-beta.52", "cookie": "^0.4.0", - "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", - "jschardet": "3.0.0", - "kerberos": "^2.0.1", + "jschardet": "3.1.3", + "kerberos": "2.1.1", "minimist": "^1.2.6", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta11", + "node-pty": "1.1.0-beta21", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "9.0.0", + "vscode-textmate": "9.1.0", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, diff --git a/patched-vscode/remote/web/package.json b/patched-vscode/remote/web/package.json index 76375094..1c75df5e 100644 --- a/patched-vscode/remote/web/package.json +++ b/patched-vscode/remote/web/package.json @@ -6,16 +6,18 @@ "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/tree-sitter-wasm": "^0.0.2", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-image": "0.9.0-beta.17", - "@xterm/addon-search": "0.16.0-beta.17", - "@xterm/addon-serialize": "0.14.0-beta.17", - "@xterm/addon-unicode11": "0.9.0-beta.17", - "@xterm/addon-webgl": "0.19.0-beta.17", - "@xterm/xterm": "5.6.0-beta.17", - "jschardet": "3.0.0", + "@xterm/addon-clipboard": "0.2.0-beta.35", + "@xterm/addon-image": "0.9.0-beta.52", + "@xterm/addon-search": "0.16.0-beta.52", + "@xterm/addon-serialize": "0.14.0-beta.52", + "@xterm/addon-unicode11": "0.9.0-beta.52", + "@xterm/addon-webgl": "0.19.0-beta.52", + "@xterm/xterm": "5.6.0-beta.52", + "jschardet": "3.1.3", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", - "vscode-textmate": "9.0.0" + "vscode-textmate": "9.1.0" } } diff --git a/patched-vscode/remote/web/yarn.lock b/patched-vscode/remote/web/yarn.lock index 81215235..519b2838 100644 --- a/patched-vscode/remote/web/yarn.lock +++ b/patched-vscode/remote/web/yarn.lock @@ -43,45 +43,62 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== +"@vscode/tree-sitter-wasm@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.0.2.tgz#da21541d343be69bb263e9380d165e3b164ec1f0" + integrity sha512-N57MR/kt4jR0H/TXeDsVYeJmvvUiK7avow0fjy+/EeKcyNBJcM2BFhj4XOAaaMbhGsOcIeSvJFouRWctXI7sKw== + "@vscode/vscode-languagedetection@1.0.21": version "1.0.21" resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== -"@xterm/addon-image@0.9.0-beta.17": - version "0.9.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.9.0-beta.17.tgz#343d0665a6060d4f893b4f2d32de6ccbbd00bb63" - integrity sha512-g0r2hpBcLABY5as4llsMP36RHtkWooEn7tf+7U0/hTndJoCAvs4uGDqZNQigFgeAM3lJ4PnRYh4lfnEh9bGt8A== - -"@xterm/addon-search@0.16.0-beta.17": - version "0.16.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.16.0-beta.17.tgz#7cb01c7f498405909d37040884ee22d1889a36d2" - integrity sha512-wBfxmWOeqG6HHHE5mVamDJ75zBdHC35ERNy5/aTpQsQsyxrnV0Ks76c8ZVTaTu9wyBCAyx7UmZT42Ot80khY/g== - -"@xterm/addon-serialize@0.14.0-beta.17": - version "0.14.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.17.tgz#1cb8e35c0d118060a807adb340624fa7f80dd9c5" - integrity sha512-/c3W39kdRgGGYDoYjXb5HrUC421qwPn6NryAT4WJuJWnyMtFbe2DPwKsTfHuCBPiPyovS3a9j950Md3O3YXDZA== - -"@xterm/addon-unicode11@0.9.0-beta.17": - version "0.9.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.17.tgz#b5558148029a796c6a6d78e2a8b7255f92a51530" - integrity sha512-z7v8uojFVrO1aLSWtnz5MzSrfWRT8phde7kh9ufqHLBv7YYtMHxlPVjSuW8PZ2h4eY1LOZf6icUAzrmyJmJ7Kg== - -"@xterm/addon-webgl@0.19.0-beta.17": - version "0.19.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.17.tgz#68ad9e68dd1cf581b391971de33f5c04966b0d8e" - integrity sha512-X8ObRgoZl7UZTgdndM+mpSO3hLzAhWKoXXrGvUQg/7XabRKAPrQ2XvdyZm04nYwibE6Tpit2h5kkxjlVqupIig== - -"@xterm/xterm@5.6.0-beta.17": - version "5.6.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.6.0-beta.17.tgz#67ce2e2ff45bd6cc9f26d455d5522c6c4a122ed9" - integrity sha512-+wAv8PhaGQSN9yXWIa8EFtT33pbrA4lZakMB1P05fr+DQ7zoH66QOAUoDY95uOf/4+S6Ihz8wzP2+FH8zETQEA== - -jschardet@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882" - integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ== +"@xterm/addon-clipboard@0.2.0-beta.35": + version "0.2.0-beta.35" + resolved "https://registry.yarnpkg.com/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.35.tgz#4c9b553ea63ce02a3c8075fea7df1637d52092ef" + integrity sha512-B8AulZEjsfvSEaLKp8oyRu7yJ7FJb5R3W0wpPbI/rOMVAuBwxDJsz0CxLvJUXnJX7OJwd5cjnyTnEcXJfMJycA== + dependencies: + js-base64 "^3.7.5" + +"@xterm/addon-image@0.9.0-beta.52": + version "0.9.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.9.0-beta.52.tgz#a3115a50f884e5ba2f8ce09118a3d7e833fceb7b" + integrity sha512-1fWhnCIvLeO0aQ3CKqkTB9ye1bUsocpgFdDOgmwfW4XhLXpvu+QcyMGQMtWJHt8JWBN2w0cgR9eyfKw7orN+9Q== + +"@xterm/addon-search@0.16.0-beta.52": + version "0.16.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.16.0-beta.52.tgz#f8c77629b95ceff7d6b93d95c4b085857f405470" + integrity sha512-ZLVh0O91dcjxCjrU3vadl+40Z/mBnYXhKNA58oU/dGWFtFxtUB9SaZoOUtBvnfDpQIloYAK6raC2AfVsKHzD8A== + +"@xterm/addon-serialize@0.14.0-beta.52": + version "0.14.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.52.tgz#19708cdd2895ddbd983b771ae9d14d7bc54cf7c9" + integrity sha512-1+ckKya1OURFmELH1Tjjoxz3Gnj78Dxj+NNRrEunfINkvyzaY+n8wT28FQxIlU5gJq+a0VGvlhNgTkMwgOn6aw== + +"@xterm/addon-unicode11@0.9.0-beta.52": + version "0.9.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.52.tgz#15afdf03c20d2e46b4eca68bd461eea71c8dd37f" + integrity sha512-5tZR/8c+vf0YNSYS6B9pEv8gyWWZpPYOf/BRQDkTGtYAnFf04MzggVE/U7tKUXGDzBhzwTPODq5qPNTX1xpGgw== + +"@xterm/addon-webgl@0.19.0-beta.52": + version "0.19.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.52.tgz#695b20a5fa88ff92e115624149592080fad59594" + integrity sha512-kbPO9iR166xW8qgRkYmKX2Vu0kQHXpxYLQ9jY/01e5kvNrI/rqRDV63FIq14ncOi7N3+dmTuUkjvbg8anCpuIw== + +"@xterm/xterm@5.6.0-beta.52": + version "5.6.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.6.0-beta.52.tgz#ed7456a8b78ea1d00431737d078b5ab0bafbcdfe" + integrity sha512-aZh8IBdZPb2N4NjTt/fQ/C90z/PM3Zxkfoqhmlsp5+v3Otmyw5kd3DepeHK1SFW/pz0/xdj4KFgX8t8Y2lDRbA== + +js-base64@^3.7.5: + version "3.7.7" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" + integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== + +jschardet@3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.1.3.tgz#10c2289fdae91a0aa9de8bba9c59055fd78898d3" + integrity sha512-Q1PKVMK/uu+yjdlobgWIYkUOCR1SqUmW9m/eUJNNj4zI2N12i25v8fYpVf+zCakQeaTdBdhnZTFbVIAVZIVVOg== tas-client-umd@0.2.0: version "0.2.0" @@ -93,7 +110,7 @@ vscode-oniguruma@1.7.0: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== -vscode-textmate@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" - integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== +vscode-textmate@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.1.0.tgz#656a6aa163a9578397ba810733952bedb2b47202" + integrity sha512-lxKSVp2DkFOx9RDAvpiYUrB9/KT1fAfi1aE8CBGstP8N7rLF+Seifj8kDA198X0mYj1CjQUC+81+nQf8CO0nVA== diff --git a/patched-vscode/remote/yarn.lock b/patched-vscode/remote/yarn.lock index 4241bf03..74b8c01c 100644 --- a/patched-vscode/remote/yarn.lock +++ b/patched-vscode/remote/yarn.lock @@ -66,10 +66,10 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/proxy-agent@^0.19.0": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.1.tgz#d9640d85df1c48885580b68bb4b2b54e17f5332c" - integrity sha512-cs1VOx6d5n69HhgzK0cWeyfudJt+9LdJi/vtgRRxxwisWKg4h83B3+EUJ4udF5SEkJgMBp3oU0jheZVt43ImnQ== +"@vscode/proxy-agent@^0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.23.0.tgz#1cdcf0b72c2180d705d4e304b807d9594845c90f" + integrity sha512-6lgxRrzURdWwBkk6TaB0+EYYjIWQXxry6GlVO0toBN8amIyVd3I7hLPKU9Xf+SOrLIrbdU0CLkmTOBHpPJLf/g== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" @@ -98,6 +98,11 @@ mkdirp "^1.0.4" node-addon-api "7.1.0" +"@vscode/tree-sitter-wasm@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.0.2.tgz#da21541d343be69bb263e9380d165e3b164ec1f0" + integrity sha512-N57MR/kt4jR0H/TXeDsVYeJmvvUiK7avow0fjy+/EeKcyNBJcM2BFhj4XOAaaMbhGsOcIeSvJFouRWctXI7sKw== + "@vscode/vscode-languagedetection@1.0.21": version "1.0.21" resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" @@ -122,40 +127,47 @@ resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.1.0.tgz#03dace7c29c46f658588b9885b9580e453ad21f9" integrity sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw== -"@xterm/addon-image@0.9.0-beta.17": - version "0.9.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.9.0-beta.17.tgz#343d0665a6060d4f893b4f2d32de6ccbbd00bb63" - integrity sha512-g0r2hpBcLABY5as4llsMP36RHtkWooEn7tf+7U0/hTndJoCAvs4uGDqZNQigFgeAM3lJ4PnRYh4lfnEh9bGt8A== - -"@xterm/addon-search@0.16.0-beta.17": - version "0.16.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.16.0-beta.17.tgz#7cb01c7f498405909d37040884ee22d1889a36d2" - integrity sha512-wBfxmWOeqG6HHHE5mVamDJ75zBdHC35ERNy5/aTpQsQsyxrnV0Ks76c8ZVTaTu9wyBCAyx7UmZT42Ot80khY/g== - -"@xterm/addon-serialize@0.14.0-beta.17": - version "0.14.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.17.tgz#1cb8e35c0d118060a807adb340624fa7f80dd9c5" - integrity sha512-/c3W39kdRgGGYDoYjXb5HrUC421qwPn6NryAT4WJuJWnyMtFbe2DPwKsTfHuCBPiPyovS3a9j950Md3O3YXDZA== - -"@xterm/addon-unicode11@0.9.0-beta.17": - version "0.9.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.17.tgz#b5558148029a796c6a6d78e2a8b7255f92a51530" - integrity sha512-z7v8uojFVrO1aLSWtnz5MzSrfWRT8phde7kh9ufqHLBv7YYtMHxlPVjSuW8PZ2h4eY1LOZf6icUAzrmyJmJ7Kg== - -"@xterm/addon-webgl@0.19.0-beta.17": - version "0.19.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.17.tgz#68ad9e68dd1cf581b391971de33f5c04966b0d8e" - integrity sha512-X8ObRgoZl7UZTgdndM+mpSO3hLzAhWKoXXrGvUQg/7XabRKAPrQ2XvdyZm04nYwibE6Tpit2h5kkxjlVqupIig== - -"@xterm/headless@5.6.0-beta.17": - version "5.6.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.6.0-beta.17.tgz#bff1d67c9c061c57adff22571e733d54e3aba2b7" - integrity sha512-ehS7y/XRqX1ppx4RPiYc0vu0SdIQ91aA4lSN/2XNOf3IGdP0A38Q7a0T6mzqxRGZKiiyA0kTR1szr78wnY+wmA== - -"@xterm/xterm@5.6.0-beta.17": - version "5.6.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.6.0-beta.17.tgz#67ce2e2ff45bd6cc9f26d455d5522c6c4a122ed9" - integrity sha512-+wAv8PhaGQSN9yXWIa8EFtT33pbrA4lZakMB1P05fr+DQ7zoH66QOAUoDY95uOf/4+S6Ihz8wzP2+FH8zETQEA== +"@xterm/addon-clipboard@0.2.0-beta.35": + version "0.2.0-beta.35" + resolved "https://registry.yarnpkg.com/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.35.tgz#4c9b553ea63ce02a3c8075fea7df1637d52092ef" + integrity sha512-B8AulZEjsfvSEaLKp8oyRu7yJ7FJb5R3W0wpPbI/rOMVAuBwxDJsz0CxLvJUXnJX7OJwd5cjnyTnEcXJfMJycA== + dependencies: + js-base64 "^3.7.5" + +"@xterm/addon-image@0.9.0-beta.52": + version "0.9.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.9.0-beta.52.tgz#a3115a50f884e5ba2f8ce09118a3d7e833fceb7b" + integrity sha512-1fWhnCIvLeO0aQ3CKqkTB9ye1bUsocpgFdDOgmwfW4XhLXpvu+QcyMGQMtWJHt8JWBN2w0cgR9eyfKw7orN+9Q== + +"@xterm/addon-search@0.16.0-beta.52": + version "0.16.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.16.0-beta.52.tgz#f8c77629b95ceff7d6b93d95c4b085857f405470" + integrity sha512-ZLVh0O91dcjxCjrU3vadl+40Z/mBnYXhKNA58oU/dGWFtFxtUB9SaZoOUtBvnfDpQIloYAK6raC2AfVsKHzD8A== + +"@xterm/addon-serialize@0.14.0-beta.52": + version "0.14.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.52.tgz#19708cdd2895ddbd983b771ae9d14d7bc54cf7c9" + integrity sha512-1+ckKya1OURFmELH1Tjjoxz3Gnj78Dxj+NNRrEunfINkvyzaY+n8wT28FQxIlU5gJq+a0VGvlhNgTkMwgOn6aw== + +"@xterm/addon-unicode11@0.9.0-beta.52": + version "0.9.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.52.tgz#15afdf03c20d2e46b4eca68bd461eea71c8dd37f" + integrity sha512-5tZR/8c+vf0YNSYS6B9pEv8gyWWZpPYOf/BRQDkTGtYAnFf04MzggVE/U7tKUXGDzBhzwTPODq5qPNTX1xpGgw== + +"@xterm/addon-webgl@0.19.0-beta.52": + version "0.19.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.52.tgz#695b20a5fa88ff92e115624149592080fad59594" + integrity sha512-kbPO9iR166xW8qgRkYmKX2Vu0kQHXpxYLQ9jY/01e5kvNrI/rqRDV63FIq14ncOi7N3+dmTuUkjvbg8anCpuIw== + +"@xterm/headless@5.6.0-beta.52": + version "5.6.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.6.0-beta.52.tgz#7f934a7d7c5bbf88e2d78c22317cd2464bc9638a" + integrity sha512-f4QITQwotblRLW6YOHnK801wHJWfFDnjD7jUEwaaAXtSp32xH3jguWnMP9/uuQX45q9ndjDjnm1t5aXX7gwqBQ== + +"@xterm/xterm@5.6.0-beta.52": + version "5.6.0-beta.52" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.6.0-beta.52.tgz#ed7456a8b78ea1d00431737d078b5ab0bafbcdfe" + integrity sha512-aZh8IBdZPb2N4NjTt/fQ/C90z/PM3Zxkfoqhmlsp5+v3Otmyw5kd3DepeHK1SFW/pz0/xdj4KFgX8t8Y2lDRbA== agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" @@ -164,6 +176,13 @@ agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: dependencies: debug "^4.3.4" +agent-base@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -185,12 +204,12 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" buffer-crc32@~0.2.3: version "0.2.13" @@ -263,10 +282,10 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -289,7 +308,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -graceful-fs@4.2.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0: +graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -325,10 +344,13 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -ip@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" - integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" is-extglob@^2.1.1: version "2.1.1" @@ -347,10 +369,20 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -jschardet@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882" - integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ== +js-base64@^3.7.5: + version "3.7.7" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" + integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== + +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +jschardet@3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.1.3.tgz#10c2289fdae91a0aa9de8bba9c59055fd78898d3" + integrity sha512-Q1PKVMK/uu+yjdlobgWIYkUOCR1SqUmW9m/eUJNNj4zI2N12i25v8fYpVf+zCakQeaTdBdhnZTFbVIAVZIVVOg== jsonfile@^6.0.1: version "6.1.0" @@ -361,14 +393,14 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -kerberos@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-2.0.1.tgz#663b0b46883b4da84495f60f2e9e399a43a33ef5" - integrity sha512-O/jIgbdGK566eUhFwIcgalbqirYU/r76MW7/UFw06Fd9x5bSwgyZWL/Vm26aAmezQww/G9KYkmmJBkEkPk5HLw== +kerberos@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-2.1.1.tgz#170fc64d1a23e8c6d6ae2736d01189e2a1dbe0f7" + integrity sha512-414s1G/qgK2T60cXnZsHbtRj8Ynjg0DBlQWeY99tkyqQ2e8vGgFHvxRdvjTlLHg/SxBA0zLQcGE6Pk6Dfq/BCA== dependencies: bindings "^1.5.0" - node-addon-api "^4.3.0" - prebuild-install "7.1.1" + node-addon-api "^6.1.0" + prebuild-install "^7.1.2" lru-cache@^6.0.0: version "6.0.0" @@ -378,11 +410,11 @@ lru-cache@^6.0.0: yallist "^4.0.0" micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mimic-response@^3.1.0: @@ -442,20 +474,20 @@ node-addon-api@^3.2.1: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== -node-addon-api@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" - integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-addon-api@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== node-gyp-build@4.8.1, node-gyp-build@^4.3.0: version "4.8.1" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw== -node-pty@1.1.0-beta11: - version "1.1.0-beta11" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta11.tgz#909d5dd8f9aa2a7857e7b632fd4d2d4768bdf69f" - integrity sha512-vTjF+VrvSCfPDILUkIT+YrG1Fdn06/eBRS2fc9a3JzYAvknMB1Ip8aoJhxl8hNpjWAbprmCEiV91mlfNpCD+GQ== +node-pty@1.1.0-beta21: + version "1.1.0-beta21" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta21.tgz#d75c1dfe4ff2c173459c974df72e91a4eae6dc52" + integrity sha512-FYpnY9g8qMQLTpqyeY9NVli6YfCWwvG6v6gmaDBbPjlc1VMp/+Zivq0SStDrRr1aciGnFCZzpL0BzdMnmbDnAw== dependencies: node-addon-api "^7.1.0" @@ -476,10 +508,10 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -prebuild-install@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" - integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== +prebuild-install@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== dependencies: detect-libc "^2.0.0" expand-template "^2.0.3" @@ -558,22 +590,27 @@ smart-buffer@^4.2.0: integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== socks-proxy-agent@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz#ffc5859a66dac89b0c4dab90253b96705f3e7120" - integrity sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ== + version "8.0.4" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz#9071dca17af95f483300316f4b063578fa0db08c" + integrity sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw== dependencies: - agent-base "^7.0.1" + agent-base "^7.1.1" debug "^4.3.4" - socks "^2.7.1" + socks "^2.8.3" -socks@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== +socks@^2.8.3: + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== dependencies: - ip "^2.0.0" + ip-address "^9.0.5" smart-buffer "^4.2.0" +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -651,10 +688,10 @@ vscode-regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f" integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg== -vscode-textmate@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" - integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== +vscode-textmate@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.1.0.tgz#656a6aa163a9578397ba810733952bedb2b47202" + integrity sha512-lxKSVp2DkFOx9RDAvpiYUrB9/KT1fAfi1aE8CBGstP8N7rLF+Seifj8kDA198X0mYj1CjQUC+81+nQf8CO0nVA== wrappy@1: version "1.0.2" diff --git a/patched-vscode/resources/linux/debian/postinst.template b/patched-vscode/resources/linux/debian/postinst.template index 16acb148..b292cff8 100755 --- a/patched-vscode/resources/linux/debian/postinst.template +++ b/patched-vscode/resources/linux/debian/postinst.template @@ -31,24 +31,54 @@ if [ "@@NAME@@" != "code-oss" ]; then # Register apt repository eval $(apt-config shell APT_SOURCE_PARTS Dir::Etc::sourceparts/d) CODE_SOURCE_PART=${APT_SOURCE_PARTS}vscode.list + CODE_SOURCE_PART_DEB822=${APT_SOURCE_PARTS}vscode.sources eval $(apt-config shell APT_TRUSTED_PARTS Dir::Etc::trustedparts/d) CODE_TRUSTED_PART=${APT_TRUSTED_PARTS}microsoft.gpg - # Install repository source list + RET=true + if [ -e '/usr/share/debconf/confmodule' ]; then + . /usr/share/debconf/confmodule + db_get @@NAME@@/add-microsoft-repo || true + fi + + # Determine whether to install the repository source list WRITE_SOURCE=0 - if [ ! -f $CODE_SOURCE_PART ] && [ ! -f /etc/rpi-issue ]; then - # Write source list if it does not exist and we're not running on Raspberry Pi OS - WRITE_SOURCE=1 - elif grep -Eq "http:\/\/packages\.microsoft\.com\/repos\/vscode" $CODE_SOURCE_PART; then + if [ "$RET" = false ]; then + # The user does not want to add the Microsoft repository + WRITE_SOURCE=0 + elif [ -f "$CODE_SOURCE_PART_DEB822" ]; then + # The user has migrated themselves to the DEB822 format + WRITE_SOURCE=0 + elif [ -f "$CODE_SOURCE_PART" ] && (grep -q "http://packages.microsoft.com/repos/vscode" $CODE_SOURCE_PART); then + # Migrate from old repository + WRITE_SOURCE=2 + elif [ -f "$CODE_SOURCE_PART" ] && (grep -q "http://packages.microsoft.com/repos/code" $CODE_SOURCE_PART); then # Migrate from old repository + WRITE_SOURCE=2 + elif apt-cache policy | grep -q "https://packages.microsoft.com/repos/code"; then + # The user is already on the new repository + WRITE_SOURCE=0 + elif [ ! -f $CODE_SOURCE_PART ] && [ ! -f /etc/rpi-issue ]; then + # Write source list if it does not exist and we're not running on Raspberry Pi OS WRITE_SOURCE=1 elif grep -q "# disabled on upgrade to" $CODE_SOURCE_PART; then # Write source list if it was disabled by OS upgrade WRITE_SOURCE=1 fi - if [ "$WRITE_SOURCE" -eq "1" ]; then + if [ "$WRITE_SOURCE" -eq "1" ] && [ -e '/usr/share/debconf/confmodule' ]; then + # Ask the user whether to actually write the source list + db_input high @@NAME@@/add-microsoft-repo || true + db_go || true + + db_get @@NAME@@/add-microsoft-repo + if [ "$RET" = false ]; then + WRITE_SOURCE=0 + fi + fi + + if [ "$WRITE_SOURCE" -ne "0" ]; then echo "### THIS FILE IS AUTOMATICALLY CONFIGURED ### # You may comment out this entry, but any other modifications may be lost. deb [arch=amd64,arm64,armhf] https://packages.microsoft.com/repos/code stable main" > $CODE_SOURCE_PART diff --git a/patched-vscode/resources/linux/debian/postrm.template b/patched-vscode/resources/linux/debian/postrm.template index fb36d522..dcbfda95 100755 --- a/patched-vscode/resources/linux/debian/postrm.template +++ b/patched-vscode/resources/linux/debian/postrm.template @@ -14,3 +14,22 @@ fi if hash update-mime-database 2>/dev/null; then update-mime-database /usr/share/mime fi + +RET=true +if [ -e '/usr/share/debconf/confmodule' ]; then + . /usr/share/debconf/confmodule + db_get @@NAME@@/add-microsoft-repo || true +fi +if [ "$RET" = "true" ]; then + eval $(apt-config shell APT_SOURCE_PARTS Dir::Etc::sourceparts/d) + CODE_SOURCE_PART=${APT_SOURCE_PARTS}vscode.list + rm -f $CODE_SOURCE_PART + + eval $(apt-config shell APT_TRUSTED_PARTS Dir::Etc::trustedparts/d) + CODE_TRUSTED_PART=${APT_TRUSTED_PARTS}microsoft.gpg + rm -f $CODE_TRUSTED_PART +fi + +if [ "$1" = "purge" ] && [ -e '/usr/share/debconf/confmodule' ]; then + db_purge +fi diff --git a/patched-vscode/resources/linux/debian/templates.template b/patched-vscode/resources/linux/debian/templates.template new file mode 100644 index 00000000..7be5e039 --- /dev/null +++ b/patched-vscode/resources/linux/debian/templates.template @@ -0,0 +1,6 @@ +Template: @@NAME@@/add-microsoft-repo +Type: boolean +Default: true +Description: Add Microsoft apt repository for Visual Studio Code? + The installer would like to add the Microsoft repository and signing + key to update VS Code through apt. diff --git a/patched-vscode/resources/linux/snap/electron-launch b/patched-vscode/resources/linux/snap/electron-launch index ea82e60b..873b0791 100755 --- a/patched-vscode/resources/linux/snap/electron-launch +++ b/patched-vscode/resources/linux/snap/electron-launch @@ -261,4 +261,4 @@ fi wait_for_async_execs -exec "$@" "--no-sandbox" "--use-gl=angle" "--use-angle=swiftshader" +exec "$@" diff --git a/patched-vscode/resources/linux/snap/snapcraft.yaml b/patched-vscode/resources/linux/snap/snapcraft.yaml index 5d4df3f5..1d7412bd 100644 --- a/patched-vscode/resources/linux/snap/snapcraft.yaml +++ b/patched-vscode/resources/linux/snap/snapcraft.yaml @@ -30,15 +30,18 @@ parts: - libcurl3-gnutls - libcurl3-nss - libcurl4 + - libegl1 - libdrm2 - libgbm1 - libgl1 + - libgles2 - libglib2.0-0 - libgtk-3-0 - libibus-1.0-5 - libnss3 - libpango-1.0-0 - libsecret-1-0 + - libwayland-egl1 - libxcomposite1 - libxdamage1 - libxfixes3 @@ -74,8 +77,8 @@ parts: apps: @@NAME@@: - command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ + command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --no-sandbox common-id: @@NAME@@.desktop url-handler: - command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --open-url + command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --open-url --no-sandbox diff --git a/patched-vscode/resources/server/bin/helpers/check-requirements-linux.sh b/patched-vscode/resources/server/bin/helpers/check-requirements-linux.sh index 07955786..31a618fb 100644 --- a/patched-vscode/resources/server/bin/helpers/check-requirements-linux.sh +++ b/patched-vscode/resources/server/bin/helpers/check-requirements-linux.sh @@ -27,6 +27,7 @@ fi ARCH=$(uname -m) found_required_glibc=0 found_required_glibcxx=0 +MIN_GLIBCXX_VERSION="3.4.25" # Extract the ID value from /etc/os-release if [ -f /etc/os-release ]; then @@ -40,7 +41,10 @@ fi # Based on https://github.com/bminor/glibc/blob/520b1df08de68a3de328b65a25b86300a7ddf512/elf/cache.c#L162-L245 case $ARCH in x86_64) LDCONFIG_ARCH="x86-64";; - armv7l | armv8l) LDCONFIG_ARCH="hard-float";; + armv7l | armv8l) + MIN_GLIBCXX_VERSION="3.4.26" + LDCONFIG_ARCH="hard-float" + ;; arm64 | aarch64) BITNESS=$(getconf LONG_BIT) if [ "$BITNESS" = "32" ]; then @@ -81,7 +85,7 @@ if [ "$OS_ID" != "alpine" ]; then libstdcpp_real_path=$(readlink -f "$libstdcpp_path_line") libstdcpp_version=$(grep -ao 'GLIBCXX_[0-9]*\.[0-9]*\.[0-9]*' "$libstdcpp_real_path" | sort -V | tail -1) libstdcpp_version_number=$(echo "$libstdcpp_version" | sed 's/GLIBCXX_//') - if [ "$(printf '%s\n' "3.4.24" "$libstdcpp_version_number" | sort -V | head -n1)" = "3.4.24" ]; then + if [ "$(printf '%s\n' "$MIN_GLIBCXX_VERSION" "$libstdcpp_version_number" | sort -V | head -n1)" = "$MIN_GLIBCXX_VERSION" ]; then found_required_glibcxx=1 break fi @@ -92,7 +96,7 @@ else found_required_glibcxx=1 fi if [ "$found_required_glibcxx" = "0" ]; then - echo "Warning: Missing GLIBCXX >= 3.4.25! from $libstdcpp_real_path" + echo "Warning: Missing GLIBCXX >= $MIN_GLIBCXX_VERSION! from $libstdcpp_real_path" fi if [ "$OS_ID" = "alpine" ]; then diff --git a/patched-vscode/resources/server/favicon.ico b/patched-vscode/resources/server/favicon.ico index 3e8cc9357b5ba643d6d6ecdcdb34a527c51c4090..f9f0de3eb46f9cf6cab16ce698bef2e6896f2dd8 100644 GIT binary patch literal 34494 zcmeHQy^CE%6dyqg8%dc;UZIdOohctCSf{d5NU48-jW%H`QLqW5*CbHre1GRkaR1Z4j~fm#{O@U-bBN)u$L3n1F%zOc+0mB!`rrTb>YCoL zok_>nAMLp9NpGTo{)?kWdw=}!RSQoK5B9XeEpFEFYqaI_xg_2+t2m=WmD1s88NZhN zb<8TA{#iWTTO1u)0rmvA>buH88?l7^#`9--j$n@R@=%=Ol&k$V(TA=)o&m?bz@>kT zeV!cLTAR0sUx?#<(09@BmB+|Wbjpj~=B(KkOmCHf}0qzAeHJnzjk%r)RS za+&Y|II^T=o!5kraftDT6w)?y45OVHCw%h?ZIK{{uU%{%=!MV}{_FPV_fL+S_am2I zQtglD$gV`s#4g?_-&FpvEw?wvhqS-XrZIJ?_9q_LlOP&hx$Sw1`Vc)=?wbGLADY06 z)6eB^+tY)rHozLXZ~mNKEWYXbkHP%0baPA-K!^=7D8vndqEZzry9`}4WmoJxp zZU5Ki4}1FTZPwZDN4fUr{*1*_`G0rgUF)8j`CfqD3HW46gfL_BdQz95|pjQ_T>3k+4sJcSYAU zwEGmVX++YfETCOgb)NRXzm2yPcSCaQRZ_w^gpfXcwn5CL6mn1Du^+Nf!aA1M4~?5^ zG?8EIA3NS;{A}m^#TwrB9@|%D+r#M^PeY9#$P$~NTbI1NUgWore~h2l`z+2Ta{G4S zL)pKJZ(sEyzkU30`hx4u5Lh5Y$Hvp(yFvVWg4`e>Q*!{@R081wxl&Y;Hj&utHxw;_${Fn|an7AS0i z)f}dCLxq`7fE~9l$8k<;uHm|d_M8?9B#8yY0<8t~ytzJcScQ%W|LyCX9XDafH+{af z{Ic$&OU>N@fWDD|%%j#{@0E4hD4~t$zxqa2{4e8Q=70P-Gh0j=B>pGFHk)j`OyXbp z_!sgWe8~P8_hkxR!~ZGo0Qj83`;QcTc^}cg{P-9D$Dcvprty#XmviO=eZRl)&c@Dv z9)oJ)HR4}QeK~K@zkK}XK8X1}82Bp`5GObzAm%oo z?;Ib){GYo2r~drI$7D%-4CB9~zMPlnU;qB!$9HhH{ZzzN?r)Bd!T(c_|DwOFe`3~k zp=~j@`F!X280J4@3VGih+s5_V1f~tfqQ5Cq$!pQS{{6qCzd5fO{>NG+xbV;Er`pf!aID|QScwD4ek;E literal 1150 zcmd^-y-EX75QRrjf`ypMPKcEjTKEM1f#4Ge7TPT~3;Vwdku)iMncxdp1#Gmjv(QFc ze?UR44Dp-I9k?uFFK#$FGv}N;d-v{&Oz~^Ch5wEmPm3&w$O=G0nds2JxUwwk=6Qau zD2ieW-mo6-{dNfW`(W&F9mnx(5Ck*y7p61w=%*Rz%VgeM?uC6USuRPEMf6AKebTR+ zr_9boe4^!3&k+Q6+mgT&8%JggKA3)FlxvHv;`R@FlocGSXR?YJn{lH~( z#$_0WEd}+Jryu%jnl-r|sJ|cPZ2~>_x`$7i`M=(e$0CMT-pGARWY`z!4@FKNCeT_n J^)zF7 !v.startsWith('--playground') && v !== '--no-playground')); diff --git a/patched-vscode/scripts/code.bat b/patched-vscode/scripts/code.bat index 008c54fc..7f48b753 100644 --- a/patched-vscode/scripts/code.bat +++ b/patched-vscode/scripts/code.bat @@ -23,9 +23,16 @@ set VSCODE_CLI=1 set ELECTRON_ENABLE_LOGGING=1 set ELECTRON_ENABLE_STACK_DUMPING=1 +set DISABLE_TEST_EXTENSION="--disable-extension=vscode.vscode-api-tests" +for %%A in (%*) do ( + if "%%~A"=="--extensionTestsPath" ( + set DISABLE_TEST_EXTENSION="" + ) +) + :: Launch Code -%CODE% . %* +%CODE% . %DISABLE_TEST_EXTENSION% %* goto end :builtin diff --git a/patched-vscode/scripts/code.sh b/patched-vscode/scripts/code.sh index 24929fdf..c29b632c 100755 --- a/patched-vscode/scripts/code.sh +++ b/patched-vscode/scripts/code.sh @@ -42,8 +42,13 @@ function code() { export ELECTRON_ENABLE_STACK_DUMPING=1 export ELECTRON_ENABLE_LOGGING=1 + DISABLE_TEST_EXTENSION="--disable-extension=vscode.vscode-api-tests" + if [[ "$@" == *"--extensionTestsPath"* ]]; then + DISABLE_TEST_EXTENSION="" + fi + # Launch Code - exec "$CODE" . "$@" + exec "$CODE" . $DISABLE_TEST_EXTENSION "$@" } function code-wsl() diff --git a/patched-vscode/scripts/playground-server.ts b/patched-vscode/scripts/playground-server.ts index 1c2074ee..474f83de 100644 --- a/patched-vscode/scripts/playground-server.ts +++ b/patched-vscode/scripts/playground-server.ts @@ -223,10 +223,10 @@ class DirWatcher { function handleGetFileChangesRequest(watcher: DirWatcher, fileServer: FileServer, moduleIdMapper: SimpleModuleIdPathMapper): ChainableRequestHandler { return async (req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); - const d = watcher.onDidChange(fsPath => { + const d = watcher.onDidChange((fsPath, newContent) => { const path = fileServer.filePathToUrlPath(fsPath); if (path) { - res.write(JSON.stringify({ changedPath: path, moduleId: moduleIdMapper.getModuleId(fsPath) }) + '\n'); + res.write(JSON.stringify({ changedPath: path, moduleId: moduleIdMapper.getModuleId(fsPath), newContent }) + '\n'); } }); res.on('close', () => d.dispose()); @@ -235,13 +235,15 @@ function handleGetFileChangesRequest(watcher: DirWatcher, fileServer: FileServer function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): string { loaderJsCode = loaderJsCode.replace( /constructor\(env, scriptLoader, defineFunc, requireFunc, loaderAvailableTimestamp = 0\) {/, - '$&globalThis.___globalModuleManager = this;' + '$&globalThis.___globalModuleManager = this; globalThis.vscode = { process: { env: { VSCODE_DEV: true } } }' ); const ___globalModuleManager: any = undefined; // This code will be appended to loader.js function $watchChanges(fileChangesUrl: string) { + interface HotReloadConfig { } + let reloadFn; if (globalThis.$sendMessageToParent) { reloadFn = () => globalThis.$sendMessageToParent({ kind: 'reload' }); @@ -262,49 +264,18 @@ function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): s buffer += new TextDecoder().decode(value); const lines = buffer.split('\n'); buffer = lines.pop()!; + + const changes: { relativePath: string; config: HotReloadConfig | undefined; path: string; newContent: string }[] = []; + for (const line of lines) { const data = JSON.parse(line); - let handled = false; - if (data.changedPath.endsWith('.css')) { - if (typeof document !== 'undefined') { - console.log('css changed', data.changedPath); - const styleSheet = [...document.querySelectorAll(`link[rel='stylesheet']`)].find((l: any) => new URL(l.href, document.location.href).pathname.endsWith(data.changedPath)) as any; - if (styleSheet) { - styleSheet.href = styleSheet.href.replace(/\?.*/, '') + '?' + Date.now(); - } - } - handled = true; - } else if (data.changedPath.endsWith('.js') && data.moduleId) { - console.log('js changed', data.changedPath); - const moduleId = ___globalModuleManager._moduleIdProvider.getModuleId(data.moduleId); - if (___globalModuleManager._modules2[moduleId]) { - const srcUrl = ___globalModuleManager._config.moduleIdToPaths(data.moduleId); - const newSrc = await (await fetch(srcUrl)).text(); - (new Function('define', newSrc))(function (deps, callback) { // CodeQL [SM01632] This code is only executed during development (as part of the dev-only playground-server). It is required for the hot-reload functionality. - const oldModule = ___globalModuleManager._modules2[moduleId]; - delete ___globalModuleManager._modules2[moduleId]; - - ___globalModuleManager.defineModule(data.moduleId, deps, callback); - const newModule = ___globalModuleManager._modules2[moduleId]; - const oldExports = { ...oldModule.exports }; - - Object.assign(oldModule.exports, newModule.exports); - newModule.exports = oldModule.exports; - - handled = true; - - for (const cb of [...globalThis.$hotReload_deprecateExports]) { - cb(oldExports, newModule.exports); - } - - if (handled) { - console.log('hot reloaded', data.moduleId); - } - }); - } - } - - if (!handled) { reloadFn(); } + const relativePath = data.changedPath.replace(/\\/g, '/').split('/out/')[1]; + changes.push({ config: {}, path: data.changedPath, relativePath, newContent: data.newContent }); + } + + const result = handleChanges(changes, 'playground-server'); + if (result.reloadFailedJsFiles.length > 0) { + reloadFn(); } } }).catch(err => { @@ -312,6 +283,163 @@ function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): s setTimeout(() => $watchChanges(fileChangesUrl), 1000); }); + + function handleChanges(changes: { + relativePath: string; + config: HotReloadConfig | undefined; + path: string; + newContent: string; + }[], debugSessionName: string) { + // This function is stringified and injected into the debuggee. + + const hotReloadData: { count: number; originalWindowTitle: any; timeout: any; shouldReload: boolean } = globalThis.$hotReloadData || (globalThis.$hotReloadData = { count: 0, messageHideTimeout: undefined, shouldReload: false }); + + const reloadFailedJsFiles: { relativePath: string; path: string }[] = []; + + for (const change of changes) { + handleChange(change.relativePath, change.path, change.newContent, change.config); + } + + return { reloadFailedJsFiles }; + + function handleChange(relativePath: string, path: string, newSrc: string, config: any) { + if (relativePath.endsWith('.css')) { + handleCssChange(relativePath); + } else if (relativePath.endsWith('.js')) { + handleJsChange(relativePath, path, newSrc, config); + } + } + + function handleCssChange(relativePath: string) { + if (typeof document === 'undefined') { + return; + } + + const styleSheet = (([...document.querySelectorAll(`link[rel='stylesheet']`)] as HTMLLinkElement[])) + .find(l => new URL(l.href, document.location.href).pathname.endsWith(relativePath)); + if (styleSheet) { + setMessage(`reload ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); + console.log(debugSessionName, 'css reloaded', relativePath); + styleSheet.href = styleSheet.href.replace(/\?.*/, '') + '?' + Date.now(); + } else { + setMessage(`could not reload ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); + console.log(debugSessionName, 'ignoring css change, as stylesheet is not loaded', relativePath); + } + } + + + function handleJsChange(relativePath: string, path: string, newSrc: string, config: any) { + const moduleIdStr = trimEnd(relativePath, '.js'); + + const requireFn: any = globalThis.require; + const moduleManager = (requireFn as any).moduleManager; + if (!moduleManager) { + console.log(debugSessionName, 'ignoring js change, as moduleManager is not available', relativePath); + return; + } + + const moduleId = moduleManager._moduleIdProvider.getModuleId(moduleIdStr); + const oldModule = moduleManager._modules2[moduleId]; + + if (!oldModule) { + console.log(debugSessionName, 'ignoring js change, as module is not loaded', relativePath); + return; + } + + // Check if we can reload + const g = globalThis as any; + + // A frozen copy of the previous exports + const oldExports = Object.freeze({ ...oldModule.exports }); + const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc, config }); + + if (!reloadFn) { + console.log(debugSessionName, 'ignoring js change, as module does not support hot-reload', relativePath); + hotReloadData.shouldReload = true; + + reloadFailedJsFiles.push({ relativePath, path }); + + setMessage(`hot reload not supported for ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); + return; + } + + // Eval maintains source maps + function newScript(/* this parameter is used by newSrc */ define) { + // eslint-disable-next-line no-eval + eval(newSrc); // CodeQL [SM01632] This code is only executed during development. It is required for the hot-reload functionality. + } + + newScript(/* define */ function (deps, callback) { + // Evaluating the new code was successful. + + // Redefine the module + delete moduleManager._modules2[moduleId]; + moduleManager.defineModule(moduleIdStr, deps, callback); + const newModule = moduleManager._modules2[moduleId]; + + + // Patch the exports of the old module, so that modules using the old module get the new exports + Object.assign(oldModule.exports, newModule.exports); + // We override the exports so that future reloads still patch the initial exports. + newModule.exports = oldModule.exports; + + const successful = reloadFn(newModule.exports); + if (!successful) { + hotReloadData.shouldReload = true; + setMessage(`hot reload failed ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); + console.log(debugSessionName, 'hot reload was not successful', relativePath); + return; + } + + console.log(debugSessionName, 'hot reloaded', moduleIdStr); + setMessage(`successfully reloaded ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); + }); + } + + function setMessage(message: string) { + const domElem = (document.querySelector('.titlebar-center .window-title')) as HTMLDivElement | undefined; + if (!domElem) { return; } + if (!hotReloadData.timeout) { + hotReloadData.originalWindowTitle = domElem.innerText; + } else { + clearTimeout(hotReloadData.timeout); + } + if (hotReloadData.shouldReload) { + message += ' (manual reload required)'; + } + + domElem.innerText = message; + hotReloadData.timeout = setTimeout(() => { + hotReloadData.timeout = undefined; + // If wanted, we can restore the previous title message + // domElem.replaceChildren(hotReloadData.originalWindowTitle); + }, 5000); + } + + function formatPath(path: string): string { + const parts = path.split('/'); + parts.reverse(); + let result = parts[0]; + parts.shift(); + for (const p of parts) { + if (result.length + p.length > 40) { + break; + } + result = p + '/' + result; + if (result.length > 20) { + break; + } + } + return result; + } + + function trimEnd(str, suffix) { + if (str.endsWith(suffix)) { + return str.substring(0, str.length - suffix.length); + } + return str; + } + } } const additionalJsCode = ` diff --git a/patched-vscode/scripts/test-esm.bat b/patched-vscode/scripts/test-esm.bat new file mode 100644 index 00000000..faeff790 --- /dev/null +++ b/patched-vscode/scripts/test-esm.bat @@ -0,0 +1,31 @@ +@echo off +setlocal + +set ELECTRON_RUN_AS_NODE= + +pushd %~dp0\.. + +:: Get Code.exe location +for /f "tokens=2 delims=:," %%a in ('findstr /R /C:"\"nameShort\":.*" product.json') do set NAMESHORT=%%~a +set NAMESHORT=%NAMESHORT: "=% +set NAMESHORT=%NAMESHORT:"=%.exe +set CODE=".build\electron\%NAMESHORT%" + +:: Download Electron if needed +call node build\lib\electron.js +if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron + +:: Run tests +set ELECTRON_ENABLE_LOGGING=1 +%CODE% .\test\unit\electron\index.esm.js --crash-reporter-directory=%~dp0\..\.build\crashes %* + +popd + +endlocal + +:: app.exit(0) is exiting with code 255 in Electron 1.7.4. +:: See https://github.com/microsoft/vscode/issues/28582 +echo errorlevel: %errorlevel% +if %errorlevel% == 255 set errorlevel=0 + +exit /b %errorlevel% diff --git a/patched-vscode/scripts/test-esm.sh b/patched-vscode/scripts/test-esm.sh new file mode 100755 index 00000000..ddb61901 --- /dev/null +++ b/patched-vscode/scripts/test-esm.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -e + +if [[ "$OSTYPE" == "darwin"* ]]; then + realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } + ROOT=$(dirname $(dirname $(realpath "$0"))) +else + ROOT=$(dirname $(dirname $(readlink -f $0))) + # --disable-dev-shm-usage: when run on docker containers where size of /dev/shm + # partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory + LINUX_EXTRA_ARGS="--disable-dev-shm-usage" +fi + +cd $ROOT + +if [[ "$OSTYPE" == "darwin"* ]]; then + NAME=`node -p "require('./product.json').nameLong"` + CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron" +else + NAME=`node -p "require('./product.json').applicationName"` + CODE=".build/electron/$NAME" +fi + +VSCODECRASHDIR=$ROOT/.build/crashes + +# Node modules +test -d node_modules || yarn + +# Get electron +yarn electron + +# Unit Tests +if [[ "$OSTYPE" == "darwin"* ]]; then + cd $ROOT ; ulimit -n 4096 ; \ + ELECTRON_ENABLE_LOGGING=1 \ + "$CODE" \ + test/unit/electron/index.esm.js --crash-reporter-directory=$VSCODECRASHDIR "$@" +else + cd $ROOT ; \ + ELECTRON_ENABLE_LOGGING=1 \ + "$CODE" \ + test/unit/electron/index.esm.js --crash-reporter-directory=$VSCODECRASHDIR $LINUX_EXTRA_ARGS "$@" +fi diff --git a/patched-vscode/scripts/test-integration-esm.bat b/patched-vscode/scripts/test-integration-esm.bat new file mode 100644 index 00000000..0d12b225 --- /dev/null +++ b/patched-vscode/scripts/test-integration-esm.bat @@ -0,0 +1,119 @@ +@echo off +setlocal + +pushd %~dp0\.. + +set VSCODEUSERDATADIR=%TEMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,2% +set VSCODECRASHDIR=%~dp0\..\.build\crashes +set VSCODELOGSDIR=%~dp0\..\.build\logs\integration-tests + +:: Figure out which Electron to use for running tests +if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( + chcp 65001 + set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat + set VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE=1 + + echo Running integration tests out of sources. +) else ( + set VSCODE_CLI=1 + set ELECTRON_ENABLE_LOGGING=1 + + echo Running integration tests with '%INTEGRATION_TEST_ELECTRON_PATH%' as build. +) + +echo Storing crash reports into '%VSCODECRASHDIR%'. +echo Storing log files into '%VSCODELOGSDIR%'. + + +:: Tests standalone (AMD) + +echo. +echo ### node.js integration tests +call .\scripts\test-esm.bat --runGlob **\*.integrationTest.js %* +if %errorlevel% neq 0 exit /b %errorlevel% + + +:: Tests in the extension host + +set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% + +echo. +echo ### API tests (folder) +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### API tests (workspace) +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Colorize tests +call yarn test-extension -l vscode-colorize-tests +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### TypeScript tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Markdown tests +call yarn test-extension -l markdown-language-features +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Emmet tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\emmet\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Git tests +for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i +set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% +mkdir %GITWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Ipynb tests +call yarn test-extension -l ipynb +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Notebook Output tests +call yarn test-extension -l notebook-renderers +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Configuration editing tests +set CFWORKSPACE=%TEMPDIR%\cf-%RANDOM% +mkdir %CFWORKSPACE% +call yarn test-extension -l configuration-editing +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### GitHub Authentication tests +call yarn test-extension -l github-authentication +if %errorlevel% neq 0 exit /b %errorlevel% + +:: Tests standalone (CommonJS) + +echo. +echo ### CSS tests +call %~dp0\node-electron.bat %~dp0\..\extensions\css-language-features/server/test/index.js +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### HTML tests +call %~dp0\node-electron.bat %~dp0\..\extensions\html-language-features/server/test/index.js +if %errorlevel% neq 0 exit /b %errorlevel% + + +:: Cleanup + +rmdir /s /q %VSCODEUSERDATADIR% + +popd + +endlocal diff --git a/patched-vscode/scripts/test-integration-esm.sh b/patched-vscode/scripts/test-integration-esm.sh new file mode 100755 index 00000000..dde3bc05 --- /dev/null +++ b/patched-vscode/scripts/test-integration-esm.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +set -e + +if [[ "$OSTYPE" == "darwin"* ]]; then + realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } + ROOT=$(dirname $(dirname $(realpath "$0"))) +else + ROOT=$(dirname $(dirname $(readlink -f $0))) + # --disable-dev-shm-usage: when run on docker containers where size of /dev/shm + # partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory + LINUX_EXTRA_ARGS="--disable-dev-shm-usage" +fi + +VSCODEUSERDATADIR=`mktemp -d 2>/dev/null` +VSCODECRASHDIR=$ROOT/.build/crashes +VSCODELOGSDIR=$ROOT/.build/logs/integration-tests + +cd $ROOT + +# Figure out which Electron to use for running tests +if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ] +then + INTEGRATION_TEST_ELECTRON_PATH="./scripts/code.sh" + + echo "Running integration tests out of sources." +else + export VSCODE_CLI=1 + export ELECTRON_ENABLE_LOGGING=1 + + echo "Running integration tests with '$INTEGRATION_TEST_ELECTRON_PATH' as build." +fi + +echo "Storing crash reports into '$VSCODECRASHDIR'." +echo "Storing log files into '$VSCODELOGSDIR'." + + +# Tests standalone (AMD) + +echo +echo "### node.js integration tests" +echo +./scripts/test-esm.sh --runGlob **/*.integrationTest.js "$@" + + +# Tests in the extension host + +API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-extensions --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" + +if [ -z "$INTEGRATION_TEST_APP_NAME" ]; then + kill_app() { true; } +else + kill_app() { killall $INTEGRATION_TEST_APP_NAME || true; } +fi + +echo +echo "### API tests (folder)" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### API tests (workspace)" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### Colorize tests" +echo +yarn test-extension -l vscode-colorize-tests +kill_app + +echo +echo "### TypeScript tests" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test/unit $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### Markdown tests" +echo +yarn test-extension -l markdown-language-features +kill_app + +echo +echo "### Emmet tests" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/emmet/test-workspace --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### Git tests" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $API_TESTS_EXTRA_ARGS +kill_app + +echo +echo "### Ipynb tests" +echo +yarn test-extension -l ipynb +kill_app + +echo +echo "### Notebook Output tests" +echo +yarn test-extension -l notebook-renderers +kill_app + +echo +echo "### Configuration editing tests" +echo +yarn test-extension -l configuration-editing +kill_app + +echo +echo "### GitHub Authentication tests" +echo +yarn test-extension -l github-authentication +kill_app + +# Tests standalone (CommonJS) + +echo +echo "### CSS tests" +echo +cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js + +echo +echo "### HTML tests" +echo +cd $ROOT/extensions/html-language-features/server && $ROOT/scripts/node-electron.sh test/index.js + + +# Cleanup + +rm -rf $VSCODEUSERDATADIR diff --git a/patched-vscode/scripts/xterm-update.js b/patched-vscode/scripts/xterm-update.js index 851b296a..8ede6191 100644 --- a/patched-vscode/scripts/xterm-update.js +++ b/patched-vscode/scripts/xterm-update.js @@ -8,6 +8,7 @@ const path = require('path'); const moduleNames = [ '@xterm/xterm', + '@xterm/addon-clipboard', '@xterm/addon-image', '@xterm/addon-search', '@xterm/addon-serialize', diff --git a/patched-vscode/scripts/xterm-update.ps1 b/patched-vscode/scripts/xterm-update.ps1 index 11c282de..2eae7fff 100644 --- a/patched-vscode/scripts/xterm-update.ps1 +++ b/patched-vscode/scripts/xterm-update.ps1 @@ -1 +1,2 @@ -node $PSScriptRoot\xterm-update.js (Get-Location) +$scriptPath = Join-Path $PSScriptRoot "xterm-update.js" +node $scriptPath (Get-Location) diff --git a/patched-vscode/src/bootstrap-amd.js b/patched-vscode/src/bootstrap-amd.js index cc47b050..344557c1 100644 --- a/patched-vscode/src/bootstrap-amd.js +++ b/patched-vscode/src/bootstrap-amd.js @@ -6,6 +6,45 @@ //@ts-check 'use strict'; +/** + * @import { INLSConfiguration } from './vs/nls' + * @import { IProductConfiguration } from './vs/base/common/product' + */ + +// ESM-uncomment-begin +// import * as path from 'path'; +// import * as fs from 'fs'; +// import { fileURLToPath } from 'url'; +// import { createRequire, register } from 'node:module'; +// import { product, pkg } from './bootstrap-meta.js'; +// import './bootstrap-node.js'; +// import * as performance from './vs/base/common/performance.js'; +// +// const require = createRequire(import.meta.url); +// /** @type any */ +// const module = { exports: {} }; +// const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// +// // Install a hook to module resolution to map 'fs' to 'original-fs' +// if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { +// const jsCode = ` +// export async function resolve(specifier, context, nextResolve) { +// if (specifier === 'fs') { +// return { +// format: 'builtin', +// shortCircuit: true, +// url: 'node:original-fs' +// }; +// } + +// // Defer to the next hook in the chain, which would be the +// // Node.js default resolve if this is the last user-specified loader. +// return nextResolve(specifier, context); +// }`; +// register(`data:text/javascript;base64,${Buffer.from(jsCode).toString('base64')}`, import.meta.url); +// } +// ESM-uncomment-end + // Store the node.js require function in a variable // before loading our AMD loader to avoid issues // when this file is bundled with other files. @@ -15,8 +54,13 @@ const nodeRequire = require; globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => nodeRequire(String(mod)) }); // VSCODE_GLOBALS: package/product.json -/** @type Record */ -globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); +/** @type Partial */ +// ESM-comment-begin +globalThis._VSCODE_PRODUCT_JSON = require('./bootstrap-meta').product; +// ESM-comment-end +// ESM-uncomment-begin +// globalThis._VSCODE_PRODUCT_JSON = { ...product }; +// ESM-uncomment-end if (process.env['VSCODE_DEV']) { // Patch product overrides when running out of sources try { @@ -25,22 +69,136 @@ if (process.env['VSCODE_DEV']) { globalThis._VSCODE_PRODUCT_JSON = Object.assign(globalThis._VSCODE_PRODUCT_JSON, overrides); } catch (error) { /* ignore */ } } -globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); +// ESM-comment-begin +globalThis._VSCODE_PACKAGE_JSON = require('./bootstrap-meta').pkg; +// ESM-comment-end +// ESM-uncomment-begin +// globalThis._VSCODE_PACKAGE_JSON = { ...pkg }; +// ESM-uncomment-end + +// VSCODE_GLOBALS: file root of all resources +globalThis._VSCODE_FILE_ROOT = __dirname; + +// ESM-comment-begin +const bootstrapNode = require('./bootstrap-node'); +const performance = require(`./vs/base/common/performance`); +const fs = require('fs'); +// ESM-comment-end + +//#region NLS helpers + +/** @type {Promise | undefined} */ +let setupNLSResult = undefined; + +/** + * @returns {Promise} + */ +function setupNLS() { + if (!setupNLSResult) { + setupNLSResult = doSetupNLS(); + } + + return setupNLSResult; +} + +/** + * @returns {Promise} + */ +async function doSetupNLS() { + performance.mark('code/amd/willLoadNls'); + + /** @type {INLSConfiguration | undefined} */ + let nlsConfig = undefined; + + /** @type {string | undefined} */ + let messagesFile; + if (process.env['VSCODE_NLS_CONFIG']) { + try { + /** @type {INLSConfiguration} */ + nlsConfig = JSON.parse(process.env['VSCODE_NLS_CONFIG']); + if (nlsConfig?.languagePack?.messagesFile) { + messagesFile = nlsConfig.languagePack.messagesFile; + } else if (nlsConfig?.defaultMessagesFile) { + messagesFile = nlsConfig.defaultMessagesFile; + } + + globalThis._VSCODE_NLS_LANGUAGE = nlsConfig?.resolvedLanguage; + } catch (e) { + console.error(`Error reading VSCODE_NLS_CONFIG from environment: ${e}`); + } + } + + if ( + process.env['VSCODE_DEV'] || // no NLS support in dev mode + !messagesFile // no NLS messages file + ) { + return undefined; + } + + try { + globalThis._VSCODE_NLS_MESSAGES = JSON.parse((await fs.promises.readFile(messagesFile)).toString()); + } catch (error) { + console.error(`Error reading NLS messages file ${messagesFile}: ${error}`); + + // Mark as corrupt: this will re-create the language pack cache next startup + if (nlsConfig?.languagePack?.corruptMarkerFile) { + try { + await fs.promises.writeFile(nlsConfig.languagePack.corruptMarkerFile, 'corrupted'); + } catch (error) { + console.error(`Error writing corrupted NLS marker file: ${error}`); + } + } + + // Fallback to the default message file to ensure english translation at least + if (nlsConfig?.defaultMessagesFile && nlsConfig.defaultMessagesFile !== messagesFile) { + try { + globalThis._VSCODE_NLS_MESSAGES = JSON.parse((await fs.promises.readFile(nlsConfig.defaultMessagesFile)).toString()); + } catch (error) { + console.error(`Error reading default NLS messages file ${nlsConfig.defaultMessagesFile}: ${error}`); + } + } + } + + performance.mark('code/amd/didLoadNls'); + + return nlsConfig; +} + +//#endregion +//#region Loader Config + +// ESM-uncomment-begin +// /** +// * @param {string=} entrypoint +// * @param {(value: any) => void} [onLoad] +// * @param {(err: Error) => void} [onError] +// */ +// module.exports.load = function (entrypoint, onLoad, onError) { +// if (!entrypoint) { +// return; +// } + +// entrypoint = `./${entrypoint}.js`; + +// onLoad = onLoad || function () { }; +// onError = onError || function (err) { console.error(err); }; + +// setupNLS().then(() => { +// performance.mark(`code/fork/willLoadCode`); +// import(entrypoint).then(onLoad, onError); +// }); +// }; +// ESM-uncomment-end + +// ESM-comment-begin // @ts-ignore const loader = require('./vs/loader'); -const bootstrap = require('./bootstrap'); -const performance = require('./vs/base/common/performance'); - -// Bootstrap: NLS -const nlsConfig = bootstrap.setupNLS(); -// Bootstrap: Loader loader.config({ - baseUrl: bootstrap.fileUriFromPath(__dirname, { isWindows: process.platform === 'win32' }), + baseUrl: bootstrapNode.fileUriFromPath(__dirname, { isWindows: process.platform === 'win32' }), catchError: true, nodeRequire, - 'vs/nls': nlsConfig, amdModulesPattern: /^vs\//, recordStats: true }); @@ -52,19 +210,12 @@ if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { }); } -// Pseudo NLS support -if (nlsConfig && nlsConfig.pseudo) { - loader(['vs/nls'], function (/** @type {import('vs/nls')} */nlsPlugin) { - nlsPlugin.setPseudoTranslation(!!nlsConfig.pseudo); - }); -} - /** * @param {string=} entrypoint - * @param {(value: any) => void=} onLoad - * @param {(err: Error) => void=} onError + * @param {(value: any) => void} [onLoad] + * @param {(err: Error) => void} [onError] */ -exports.load = function (entrypoint, onLoad, onError) { +module.exports.load = function (entrypoint, onLoad, onError) { if (!entrypoint) { return; } @@ -82,6 +233,15 @@ exports.load = function (entrypoint, onLoad, onError) { onLoad = onLoad || function () { }; onError = onError || function (err) { console.error(err); }; - performance.mark('code/fork/willLoadCode'); - loader([entrypoint], onLoad, onError); + setupNLS().then(() => { + performance.mark('code/fork/willLoadCode'); + loader([entrypoint], onLoad, onError); + }); }; +// ESM-comment-end + +//#endregion + +// ESM-uncomment-begin +// export const load = module.exports.load; +// ESM-uncomment-end diff --git a/patched-vscode/extensions/markdown-language-features/server/extension.webpack.config.js b/patched-vscode/src/bootstrap-cli.js similarity index 55% rename from patched-vscode/extensions/markdown-language-features/server/extension.webpack.config.js rename to patched-vscode/src/bootstrap-cli.js index aafc9c1f..8f077a0e 100644 --- a/patched-vscode/extensions/markdown-language-features/server/extension.webpack.config.js +++ b/patched-vscode/src/bootstrap-cli.js @@ -4,19 +4,11 @@ *--------------------------------------------------------------------------------------------*/ //@ts-check - 'use strict'; -const withDefaults = require('../../shared.webpack.config'); -const path = require('path'); - -module.exports = withDefaults({ - context: path.join(__dirname), - entry: { - extension: './src/node/workerMain.ts', - }, - output: { - filename: 'workerMain.js', - path: path.join(__dirname, 'dist', 'node'), - } -}); +// Delete `VSCODE_CWD` very early. We have seen +// reports where `code .` would use the wrong +// current working directory due to our variable +// somehow escaping to the parent shell +// (https://github.com/microsoft/vscode/issues/126399) +delete process.env['VSCODE_CWD']; diff --git a/patched-vscode/src/bootstrap-fork.js b/patched-vscode/src/bootstrap-fork.js index 9de1e6f0..e1826549 100644 --- a/patched-vscode/src/bootstrap-fork.js +++ b/patched-vscode/src/bootstrap-fork.js @@ -6,23 +6,30 @@ //@ts-check 'use strict'; +// ESM-comment-begin const performance = require('./vs/base/common/performance'); -performance.mark('code/fork/start'); - -const bootstrap = require('./bootstrap'); const bootstrapNode = require('./bootstrap-node'); +const bootstrapAmd = require('./bootstrap-amd'); +// ESM-comment-end +// ESM-uncomment-begin +// import * as performance from './vs/base/common/performance.js'; +// import * as bootstrapNode from './bootstrap-node.js'; +// import * as bootstrapAmd from './bootstrap-amd.js'; +// ESM-uncomment-end + +performance.mark('code/fork/start'); // Crash reporter configureCrashReporter(); -// Remove global paths from the node module lookup -bootstrapNode.removeGlobalNodeModuleLookupPaths(); +// Remove global paths from the node module lookup (node.js only) +bootstrapNode.removeGlobalNodeJsModuleLookupPaths(); // Enable ASAR in our forked processes -bootstrap.enableASARSupport(); +bootstrapNode.enableASARSupport(); -if (process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']) { - bootstrapNode.injectNodeModuleLookupPath(process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']); +if (process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH']) { + bootstrapNode.devInjectNodeModuleLookupPath(process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH']); } // Configure: pipe logging to parent process @@ -41,7 +48,7 @@ if (process.env['VSCODE_PARENT_PID']) { } // Load AMD entry point -require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']); +bootstrapAmd.load(process.env['VSCODE_AMD_ENTRYPOINT']); //#region Helpers diff --git a/patched-vscode/src/bootstrap-import.js b/patched-vscode/src/bootstrap-import.js new file mode 100644 index 00000000..070b0b93 --- /dev/null +++ b/patched-vscode/src/bootstrap-import.js @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +// ********************************************************************* +// * * +// * We need this to redirect to node_modules from the remote-folder. * +// * This ONLY applies when running out of source. * +// * * +// ********************************************************************* + +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { promises } from 'node:fs'; +import { join } from 'node:path'; + +// SEE https://nodejs.org/docs/latest/api/module.html#initialize + +/** + * @type {Object.} + */ +const _specifierToUrl = {}; + +/** + * @param {string} injectPath + */ +export async function initialize(injectPath) { + // populate mappings + + const injectPackageJSONPath = fileURLToPath(new URL('../package.json', pathToFileURL(injectPath))); + const packageJSON = JSON.parse(String(await promises.readFile(injectPackageJSONPath))); + + for (const [name] of Object.entries(packageJSON.dependencies)) { + try { + const path = join(injectPackageJSONPath, `../node_modules/${name}/package.json`); + let { main } = JSON.parse(String(await promises.readFile(path))); + + if (!main) { + main = 'index.js'; + } + if (!main.endsWith('.js')) { + main += '.js'; + } + const mainPath = join(injectPackageJSONPath, `../node_modules/${name}/${main}`); + _specifierToUrl[name] = pathToFileURL(mainPath).href; + + } catch (err) { + console.error(name); + console.error(err); + } + } + + console.log(`[bootstrap-import] Initialized node_modules redirector for: ${injectPath}`); +} + +/** + * @param {string | number} specifier + * @param {any} context + * @param {(arg0: any, arg1: any) => any} nextResolve + */ +export async function resolve(specifier, context, nextResolve) { + + const newSpecifier = _specifierToUrl[specifier]; + if (newSpecifier !== undefined) { + // console.log('[HOOKS]', specifier, '--->', newSpecifier); + return { + format: 'commonjs', + shortCircuit: true, + url: newSpecifier + }; + } + + // Defer to the next hook in the chain, which would be the + // Node.js default resolve if this is the last user-specified loader. + return nextResolve(specifier, context); +} diff --git a/patched-vscode/src/bootstrap-meta.js b/patched-vscode/src/bootstrap-meta.js new file mode 100644 index 00000000..b437ed75 --- /dev/null +++ b/patched-vscode/src/bootstrap-meta.js @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check +'use strict'; + +/** + * @import { IProductConfiguration } from './vs/base/common/product' + */ + +// ESM-uncomment-begin +// import { createRequire } from 'node:module'; +// +// const require = createRequire(import.meta.url); +// /** @type any */ +// const module = { exports: {} }; +// ESM-uncomment-end + +/** @type Partial & { BUILD_INSERT_PRODUCT_CONFIGURATION?: string } */ +let productObj = { BUILD_INSERT_PRODUCT_CONFIGURATION: 'BUILD_INSERT_PRODUCT_CONFIGURATION' }; // DO NOT MODIFY, PATCHED DURING BUILD +if (productObj['BUILD_INSERT_PRODUCT_CONFIGURATION']) { + // @ts-ignore + productObj = require('../product.json'); // Running out of sources +} + +/** @type object & { BUILD_INSERT_PACKAGE_CONFIGURATION?: string } */ +let pkgObj = { BUILD_INSERT_PACKAGE_CONFIGURATION: 'BUILD_INSERT_PACKAGE_CONFIGURATION' }; // DO NOT MODIFY, PATCHED DURING BUILD +if (pkgObj['BUILD_INSERT_PACKAGE_CONFIGURATION']) { + // @ts-ignore + pkgObj = require('../package.json'); // Running out of sources +} + +module.exports.product = productObj; +module.exports.pkg = pkgObj; + +// ESM-uncomment-begin +// export const product = module.exports.product; +// export const pkg = module.exports.pkg; +// ESM-uncomment-end diff --git a/patched-vscode/src/bootstrap-node.js b/patched-vscode/src/bootstrap-node.js index 914b8290..8c9af7d9 100644 --- a/patched-vscode/src/bootstrap-node.js +++ b/patched-vscode/src/bootstrap-node.js @@ -6,12 +6,45 @@ //@ts-check 'use strict'; +// ESM-comment-begin +const path = require('path'); +const fs = require('fs'); +const Module = require('module'); +// ESM-comment-end +// ESM-uncomment-begin +// import * as path from 'path'; +// import * as fs from 'fs'; +// import { fileURLToPath } from 'url'; +// import { createRequire } from 'node:module'; +// +// const require = createRequire(import.meta.url); +// /** @type any */ +// const module = { exports: {} }; +// const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// ESM-uncomment-end + +// increase number of stack frames(from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) +Error.stackTraceLimit = 100; + +if (!process.env['VSCODE_HANDLES_SIGPIPE']) { + // Workaround for Electron not installing a handler to ignore SIGPIPE + // (https://github.com/electron/electron/issues/13254) + let didLogAboutSIGPIPE = false; + process.on('SIGPIPE', () => { + // See https://github.com/microsoft/vscode-remote-release/issues/6543 + // In certain situations, the console itself can be in a broken pipe state + // so logging SIGPIPE to the console will cause an infinite async loop + if (!didLogAboutSIGPIPE) { + didLogAboutSIGPIPE = true; + console.error(new Error(`Unexpected SIGPIPE`)); + } + }); +} + // Setup current working directory in all our node & electron processes // - Windows: call `process.chdir()` to always set application folder as cwd // - all OS: store the `process.cwd()` inside `VSCODE_CWD` for consistent lookups function setupCurrentWorkingDirectory() { - const path = require('path'); - try { // Store the `process.cwd()` inside `VSCODE_CWD` @@ -36,16 +69,25 @@ setupCurrentWorkingDirectory(); /** * Add support for redirecting the loading of node modules * + * Note: only applies when running out of sources. + * * @param {string} injectPath */ -exports.injectNodeModuleLookupPath = function (injectPath) { +module.exports.devInjectNodeModuleLookupPath = function (injectPath) { + if (!process.env['VSCODE_DEV']) { + return; // only applies running out of sources + } + if (!injectPath) { throw new Error('Missing injectPath'); } - const Module = require('module'); - const path = require('path'); - + const Module = require('node:module'); + // ESM-uncomment-begin + // // register a loader hook + // Module.register('./bootstrap-import.js', { parentURL: import.meta.url, data: injectPath }); + // ESM-uncomment-end + // ESM-comment-begin const nodeModulesPath = path.join(__dirname, '../node_modules'); // @ts-ignore @@ -65,9 +107,14 @@ exports.injectNodeModuleLookupPath = function (injectPath) { return paths; }; + // ESM-comment-end }; -exports.removeGlobalNodeModuleLookupPaths = function () { +module.exports.removeGlobalNodeJsModuleLookupPaths = function () { + if (typeof process?.versions?.electron === 'string') { + return; // Electron disables global search paths in https://github.com/electron/electron/blob/3186c2f0efa92d275dc3d57b5a14a60ed3846b0e/shell/common/node_bindings.cc#L653 + } + const Module = require('module'); // @ts-ignore const globalPaths = Module.globalPaths; @@ -95,10 +142,7 @@ exports.removeGlobalNodeModuleLookupPaths = function () { * @param {Partial} product * @returns {{ portableDataPath: string; isPortable: boolean; }} */ -exports.configurePortable = function (product) { - const fs = require('fs'); - const path = require('path'); - +module.exports.configurePortable = function (product) { const appRoot = path.dirname(__dirname); /** @@ -158,3 +202,77 @@ exports.configurePortable = function (product) { isPortable }; }; + +/** + * Helper to enable ASAR support. + */ +module.exports.enableASARSupport = function () { + // ESM-comment-begin + const NODE_MODULES_PATH = path.join(__dirname, '../node_modules'); + const NODE_MODULES_ASAR_PATH = `${NODE_MODULES_PATH}.asar`; + + // @ts-ignore + const originalResolveLookupPaths = Module._resolveLookupPaths; + + // @ts-ignore + Module._resolveLookupPaths = function (request, parent) { + const paths = originalResolveLookupPaths(request, parent); + if (Array.isArray(paths)) { + for (let i = 0, len = paths.length; i < len; i++) { + if (paths[i] === NODE_MODULES_PATH) { + paths.splice(i, 0, NODE_MODULES_ASAR_PATH); + break; + } + } + } + + return paths; + }; + // ESM-comment-end +}; + +/** + * Helper to convert a file path to a URI. + * + * TODO@bpasero TODO@esm check for removal once ESM has landed. + * + * @param {string} path + * @param {{ isWindows?: boolean, scheme?: string, fallbackAuthority?: string }} config + * @returns {string} + */ +module.exports.fileUriFromPath = function (path, config) { + + // Since we are building a URI, we normalize any backslash + // to slashes and we ensure that the path begins with a '/'. + let pathName = path.replace(/\\/g, '/'); + if (pathName.length > 0 && pathName.charAt(0) !== '/') { + pathName = `/${pathName}`; + } + + /** @type {string} */ + let uri; + + // Windows: in order to support UNC paths (which start with '//') + // that have their own authority, we do not use the provided authority + // but rather preserve it. + if (config.isWindows && pathName.startsWith('//')) { + uri = encodeURI(`${config.scheme || 'file'}:${pathName}`); + } + + // Otherwise we optionally add the provided authority if specified + else { + uri = encodeURI(`${config.scheme || 'file'}://${config.fallbackAuthority || ''}${pathName}`); + } + + return uri.replace(/#/g, '%23'); +}; + +//#endregion + +// ESM-uncomment-begin +// export const devInjectNodeModuleLookupPath = module.exports.devInjectNodeModuleLookupPath; +// export const removeGlobalNodeJsModuleLookupPaths = module.exports.removeGlobalNodeJsModuleLookupPaths; +// export const configurePortable = module.exports.configurePortable; +// export const enableASARSupport = module.exports.enableASARSupport; +// export const fileUriFromPath = module.exports.fileUriFromPath; +// ESM-uncomment-end diff --git a/patched-vscode/src/vs/workbench/workbench.desktop.main.nls.js b/patched-vscode/src/bootstrap-server.js similarity index 75% rename from patched-vscode/src/vs/workbench/workbench.desktop.main.nls.js rename to patched-vscode/src/bootstrap-server.js index d6a8b487..b84cc6a6 100644 --- a/patched-vscode/src/vs/workbench/workbench.desktop.main.nls.js +++ b/patched-vscode/src/bootstrap-server.js @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// NOTE: THIS FILE WILL BE OVERWRITTEN DURING BUILD TIME, DO NOT EDIT +//@ts-check +'use strict'; -define([], {}); \ No newline at end of file +// Keep bootstrap-amd.js from redefining 'fs'. +delete process.env['ELECTRON_RUN_AS_NODE']; diff --git a/patched-vscode/src/bootstrap-window.js b/patched-vscode/src/bootstrap-window.js index fa3bc5eb..25a4a216 100644 --- a/patched-vscode/src/bootstrap-window.js +++ b/patched-vscode/src/bootstrap-window.js @@ -8,29 +8,24 @@ //@ts-check 'use strict'; -/* eslint-disable no-restricted-globals */ - -// Simple module style to support node.js and browser environments -(function (globalThis, factory) { +/** + * @import { ISandboxConfiguration } from './vs/base/parts/sandbox/common/sandboxTypes' + * @typedef {any} LoaderConfig + */ - // Node.js - if (typeof exports === 'object') { - module.exports = factory(); - } +/* eslint-disable no-restricted-globals */ - // Browser - else { - // @ts-ignore - globalThis.MonacoBootstrapWindow = factory(); - } -}(this, function () { - const bootstrapLib = bootstrap(); +(function (factory) { + // @ts-ignore + globalThis.MonacoBootstrapWindow = factory(); +}(function () { const preloadGlobals = sandboxGlobals(); const safeProcess = preloadGlobals.process; + // increase number of stack frames(from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) + Error.stackTraceLimit = 100; + /** - * @typedef {import('./vs/base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration - * * @param {string[]} modulePaths * @param {(result: unknown, configuration: ISandboxConfiguration) => Promise | undefined} resultCallback * @param {{ @@ -80,28 +75,93 @@ developerDeveloperKeybindingsDisposable = registerDeveloperKeybindings(disallowReloadKeybinding); } - // Get the nls configuration into the process.env as early as possible - // @ts-ignore - const nlsConfig = globalThis.MonacoBootstrap.setupNLS(); - - let locale = nlsConfig.availableLanguages['*'] || 'en'; - if (locale === 'zh-tw') { - locale = 'zh-Hant'; - } else if (locale === 'zh-cn') { - locale = 'zh-Hans'; + globalThis._VSCODE_NLS_MESSAGES = configuration.nls.messages; + globalThis._VSCODE_NLS_LANGUAGE = configuration.nls.language; + let language = configuration.nls.language || 'en'; + if (language === 'zh-tw') { + language = 'zh-Hant'; + } else if (language === 'zh-cn') { + language = 'zh-Hans'; } - window.document.documentElement.setAttribute('lang', locale); + window.document.documentElement.setAttribute('lang', language); window['MonacoEnvironment'] = {}; - /** - * @typedef {any} LoaderConfig - */ + // ESM-uncomment-begin + // // Signal before require() + // if (typeof options?.beforeRequire === 'function') { + // options.beforeRequire(configuration); + // } + + // const baseUrl = new URL(`${fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out/`); + // globalThis._VSCODE_FILE_ROOT = baseUrl.toString(); + + // // DEV --------------------------------------------------------------------------------------- + // // DEV: This is for development and enables loading CSS via import-statements via import-maps. + // // DEV: For each CSS modules that we have we defined an entry in the import map that maps to + // // DEV: a blob URL that loads the CSS via a dynamic @import-rule. + // // DEV --------------------------------------------------------------------------------------- + // if (Array.isArray(configuration.cssModules) && configuration.cssModules.length > 0) { + // performance.mark('code/willAddCssLoader'); + + // const style = document.createElement('style'); + // style.type = 'text/css'; + // style.media = 'screen'; + // style.id = 'vscode-css-loading'; + // document.head.appendChild(style); + + // globalThis._VSCODE_CSS_LOAD = function (url) { + // style.textContent += `@import url(${url});\n`; + // }; + + // /** + // * @type { { imports: Record }} + // */ + // const importMap = { imports: {} }; + // for (const cssModule of configuration.cssModules) { + // const cssUrl = new URL(cssModule, baseUrl).href; + // const jsSrc = `globalThis._VSCODE_CSS_LOAD('${cssUrl}');\n`; + // const blob = new Blob([jsSrc], { type: 'application/javascript' }); + // importMap.imports[cssUrl] = URL.createObjectURL(blob); + // } + + // const ttp = window.trustedTypes?.createPolicy('vscode-bootstrapImportMap', { createScript(value) { return value; }, }); + // const importMapSrc = JSON.stringify(importMap, undefined, 2); + // const importMapScript = document.createElement('script'); + // importMapScript.type = 'importmap'; + // importMapScript.setAttribute('nonce', '0c6a828f1297'); + // // @ts-ignore + // importMapScript.textContent = ttp?.createScript(importMapSrc) ?? importMapSrc; + // document.head.appendChild(importMapScript); + + // performance.mark('code/didAddCssLoader'); + // } + + // const result = Promise.all(modulePaths.map(modulePath => { + // if (modulePath.includes('vs/css!')) { + // // ESM/CSS when seeing the old `vs/css!` prefix we use that as a signal to + // // load CSS via a tag + // const cssModule = modulePath.replace('vs/css!', ''); + // const link = document.createElement('link'); + // link.rel = 'stylesheet'; + // link.href = new URL(`${cssModule}.css`, baseUrl).href; + // document.head.appendChild(link); + // return Promise.resolve(); + + // } else { + // // ESM/JS module loading + // return import(new URL(`${modulePath}.js`, baseUrl).href); + // } + // })); + + // result.then((res) => invokeResult(res[0]), onUnexpectedError); + // ESM-uncomment-end + + // ESM-comment-begin /** @type {LoaderConfig} */ const loaderConfig = { - baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out`, - 'vs/nls': nlsConfig, + baseUrl: `${fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out`, preferScriptTags: true }; @@ -120,10 +180,12 @@ // using a fallback such as node.js require which does not exist in sandbox const baseNodeModulesPath = isDev ? '../node_modules' : '../node_modules.asar'; loaderConfig.paths = { + '@vscode/tree-sitter-wasm': `${baseNodeModulesPath}/@vscode/tree-sitter-wasm/wasm/tree-sitter.js`, 'vscode-textmate': `${baseNodeModulesPath}/vscode-textmate/release/main.js`, 'vscode-oniguruma': `${baseNodeModulesPath}/vscode-oniguruma/release/main.js`, 'vsda': `${baseNodeModulesPath}/vsda/index.js`, '@xterm/xterm': `${baseNodeModulesPath}/@xterm/xterm/lib/xterm.js`, + '@xterm/addon-clipboard': `${baseNodeModulesPath}/@xterm/addon-clipboard/lib/addon-clipboard.js`, '@xterm/addon-image': `${baseNodeModulesPath}/@xterm/addon-image/lib/addon-image.js`, '@xterm/addon-search': `${baseNodeModulesPath}/@xterm/addon-search/lib/addon-search.js`, '@xterm/addon-serialize': `${baseNodeModulesPath}/@xterm/addon-serialize/lib/addon-serialize.js`, @@ -144,20 +206,19 @@ // Configure loader require.config(loaderConfig); - // Handle pseudo NLS - if (nlsConfig.pseudo) { - require(['vs/nls'], function (nlsPlugin) { - nlsPlugin.setPseudoTranslation(nlsConfig.pseudo); - }); - } - // Signal before require() if (typeof options?.beforeRequire === 'function') { options.beforeRequire(configuration); } // Actually require the main module as specified - require(modulePaths, async firstModule => { + require(modulePaths, invokeResult, onUnexpectedError); + // ESM-comment-end + + /** + * @param {any} firstModule + */ + async function invokeResult(firstModule) { try { // Callback only after process environment is resolved @@ -172,7 +233,7 @@ } catch (error) { onUnexpectedError(error, enableDeveloperKeybindings); } - }, onUnexpectedError); + } } /** @@ -239,11 +300,35 @@ } /** - * @return {{ fileUriFromPath: (path: string, config: { isWindows?: boolean, scheme?: string, fallbackAuthority?: string }) => string; }} + * @param {string} path + * @param {{ isWindows?: boolean, scheme?: string, fallbackAuthority?: string }} config + * @returns {string} */ - function bootstrap() { - // @ts-ignore (defined in bootstrap.js) - return window.MonacoBootstrap; + function fileUriFromPath(path, config) { + + // Since we are building a URI, we normalize any backslash + // to slashes and we ensure that the path begins with a '/'. + let pathName = path.replace(/\\/g, '/'); + if (pathName.length > 0 && pathName.charAt(0) !== '/') { + pathName = `/${pathName}`; + } + + /** @type {string} */ + let uri; + + // Windows: in order to support UNC paths (which start with '//') + // that have their own authority, we do not use the provided authority + // but rather preserve it. + if (config.isWindows && pathName.startsWith('//')) { + uri = encodeURI(`${config.scheme || 'file'}:${pathName}`); + } + + // Otherwise we optionally add the provided authority if specified + else { + uri = encodeURI(`${config.scheme || 'file'}://${config.fallbackAuthority || ''}${pathName}`); + } + + return uri.replace(/#/g, '%23'); } /** diff --git a/patched-vscode/src/bootstrap.js b/patched-vscode/src/bootstrap.js deleted file mode 100644 index bd0e92e6..00000000 --- a/patched-vscode/src/bootstrap.js +++ /dev/null @@ -1,258 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check -'use strict'; - -// Simple module style to support node.js and browser environments -(function (globalThis, factory) { - - // Node.js - if (typeof exports === 'object') { - module.exports = factory(); - } - - // Browser - else { - // @ts-ignore - globalThis.MonacoBootstrap = factory(); - } -}(this, function () { - const Module = typeof require === 'function' ? require('module') : undefined; - const path = typeof require === 'function' ? require('path') : undefined; - const fs = typeof require === 'function' ? require('fs') : undefined; - const util = typeof require === 'function' ? require('util') : undefined; - - //#region global bootstrapping - - // increase number of stack frames(from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) - Error.stackTraceLimit = 100; - - if (typeof process !== 'undefined' && !process.env['VSCODE_HANDLES_SIGPIPE']) { - // Workaround for Electron not installing a handler to ignore SIGPIPE - // (https://github.com/electron/electron/issues/13254) - let didLogAboutSIGPIPE = false; - process.on('SIGPIPE', () => { - // See https://github.com/microsoft/vscode-remote-release/issues/6543 - // We would normally install a SIGPIPE listener in bootstrap.js - // But in certain situations, the console itself can be in a broken pipe state - // so logging SIGPIPE to the console will cause an infinite async loop - if (!didLogAboutSIGPIPE) { - didLogAboutSIGPIPE = true; - console.error(new Error(`Unexpected SIGPIPE`)); - } - }); - } - - //#endregion - - - //#region Add support for using node_modules.asar - - function enableASARSupport() { - if (!path || !Module || typeof process === 'undefined') { - console.warn('enableASARSupport() is only available in node.js environments'); - return; - } - - const NODE_MODULES_PATH = path.join(__dirname, '../node_modules'); - const NODE_MODULES_ASAR_PATH = `${NODE_MODULES_PATH}.asar`; - - // @ts-ignore - const originalResolveLookupPaths = Module._resolveLookupPaths; - - // @ts-ignore - Module._resolveLookupPaths = function (request, parent) { - const paths = originalResolveLookupPaths(request, parent); - if (Array.isArray(paths)) { - for (let i = 0, len = paths.length; i < len; i++) { - if (paths[i] === NODE_MODULES_PATH) { - paths.splice(i, 0, NODE_MODULES_ASAR_PATH); - break; - } - } - } - - return paths; - }; - } - - //#endregion - - - //#region URI helpers - - /** - * @param {string} path - * @param {{ isWindows?: boolean, scheme?: string, fallbackAuthority?: string }} config - * @returns {string} - */ - function fileUriFromPath(path, config) { - - // Since we are building a URI, we normalize any backslash - // to slashes and we ensure that the path begins with a '/'. - let pathName = path.replace(/\\/g, '/'); - if (pathName.length > 0 && pathName.charAt(0) !== '/') { - pathName = `/${pathName}`; - } - - /** @type {string} */ - let uri; - - // Windows: in order to support UNC paths (which start with '//') - // that have their own authority, we do not use the provided authority - // but rather preserve it. - if (config.isWindows && pathName.startsWith('//')) { - uri = encodeURI(`${config.scheme || 'file'}:${pathName}`); - } - - // Otherwise we optionally add the provided authority if specified - else { - uri = encodeURI(`${config.scheme || 'file'}://${config.fallbackAuthority || ''}${pathName}`); - } - - return uri.replace(/#/g, '%23'); - } - - //#endregion - - - //#region NLS helpers - - /** - * @returns {{locale?: string, availableLanguages: {[lang: string]: string;}, pseudo?: boolean } | undefined} - */ - function setupNLS() { - - // Get the nls configuration as early as possible. - const process = safeProcess(); - /** @type {{ availableLanguages: {}; loadBundle?: (bundle: string, language: string, cb: (err: Error | undefined, result: string | undefined) => void) => void; _resolvedLanguagePackCoreLocation?: string; _corruptedFile?: string }} */ - let nlsConfig = { availableLanguages: {} }; - if (process && process.env['VSCODE_NLS_CONFIG']) { - try { - nlsConfig = JSON.parse(process.env['VSCODE_NLS_CONFIG']); - } catch (e) { - // Ignore - } - } - - if (nlsConfig._resolvedLanguagePackCoreLocation) { - const bundles = Object.create(null); - - /** - * @param {string} bundle - * @param {string} language - * @param {(err: Error | undefined, result: string | undefined) => void} cb - */ - nlsConfig.loadBundle = function (bundle, language, cb) { - const result = bundles[bundle]; - if (result) { - cb(undefined, result); - - return; - } - - // @ts-ignore - safeReadNlsFile(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`).then(function (content) { - const json = JSON.parse(content); - bundles[bundle] = json; - - cb(undefined, json); - }).catch((error) => { - try { - if (nlsConfig._corruptedFile) { - safeWriteNlsFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); }); - } - } finally { - cb(error, undefined); - } - }); - }; - } - - return nlsConfig; - } - - /** - * @returns {typeof import('./vs/base/parts/sandbox/electron-sandbox/globals') | undefined} - */ - function safeSandboxGlobals() { - const globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); - - // @ts-ignore - return globals.vscode; - } - - /** - * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process | undefined} - */ - function safeProcess() { - const sandboxGlobals = safeSandboxGlobals(); - if (sandboxGlobals) { - return sandboxGlobals.process; // Native environment (sandboxed) - } - - if (typeof process !== 'undefined') { - return process; // Native environment (non-sandboxed) - } - - return undefined; - } - - /** - * @returns {import('./vs/base/parts/sandbox/electron-sandbox/electronTypes').IpcRenderer | undefined} - */ - function safeIpcRenderer() { - const sandboxGlobals = safeSandboxGlobals(); - if (sandboxGlobals) { - return sandboxGlobals.ipcRenderer; - } - - return undefined; - } - - /** - * @param {string[]} pathSegments - * @returns {Promise} - */ - async function safeReadNlsFile(...pathSegments) { - const ipcRenderer = safeIpcRenderer(); - if (ipcRenderer) { - return ipcRenderer.invoke('vscode:readNlsFile', ...pathSegments); - } - - if (fs && path && util) { - return (await util.promisify(fs.readFile)(path.join(...pathSegments))).toString(); - } - - throw new Error('Unsupported operation (read NLS files)'); - } - - /** - * @param {string} path - * @param {string} content - * @returns {Promise} - */ - function safeWriteNlsFile(path, content) { - const ipcRenderer = safeIpcRenderer(); - if (ipcRenderer) { - return ipcRenderer.invoke('vscode:writeNlsFile', path, content); - } - - if (fs && util) { - return util.promisify(fs.writeFile)(path, content); - } - - throw new Error('Unsupported operation (write NLS files)'); - } - - //#endregion - - return { - enableASARSupport, - setupNLS, - fileUriFromPath - }; -})); diff --git a/patched-vscode/src/buildfile.js b/patched-vscode/src/buildfile.js deleted file mode 100644 index 9898cd44..00000000 --- a/patched-vscode/src/buildfile.js +++ /dev/null @@ -1,84 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * @param {string} name - * @param {string[]=} exclude - */ -function createModuleDescription(name, exclude) { - - let excludes = ['vs/css', 'vs/nls']; - if (Array.isArray(exclude) && exclude.length > 0) { - excludes = excludes.concat(exclude); - } - - return { - name: name, - include: [], - exclude: excludes - }; -} - -/** - * @param {string} name - */ -function createEditorWorkerModuleDescription(name) { - return createModuleDescription(name, ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']); -} - -exports.base = [ - { - name: 'vs/editor/common/services/editorSimpleWorker', - include: ['vs/base/common/worker/simpleWorker'], - exclude: ['vs/nls'], - prepend: [ - { path: 'vs/loader.js' }, - { path: 'vs/base/worker/workerMain.js' } - ], - dest: 'vs/base/worker/workerMain.js' - }, - { - name: 'vs/base/common/worker/simpleWorker', - exclude: ['vs/nls'], - } -]; - -exports.workerExtensionHost = [createEditorWorkerModuleDescription('vs/workbench/api/worker/extensionHostWorker')]; -exports.workerNotebook = [createEditorWorkerModuleDescription('vs/workbench/contrib/notebook/common/services/notebookSimpleWorker')]; -exports.workerLanguageDetection = [createEditorWorkerModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker')]; -exports.workerLocalFileSearch = [createEditorWorkerModuleDescription('vs/workbench/services/search/worker/localFileSearch')]; -exports.workerProfileAnalysis = [createEditorWorkerModuleDescription('vs/platform/profiling/electron-sandbox/profileAnalysisWorker')]; - -exports.workbenchDesktop = [ - createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), - createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'), - createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), - createModuleDescription('vs/platform/files/node/watcher/watcherMain'), - createModuleDescription('vs/platform/terminal/node/ptyHostMain'), - createModuleDescription('vs/workbench/api/node/extensionHostProcess'), - createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain'), -]; - -exports.workbenchWeb = [ - createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), - createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'), - createModuleDescription('vs/code/browser/workbench/workbench', ['vs/workbench/workbench.web.main']) -]; - -exports.keyboardMaps = [ - createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux'), - createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin'), - createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win') -]; - -exports.code = [ - createModuleDescription('vs/code/electron-main/main'), - createModuleDescription('vs/code/node/cli'), - createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), - createModuleDescription('vs/code/node/sharedProcess/sharedProcessMain'), - createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain') -]; - -exports.entrypoint = createModuleDescription; diff --git a/patched-vscode/src/cli.js b/patched-vscode/src/cli.js index a8aaa1f0..71919773 100644 --- a/patched-vscode/src/cli.js +++ b/patched-vscode/src/cli.js @@ -6,27 +6,48 @@ //@ts-check 'use strict'; -// Delete `VSCODE_CWD` very early even before -// importing bootstrap files. We have seen +// ESM-comment-begin +// Delete `VSCODE_CWD` very early. We have seen // reports where `code .` would use the wrong // current working directory due to our variable // somehow escaping to the parent shell // (https://github.com/microsoft/vscode/issues/126399) delete process.env['VSCODE_CWD']; - -const bootstrap = require('./bootstrap'); const bootstrapNode = require('./bootstrap-node'); -const product = require('../product.json'); +const bootstrapAmd = require('./bootstrap-amd'); +const { resolveNLSConfiguration } = require('./vs/base/node/nls'); +const product = require('./bootstrap-meta').product; +// ESM-comment-end +// ESM-uncomment-begin +// import './bootstrap-cli.js'; // this MUST come before other imports as it changes global state +// import * as path from 'path'; +// import { fileURLToPath } from 'url'; +// import * as bootstrapNode from './bootstrap-node.js'; +// import * as bootstrapAmd from './bootstrap-amd.js'; +// import { resolveNLSConfiguration } from './vs/base/node/nls.js'; +// import { product } from './bootstrap-meta.js'; +// +// const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// ESM-uncomment-end + +async function start() { + + // NLS + const nlsConfiguration = await resolveNLSConfiguration({ userLocale: 'en', osLocale: 'en', commit: product.commit, userDataPath: '', nlsMetadataPath: __dirname }); + process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfiguration); // required for `bootstrap-amd` to pick up NLS messages + + // Enable portable support + // @ts-ignore + bootstrapNode.configurePortable(product); -// Enable portable support -// @ts-ignore -bootstrapNode.configurePortable(product); + // Enable ASAR support + bootstrapNode.enableASARSupport(); -// Enable ASAR support -bootstrap.enableASARSupport(); + // Signal processes that we got launched as CLI + process.env['VSCODE_CLI'] = '1'; -// Signal processes that we got launched as CLI -process.env['VSCODE_CLI'] = '1'; + // Load CLI through AMD loader + bootstrapAmd.load('vs/code/node/cli'); +} -// Load CLI through AMD loader -require('./bootstrap-amd').load('vs/code/node/cli'); +start(); diff --git a/patched-vscode/src/main.js b/patched-vscode/src/main.js index d4bc388f..719f51a4 100644 --- a/patched-vscode/src/main.js +++ b/patched-vscode/src/main.js @@ -7,32 +7,54 @@ 'use strict'; /** - * @typedef {import('./vs/base/common/product').IProductConfiguration} IProductConfiguration - * @typedef {import('./vs/base/node/languagePacks').NLSConfiguration} NLSConfiguration - * @typedef {import('./vs/platform/environment/common/argv').NativeParsedArgs} NativeParsedArgs + * @import { INLSConfiguration } from './vs/nls' + * @import { NativeParsedArgs } from './vs/platform/environment/common/argv' */ -const perf = require('./vs/base/common/performance'); -perf.mark('code/didStartMain'); - +// ESM-comment-begin const path = require('path'); -const fs = require('fs'); +const fs = require('original-fs'); const os = require('os'); -const bootstrap = require('./bootstrap'); const bootstrapNode = require('./bootstrap-node'); -const { getUserDataPath } = require('./vs/platform/environment/node/userDataPath'); -const { stripComments } = require('./vs/base/common/stripComments'); +const bootstrapAmd = require('./bootstrap-amd'); +const { getUserDataPath } = require(`./vs/platform/environment/node/userDataPath`); +const { parse } = require('./vs/base/common/jsonc'); +const perf = require('./vs/base/common/performance'); +const { resolveNLSConfiguration } = require('./vs/base/node/nls'); const { getUNCHost, addUNCHostToAllowlist } = require('./vs/base/node/unc'); -/** @type {Partial} */ -// @ts-ignore -const product = require('../product.json'); -const { app, protocol, crashReporter, Menu } = require('electron'); +const product = require('./bootstrap-meta').product; +const { app, protocol, crashReporter, Menu, contentTracing } = require('electron'); +// ESM-comment-end +// ESM-uncomment-begin +// import * as path from 'path'; +// import * as fs from 'original-fs'; +// import * as os from 'os'; +// import * as bootstrapNode from './bootstrap-node.js'; +// import * as bootstrapAmd from './bootstrap-amd.js'; +// import { fileURLToPath } from 'url'; +// import { app, protocol, crashReporter, Menu, contentTracing } from 'electron'; +// import minimist from 'minimist'; +// import { product } from './bootstrap-meta.js'; +// import { parse } from './vs/base/common/jsonc.js'; +// import { getUserDataPath } from './vs/platform/environment/node/userDataPath.js'; +// import * as perf from './vs/base/common/performance.js'; +// import { resolveNLSConfiguration } from './vs/base/node/nls.js'; +// import { getUNCHost, addUNCHostToAllowlist } from './vs/base/node/unc.js'; +// +// const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// ESM-uncomment-end + +perf.mark('code/didStartMain'); // Enable portable support const portable = bootstrapNode.configurePortable(product); // Enable ASAR support -bootstrap.enableASARSupport(); +bootstrapNode.enableASARSupport(); + +// ESM-comment-begin +const minimist = require('minimist'); // !!! IMPORTANT: MUST come after bootstrap#enableASARSupport +// ESM-comment-end const args = parseCLIArgs(); // Configure static command line arguments @@ -109,27 +131,28 @@ protocol.registerSchemesAsPrivileged([ registerListeners(); /** - * Support user defined locale: load it early before app('ready') - * to have more things running in parallel. + * We can resolve the NLS configuration early if it is defined + * in argv.json before `app.ready` event. Otherwise we can only + * resolve NLS after `app.ready` event to resolve the OS locale. * - * @type {Promise | undefined} + * @type {Promise | undefined} */ let nlsConfigurationPromise = undefined; -/** - * @type {String} - **/ // Use the most preferred OS language for language recommendation. // The API might return an empty array on Linux, such as when // the 'C' locale is the user's only configured locale. // No matter the OS, if the array is empty, default back to 'en'. -const resolved = app.getPreferredSystemLanguages()?.[0] ?? 'en'; -const osLocale = processZhLocale(resolved.toLowerCase()); -const metaDataFile = path.join(__dirname, 'nls.metadata.json'); -const locale = getUserDefinedLocale(argvConfig); -if (locale) { - const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); - nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale, osLocale); +const osLocale = processZhLocale((app.getPreferredSystemLanguages()?.[0] ?? 'en').toLowerCase()); +const userLocale = getUserDefinedLocale(argvConfig); +if (userLocale) { + nlsConfigurationPromise = resolveNLSConfiguration({ + userLocale, + osLocale, + commit: product.commit, + userDataPath, + nlsMetadataPath: __dirname + }); } // Pass in the locale to Electron so that the @@ -141,15 +164,13 @@ if (locale) { // In that case, use `en` as the Electron locale. if (process.platform === 'win32' || process.platform === 'linux') { - const electronLocale = (!locale || locale === 'qps-ploc') ? 'en' : locale; + const electronLocale = (!userLocale || userLocale === 'qps-ploc') ? 'en' : userLocale; app.commandLine.appendSwitch('lang', electronLocale); } // Load our code once ready app.once('ready', function () { if (args['trace']) { - const contentTracing = require('electron').contentTracing; - const traceOptions = { categoryFilter: args['trace-category-filter'] || '*', traceOptions: args['trace-options'] || 'record-until-full,enable-sampling' @@ -161,37 +182,38 @@ app.once('ready', function () { } }); +async function onReady() { + perf.mark('code/mainAppReady'); + + try { + const [, nlsConfig] = await Promise.all([ + mkdirpIgnoreError(codeCachePath), + resolveNlsConfiguration() + ]); + + startup(codeCachePath, nlsConfig); + } catch (error) { + console.error(error); + } +} + /** * Main startup routine * * @param {string | undefined} codeCachePath - * @param {NLSConfiguration} nlsConfig + * @param {INLSConfiguration} nlsConfig */ function startup(codeCachePath, nlsConfig) { - nlsConfig._languagePackSupport = true; - process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || ''; // Load main in AMD perf.mark('code/willLoadMainBundle'); - require('./bootstrap-amd').load('vs/code/electron-main/main', () => { + bootstrapAmd.load('vs/code/electron-main/main', () => { perf.mark('code/didLoadMainBundle'); }); } -async function onReady() { - perf.mark('code/mainAppReady'); - - try { - const [, nlsConfig] = await Promise.all([mkdirpIgnoreError(codeCachePath), resolveNlsConfiguration()]); - - startup(codeCachePath, nlsConfig); - } catch (error) { - console.error(error); - } -} - /** * @param {NativeParsedArgs} cliArgs */ @@ -205,10 +227,14 @@ function configureCommandlineSwitchesSync(cliArgs) { 'force-color-profile', // disable LCD font rendering, a Chromium flag - 'disable-lcd-text' + 'disable-lcd-text', + + // bypass any specified proxy for the given semi-colon-separated list of hosts + 'proxy-bypass-list' ]; if (process.platform === 'linux') { + // Force enable screen readers on Linux via this flag SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); @@ -243,10 +269,7 @@ function configureCommandlineSwitchesSync(cliArgs) { app.commandLine.appendSwitch(argvKey); } } else if (argvValue) { - if (argvKey === 'force-color-profile') { - // Color profile - app.commandLine.appendSwitch(argvKey, argvValue); - } else if (argvKey === 'password-store') { + if (argvKey === 'password-store') { // Password store // TODO@TylerLeonhardt: Remove this migration in 3 months let migratedArgvValue = argvValue; @@ -254,6 +277,8 @@ function configureCommandlineSwitchesSync(cliArgs) { migratedArgvValue = 'gnome-libsecret'; } app.commandLine.appendSwitch(argvKey, migratedArgvValue); + } else { + app.commandLine.appendSwitch(argvKey, argvValue); } } } @@ -295,8 +320,7 @@ function configureCommandlineSwitchesSync(cliArgs) { app.commandLine.appendSwitch('disable-features', featuresToDisable); // Blink features to configure. - // `FontMatchingCTMigration` - Siwtch font matching on macOS to CoreText (Refs https://github.com/microsoft/vscode/issues/214390). - // TODO(deepak1556): Enable this feature again after updating to Electron 30. + // `FontMatchingCTMigration` - Siwtch font matching on macOS to Appkit (Refs https://github.com/microsoft/vscode/issues/224496#issuecomment-2270418470). const blinkFeaturesToDisable = `FontMatchingCTMigration,${app.commandLine.getSwitchValue('disable-blink-features')}`; app.commandLine.appendSwitch('disable-blink-features', blinkFeaturesToDisable); @@ -316,7 +340,7 @@ function readArgvConfigSync() { const argvConfigPath = getArgvConfigPath(); let argvConfig; try { - argvConfig = JSON.parse(stripComments(fs.readFileSync(argvConfigPath).toString())); + argvConfig = parse(fs.readFileSync(argvConfigPath).toString()); } catch (error) { if (error && error.code === 'ENOENT') { createDefaultArgvConfigSync(argvConfigPath); @@ -494,8 +518,6 @@ function getJSFlags(cliArgs) { * @returns {NativeParsedArgs} */ function parseCLIArgs() { - const minimist = require('minimist'); - return minimist(process.argv, { string: [ 'user-data-dir', @@ -584,16 +606,6 @@ function getCodeCachePath() { return path.join(userDataPath, 'CachedData', commit); } -/** - * @param {string} dir - * @returns {Promise} - */ -function mkdirp(dir) { - return new Promise((resolve, reject) => { - fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? reject(err) : resolve(dir)); - }); -} - /** * @param {string | undefined} dir * @returns {Promise} @@ -601,7 +613,7 @@ function mkdirp(dir) { async function mkdirpIgnoreError(dir) { if (typeof dir === 'string') { try { - await mkdirp(dir); + await fs.promises.mkdir(dir, { recursive: true }); return dir; } catch (error) { @@ -641,35 +653,46 @@ function processZhLocale(appLocale) { /** * Resolve the NLS configuration * - * @return {Promise} + * @return {Promise} */ async function resolveNlsConfiguration() { - // First, we need to test a user defined locale. If it fails we try the app locale. + // First, we need to test a user defined locale. + // If it fails we try the app locale. // If that fails we fall back to English. - let nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined; + + const nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined; if (nlsConfiguration) { return nlsConfiguration; } - // Try to use the app locale. Please note that the app locale is only - // valid after we have received the app ready event. This is why the - // code is here. + // Try to use the app locale which is only valid + // after the app ready event has been fired. - /** - * @type string - */ - let appLocale = app.getLocale(); - if (!appLocale) { - return { locale: 'en', osLocale, availableLanguages: {} }; + let userLocale = app.getLocale(); + if (!userLocale) { + return { + userLocale: 'en', + osLocale, + resolvedLanguage: 'en', + defaultMessagesFile: path.join(__dirname, 'nls.messages.json'), + + // NLS: below 2 are a relic from old times only used by vscode-nls and deprecated + locale: 'en', + availableLanguages: {} + }; } // See above the comment about the loader and case sensitiveness - appLocale = processZhLocale(appLocale.toLowerCase()); - - const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); - nlsConfiguration = await getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale, osLocale); - return nlsConfiguration ?? { locale: 'en', osLocale, availableLanguages: {} }; + userLocale = processZhLocale(userLocale.toLowerCase()); + + return resolveNLSConfiguration({ + userLocale, + osLocale, + commit: product.commit, + userDataPath, + nlsMetadataPath: __dirname + }); } /** @@ -687,7 +710,7 @@ function getUserDefinedLocale(argvConfig) { return locale.toLowerCase(); // a directly provided --locale always wins } - return argvConfig.locale && typeof argvConfig.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined; + return typeof argvConfig?.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined; } //#endregion diff --git a/patched-vscode/src/server-cli.js b/patched-vscode/src/server-cli.js index fdfdac48..896e4f4c 100644 --- a/patched-vscode/src/server-cli.js +++ b/patched-vscode/src/server-cli.js @@ -4,18 +4,44 @@ *--------------------------------------------------------------------------------------------*/ // @ts-check +'use strict'; -const path = require('path'); - +// ESM-comment-begin // Keep bootstrap-amd.js from redefining 'fs'. delete process.env['ELECTRON_RUN_AS_NODE']; +const path = require('path'); +const bootstrapNode = require('./bootstrap-node'); +const bootstrapAmd = require('./bootstrap-amd'); +const { resolveNLSConfiguration } = require('./vs/base/node/nls'); +const product = require('./bootstrap-meta').product; +// ESM-comment-end +// ESM-uncomment-begin +// import './bootstrap-server.js'; // this MUST come before other imports as it changes global state +// import * as path from 'path'; +// import { fileURLToPath } from 'url'; +// import * as bootstrapNode from './bootstrap-node.js'; +// import * as bootstrapAmd from './bootstrap-amd.js'; +// import { resolveNLSConfiguration } from './vs/base/node/nls.js'; +// import { product } from './bootstrap-meta.js'; +// +// const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// ESM-uncomment-end -if (process.env['VSCODE_DEV']) { - // When running out of sources, we need to load node modules from remote/node_modules, - // which are compiled against nodejs, not electron - process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'] = process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'] || path.join(__dirname, '..', 'remote', 'node_modules'); - require('./bootstrap-node').injectNodeModuleLookupPath(process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']); -} else { - delete process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']; +async function start() { + + // NLS + const nlsConfiguration = await resolveNLSConfiguration({ userLocale: 'en', osLocale: 'en', commit: product.commit, userDataPath: '', nlsMetadataPath: __dirname }); + process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfiguration); // required for `bootstrap-amd` to pick up NLS messages + + if (process.env['VSCODE_DEV']) { + // When running out of sources, we need to load node modules from remote/node_modules, + // which are compiled against nodejs, not electron + process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH'] = process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH'] || path.join(__dirname, '..', 'remote', 'node_modules'); + bootstrapNode.devInjectNodeModuleLookupPath(process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH']); + } else { + delete process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH']; + } + bootstrapAmd.load('vs/server/node/server.cli'); } -require('./bootstrap-amd').load('vs/server/node/server.cli'); + +start(); diff --git a/patched-vscode/src/server-main.js b/patched-vscode/src/server-main.js index 81e88e11..e3dd44e4 100644 --- a/patched-vscode/src/server-main.js +++ b/patched-vscode/src/server-main.js @@ -4,19 +4,52 @@ *--------------------------------------------------------------------------------------------*/ // @ts-check +'use strict'; -const perf = require('./vs/base/common/performance'); -const performance = require('perf_hooks').performance; -const product = require('../product.json'); -const readline = require('readline'); +/** + * @import { INLSConfiguration } from './vs/nls' + * @import { IServerAPI } from './vs/server/node/remoteExtensionHostAgentServer' + */ + +// ESM-comment-begin +// Keep bootstrap-amd.js from redefining 'fs'. +delete process.env['ELECTRON_RUN_AS_NODE']; + +const path = require('path'); const http = require('http'); +const os = require('os'); +const readline = require('readline'); +const performance = require('perf_hooks').performance; +const bootstrapNode = require('./bootstrap-node'); +const bootstrapAmd = require('./bootstrap-amd'); +const { resolveNLSConfiguration } = require('./vs/base/node/nls'); +const product = require('./bootstrap-meta').product; +const perf = require(`./vs/base/common/performance`); +const minimist = require('minimist'); +// ESM-comment-end +// ESM-uncomment-begin +// import './bootstrap-server.js'; // this MUST come before other imports as it changes global state +// import * as path from 'path'; +// import * as http from 'http'; +// import * as os from 'os'; +// import * as readline from 'readline'; +// import { performance }from 'perf_hooks'; +// import { fileURLToPath } from 'url'; +// import minimist from 'minimist'; +// import * as bootstrapNode from './bootstrap-node.js'; +// import * as bootstrapAmd from './bootstrap-amd.js'; +// import { resolveNLSConfiguration } from './vs/base/node/nls.js'; +// import { product } from './bootstrap-meta.js'; +// import * as perf from './vs/base/common/performance.js'; +// +// const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// ESM-uncomment-end perf.mark('code/server/start'); // @ts-ignore global.vscodeServerStartTime = performance.now(); async function start() { - const minimist = require('minimist'); // Do a quick parse to determine if a server or the cli needs to be started const parsedArgs = minimist(process.argv.slice(2), { @@ -38,16 +71,15 @@ async function start() { const shouldSpawnCli = parsedArgs.help || parsedArgs.version || extensionLookupArgs.some(a => !!parsedArgs[a]) || (extensionInstallArgs.some(a => !!parsedArgs[a]) && !parsedArgs['start-server']); + const nlsConfiguration = await resolveNLSConfiguration({ userLocale: 'en', osLocale: 'en', commit: product.commit, userDataPath: '', nlsMetadataPath: __dirname }); + if (shouldSpawnCli) { - loadCode().then((mod) => { + loadCode(nlsConfiguration).then((mod) => { mod.spawnCli(); }); return; } - /** - * @typedef { import('./vs/server/node/remoteExtensionHostAgentServer').IServerAPI } IServerAPI - */ /** @type {IServerAPI | null} */ let _remoteExtensionHostAgentServer = null; /** @type {Promise | null} */ @@ -55,7 +87,7 @@ async function start() { /** @returns {Promise} */ const getRemoteExtensionHostAgentServer = () => { if (!_remoteExtensionHostAgentServerPromise) { - _remoteExtensionHostAgentServerPromise = loadCode().then(async (mod) => { + _remoteExtensionHostAgentServerPromise = loadCode(nlsConfiguration).then(async (mod) => { const server = await mod.createServer(address); _remoteExtensionHostAgentServer = server; return server; @@ -64,9 +96,6 @@ async function start() { return _remoteExtensionHostAgentServerPromise; }; - const http = require('http'); - const os = require('os'); - if (Array.isArray(product.serverLicense) && product.serverLicense.length) { console.log(product.serverLicense.join('\n')); if (product.serverLicensePrompt && parsedArgs['accept-server-license-terms'] !== true) { @@ -248,15 +277,18 @@ async function findFreePort(host, start, end) { return undefined; } -/** @returns { Promise } */ -function loadCode() { +/** + * @param {INLSConfiguration} nlsConfiguration + * @returns { Promise } + */ +function loadCode(nlsConfiguration) { return new Promise((resolve, reject) => { - const path = require('path'); - delete process.env['ELECTRON_RUN_AS_NODE']; // Keep bootstrap-amd.js from redefining 'fs'. + /** @type {INLSConfiguration} */ + process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfiguration); // required for `bootstrap-amd` to pick up NLS messages // See https://github.com/microsoft/vscode-remote-release/issues/6543 - // We would normally install a SIGPIPE listener in bootstrap.js + // We would normally install a SIGPIPE listener in bootstrap-node.js // But in certain situations, the console itself can be in a broken pipe state // so logging SIGPIPE to the console will cause an infinite async loop process.env['VSCODE_HANDLES_SIGPIPE'] = 'true'; @@ -264,12 +296,12 @@ function loadCode() { if (process.env['VSCODE_DEV']) { // When running out of sources, we need to load node modules from remote/node_modules, // which are compiled against nodejs, not electron - process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'] = process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH'] || path.join(__dirname, '..', 'remote', 'node_modules'); - require('./bootstrap-node').injectNodeModuleLookupPath(process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']); + process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH'] = process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH'] || path.join(__dirname, '..', 'remote', 'node_modules'); + bootstrapNode.devInjectNodeModuleLookupPath(process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH']); } else { - delete process.env['VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH']; + delete process.env['VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH']; } - require('./bootstrap-amd').load('vs/server/node/server.main', resolve, reject); + bootstrapAmd.load('vs/server/node/server.main', resolve, reject); }); } @@ -307,5 +339,4 @@ function prompt(question) { }); } - start(); diff --git a/patched-vscode/src/tsconfig.json b/patched-vscode/src/tsconfig.json index 2ac81949..096655d9 100644 --- a/patched-vscode/src/tsconfig.json +++ b/patched-vscode/src/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { + "esModuleInterop": true, "removeComments": false, "preserveConstEnums": true, "sourceMap": false, @@ -24,15 +25,28 @@ ] }, "include": [ - "./bootstrap.js", "./bootstrap-amd.js", + "./bootstrap-cli.js", "./bootstrap-fork.js", + "./bootstrap-import.js", + "./bootstrap-meta.js", "./bootstrap-node.js", + "./bootstrap-server.js", "./bootstrap-window.js", "./cli.js", "./main.js", "./server-main.js", "./server-cli.js", + "./vs/base/common/jsonc.js", + "./vs/base/common/performance.js", + "./vs/base/node/unc.js", + "./vs/base/node/nls.js", + "./vs/platform/environment/node/userDataPath.js", + "./vs/base/parts/sandbox/electron-sandbox/preload-aux.js", + "./vs/base/parts/sandbox/electron-sandbox/preload.js", + "./vs/code/electron-sandbox/processExplorer/processExplorer.js", + "./vs/code/electron-sandbox/workbench/workbench.js", + "./vs/workbench/contrib/issue/electron-sandbox/issueReporter.js", "./typings", "./vs/**/*.ts", "vscode-dts/vscode.proposed.*.d.ts", diff --git a/patched-vscode/src/tsconfig.monaco.json b/patched-vscode/src/tsconfig.monaco.json index 988f0485..bb4fd1cc 100644 --- a/patched-vscode/src/tsconfig.monaco.json +++ b/patched-vscode/src/tsconfig.monaco.json @@ -8,10 +8,10 @@ ], "paths": {}, "module": "amd", - "moduleResolution": "classic", + "moduleResolution": "node", "removeComments": false, "preserveConstEnums": true, - "target": "es2018", + "target": "ES2022", "sourceMap": false, "declaration": true }, @@ -19,6 +19,7 @@ "typings/require.d.ts", "typings/thenable.d.ts", "typings/vscode-globals-product.d.ts", + "typings/vscode-globals-nls.d.ts", "vs/loader.d.ts", "vs/monaco.d.ts", "vs/editor/*", diff --git a/patched-vscode/src/tsconfig.tsec.json b/patched-vscode/src/tsconfig.tsec.json index d2524df2..d822b0a4 100644 --- a/patched-vscode/src/tsconfig.tsec.json +++ b/patched-vscode/src/tsconfig.tsec.json @@ -10,6 +10,7 @@ ] }, "exclude": [ + "./vs/workbench/contrib/webview/browser/pre/service-worker.js", "*/test/*", "**/*.test.ts" ] diff --git a/patched-vscode/src/tsec.exemptions.json b/patched-vscode/src/tsec.exemptions.json index 5fef1715..dd369477 100644 --- a/patched-vscode/src/tsec.exemptions.json +++ b/patched-vscode/src/tsec.exemptions.json @@ -39,5 +39,8 @@ ], "ban-element-insertadjacenthtml": [ "**/*.ts" + ], + "ban-script-content-assignments": [ + "bootstrap-window.js" ] } diff --git a/patched-vscode/src/typings/vscode-globals-modules.d.ts b/patched-vscode/src/typings/vscode-globals-modules.d.ts index c538b99b..cfeeac71 100644 --- a/patched-vscode/src/typings/vscode-globals-modules.d.ts +++ b/patched-vscode/src/typings/vscode-globals-modules.d.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// AMD2ESM mirgation relevant +// AMD2ESM migration relevant declare global { /** - * @deprecated node modules that are in used in a context that + * TODO@esm @deprecated node modules that are in used in a context that * shouldn't have access to node_modules (node-free renderer or * shared process) */ diff --git a/patched-vscode/src/typings/vscode-globals-nls.d.ts b/patched-vscode/src/typings/vscode-globals-nls.d.ts new file mode 100644 index 00000000..2b5d48e1 --- /dev/null +++ b/patched-vscode/src/typings/vscode-globals-nls.d.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// AMD2ESM migration relevant + +/** + * NLS Globals: these need to be defined in all contexts that make + * use of our `nls.localize` and `nls.localize2` functions. This includes: + * - Electron main process + * - Electron window (renderer) process + * - Utility Process + * - Node.js + * - Browser + * - Web worker + * + * That is because during build time we strip out all english strings from + * the resulting JS code and replace it with a that is then looked + * up from the `_VSCODE_NLS_MESSAGES` array. + */ +declare global { + /** + * All NLS messages produced by `localize` and `localize2` calls + * under `src/vs` translated to the language as indicated by + * `_VSCODE_NLS_LANGUAGE`. + * + * Instead of accessing this global variable directly, use function getNLSMessages. + */ + var _VSCODE_NLS_MESSAGES: string[]; + /** + * The actual language of the NLS messages (e.g. 'en', de' or 'pt-br'). + * + * Instead of accessing this global variable directly, use function getNLSLanguage. + */ + var _VSCODE_NLS_LANGUAGE: string | undefined; +} + +// fake export to make global work +export { } diff --git a/patched-vscode/src/typings/vscode-globals-product.d.ts b/patched-vscode/src/typings/vscode-globals-product.d.ts index 780a6477..2cd632e7 100644 --- a/patched-vscode/src/typings/vscode-globals-product.d.ts +++ b/patched-vscode/src/typings/vscode-globals-product.d.ts @@ -3,10 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// AMD2ESM mirgation relevant +// AMD2ESM migration relevant declare global { + /** + * Holds the file root for resources. + */ + var _VSCODE_FILE_ROOT: string; + + /** + * CSS loader that's available during development time. + * DO NOT call directly, instead just import css modules, like `import 'some.css'` + */ + var _VSCODE_CSS_LOAD: (module: string) => void; + /** * @deprecated You MUST use `IProductService` whenever possible. */ diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookWorkerHost.ts b/patched-vscode/src/typings/vscode-globals-ttp.d.ts similarity index 59% rename from patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookWorkerHost.ts rename to patched-vscode/src/typings/vscode-globals-ttp.d.ts index 44a4e22d..b91080ec 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookWorkerHost.ts +++ b/patched-vscode/src/typings/vscode-globals-ttp.d.ts @@ -3,7 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export interface INotebookWorkerHost { - // foreign host request - fhr(method: string, args: any[]): Promise; +// AMD2ESM migration relevant + +declare global { + + var _VSCODE_WEB_PACKAGE_TTP: Pick, 'name' | 'createScriptURL'> | undefined; } + +// fake export to make global work +export { } diff --git a/patched-vscode/src/vs/amdX.ts b/patched-vscode/src/vs/amdX.ts index ba164787..60d61fab 100644 --- a/patched-vscode/src/vs/amdX.ts +++ b/patched-vscode/src/vs/amdX.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isESM } from 'vs/base/common/amd'; -import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath } from 'vs/base/common/network'; +import { isESM, canASAR } from 'vs/base/common/amd'; +import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath, Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network'; import * as platform from 'vs/base/common/platform'; import { IProductConfiguration } from 'vs/base/common/product'; +import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; - class DefineCall { constructor( public readonly id: string | null | undefined, @@ -38,7 +38,7 @@ class AMDModuleImporter { } this._initialized = true; - (globalThis).define = (id: any, dependencies: any, callback: any) => { + (globalThis as any).define = (id: any, dependencies: any, callback: any) => { if (typeof id !== 'string') { callback = dependencies; dependencies = id; @@ -54,24 +54,24 @@ class AMDModuleImporter { this._defineCalls.push(new DefineCall(id, dependencies, callback)); }; - (globalThis).define.amd = true; + (globalThis as any).define.amd = true; if (this._isRenderer) { // eslint-disable-next-line no-restricted-globals - this._amdPolicy = window.trustedTypes?.createPolicy('amdLoader', { + this._amdPolicy = (globalThis as any)._VSCODE_WEB_PACKAGE_TTP ?? window.trustedTypes?.createPolicy('amdLoader', { createScriptURL(value) { // eslint-disable-next-line no-restricted-globals if (value.startsWith(window.location.origin)) { return value; } - if (value.startsWith('vscode-file://vscode-app')) { + if (value.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}`)) { return value; } throw new Error(`[trusted_script_src] Invalid script url: ${value}`); } }); } else if (this._isWebWorker) { - this._amdPolicy = (globalThis).trustedTypes?.createPolicy('amdLoader', { + this._amdPolicy = (globalThis as any)._VSCODE_WEB_PACKAGE_TTP ?? (globalThis as any).trustedTypes?.createPolicy('amdLoader', { createScriptURL(value: string) { return value; } @@ -83,14 +83,31 @@ class AMDModuleImporter { this._initialize(); const defineCall = await (this._isWebWorker ? this._workerLoadScript(scriptSrc) : this._isRenderer ? this._rendererLoadScript(scriptSrc) : this._nodeJSLoadScript(scriptSrc)); if (!defineCall) { - throw new Error(`Did not receive a define call from script ${scriptSrc}`); + // throw new Error(`Did not receive a define call from script ${scriptSrc}`); + console.warn(`Did not receive a define call from script ${scriptSrc}`); + return undefined; + } + // TODO require, module + const exports = {}; + const dependencyObjs: any[] = []; + const dependencyModules: string[] = []; + + if (Array.isArray(defineCall.dependencies)) { + + for (const mod of defineCall.dependencies) { + if (mod === 'exports') { + dependencyObjs.push(exports); + } else { + dependencyModules.push(mod); + } + } } - // TODO require, exports, module - if (Array.isArray(defineCall.dependencies) && defineCall.dependencies.length > 0) { - throw new Error(`Cannot resolve dependencies for script ${scriptSrc}. The dependencies are: ${defineCall.dependencies.join(', ')}`); + + if (dependencyModules.length > 0) { + throw new Error(`Cannot resolve dependencies for script ${scriptSrc}. The dependencies are: ${dependencyModules.join(', ')}`); } if (typeof defineCall.callback === 'function') { - return defineCall.callback([]); + return defineCall.callback(...dependencyObjs) ?? exports; } else { return defineCall.callback; } @@ -128,25 +145,23 @@ class AMDModuleImporter { }); } - private _workerLoadScript(scriptSrc: string): Promise { - return new Promise((resolve, reject) => { - try { - if (this._amdPolicy) { - scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as any as string; - } - importScripts(scriptSrc); - resolve(this._defineCalls.pop()); - } catch (err) { - reject(err); - } - }); + private async _workerLoadScript(scriptSrc: string): Promise { + if (this._amdPolicy) { + scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as any as string; + } + if (isESM) { + await import(scriptSrc); + } else { + importScripts(scriptSrc); + } + return this._defineCalls.pop(); } private async _nodeJSLoadScript(scriptSrc: string): Promise { try { - const fs = globalThis._VSCODE_NODE_MODULES['fs']; - const vm = globalThis._VSCODE_NODE_MODULES['vm']; - const module = globalThis._VSCODE_NODE_MODULES['module']; + const fs = (globalThis as any)._VSCODE_NODE_MODULES['fs']; + const vm = (globalThis as any)._VSCODE_NODE_MODULES['vm']; + const module = (globalThis as any)._VSCODE_NODE_MODULES['module']; const filePath = URI.parse(scriptSrc).fsPath; const content = fs.readFileSync(filePath).toString(); @@ -164,11 +179,6 @@ class AMDModuleImporter { const cache = new Map>(); -let _paths: Record = {}; -if (typeof globalThis.require === 'object') { - _paths = (>globalThis.require).paths ?? {}; -} - /** * Utility for importing an AMD node module. This util supports AMD and ESM contexts and should be used while the ESM adoption * is on its way. @@ -180,14 +190,10 @@ export async function importAMDNodeModule(nodeModuleName: string, pathInsideN if (isBuilt === undefined) { const product = globalThis._VSCODE_PRODUCT_JSON as unknown as IProductConfiguration; - isBuilt = Boolean((product ?? (globalThis).vscode?.context?.configuration()?.product)?.commit); - } - - if (_paths[nodeModuleName]) { - nodeModuleName = _paths[nodeModuleName]; + isBuilt = Boolean((product ?? (globalThis as any).vscode?.context?.configuration()?.product)?.commit); } - const nodeModulePath = `${nodeModuleName}/${pathInsideNodeModule}`; + const nodeModulePath = pathInsideNodeModule ? `${nodeModuleName}/${pathInsideNodeModule}` : nodeModuleName; if (cache.has(nodeModulePath)) { return cache.get(nodeModulePath)!; } @@ -197,7 +203,7 @@ export async function importAMDNodeModule(nodeModuleName: string, pathInsideN // bit of a special case for: src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts scriptSrc = nodeModulePath; } else { - const useASAR = (isBuilt && !platform.isWeb); + const useASAR = (canASAR && isBuilt && !platform.isWeb); const actualNodeModulesPath = (useASAR ? nodeModulesAsarPath : nodeModulesPath); const resourcePath: AppResourcePath = `${actualNodeModulesPath}/${nodeModulePath}`; scriptSrc = FileAccess.asBrowserUri(resourcePath).toString(true); @@ -209,3 +215,16 @@ export async function importAMDNodeModule(nodeModuleName: string, pathInsideN return await import(nodeModuleName); } } + +export function resolveAmdNodeModulePath(nodeModuleName: string, pathInsideNodeModule: string): string { + assertType(isESM); + + const product = globalThis._VSCODE_PRODUCT_JSON as unknown as IProductConfiguration; + const isBuilt = Boolean((product ?? (globalThis as any).vscode?.context?.configuration()?.product)?.commit); + const useASAR = (canASAR && isBuilt && !platform.isWeb); + + const nodeModulePath = `${nodeModuleName}/${pathInsideNodeModule}`; + const actualNodeModulesPath = (useASAR ? nodeModulesAsarPath : nodeModulesPath); + const resourcePath: AppResourcePath = `${actualNodeModulesPath}/${nodeModulePath}`; + return FileAccess.asBrowserUri(resourcePath).toString(true); +} diff --git a/patched-vscode/src/vs/base/browser/browser.ts b/patched-vscode/src/vs/base/browser/browser.ts index 77f55b94..f3e6b43c 100644 --- a/patched-vscode/src/vs/base/browser/browser.ts +++ b/patched-vscode/src/vs/base/browser/browser.ts @@ -133,3 +133,9 @@ export function isStandalone(): boolean { export function isWCOEnabled(): boolean { return (navigator as any)?.windowControlsOverlay?.visible; } + +// Returns the bounding rect of the titlebar area if it is supported and defined +// See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/getTitlebarAreaRect +export function getWCOTitlebarAreaRect(targetWindow: Window): DOMRect | undefined { + return (targetWindow.navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); +} diff --git a/patched-vscode/src/vs/base/browser/defaultWorkerFactory.ts b/patched-vscode/src/vs/base/browser/defaultWorkerFactory.ts index 71391b06..7d013997 100644 --- a/patched-vscode/src/vs/base/browser/defaultWorkerFactory.ts +++ b/patched-vscode/src/vs/base/browser/defaultWorkerFactory.ts @@ -5,20 +5,38 @@ import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { COI } from 'vs/base/common/network'; -import { IWorker, IWorkerCallback, IWorkerFactory, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker'; +import { AppResourcePath, COI, FileAccess } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { IWorker, IWorkerCallback, IWorkerClient, IWorkerDescriptor, IWorkerFactory, logOnceWebWorkerWarning, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { coalesce } from 'vs/base/common/arrays'; +import { getNLSLanguage, getNLSMessages } from 'vs/nls'; -const ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value }); +// ESM-comment-begin +const isESM = false; +// ESM-comment-end +// ESM-uncomment-begin +// const isESM = true; +// ESM-uncomment-end + +// Reuse the trusted types policy defined from worker bootstrap +// when available. +// Refs https://github.com/microsoft/vscode/issues/222193 +let ttPolicy: ReturnType; +if (typeof self === 'object' && self.constructor && self.constructor.name === 'DedicatedWorkerGlobalScope' && (globalThis as any).workerttPolicy !== undefined) { + ttPolicy = (globalThis as any).workerttPolicy; +} else { + ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value }); +} export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Worker { if (!blobUrl.startsWith('blob:')) { throw new URIError('Not a blob-url: ' + blobUrl); } - return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, options); + return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, { ...options, type: isESM ? 'module' : undefined }); } -function getWorker(label: string): Worker | Promise { +function getWorker(esmWorkerLocation: URI | undefined, label: string): Worker | Promise { // Option for hosts to overwrite the worker script (used in the standalone editor) interface IMonacoEnvironment { getWorker?(moduleId: string, label: string): Worker | Promise; @@ -31,48 +49,76 @@ function getWorker(label: string): Worker | Promise { } if (typeof monacoEnvironment.getWorkerUrl === 'function') { const workerUrl = monacoEnvironment.getWorkerUrl('workerMain.js', label); - return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label }); + return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: isESM ? 'module' : undefined }); } } // ESM-comment-begin if (typeof require === 'function') { - // check if the JS lives on a different origin - const workerMain = require.toUrl('vs/base/worker/workerMain.js'); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321 - const workerUrl = getWorkerBootstrapUrl(workerMain, label); - return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label }); + const workerMainLocation = require.toUrl('vs/base/worker/workerMain.js'); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321 + const factoryModuleId = 'vs/base/worker/defaultWorkerFactory.js'; + const workerBaseUrl = require.toUrl(factoryModuleId).slice(0, -factoryModuleId.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321 + const workerUrl = getWorkerBootstrapUrl(label, workerMainLocation, workerBaseUrl); + return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: isESM ? 'module' : undefined }); } // ESM-comment-end + if (esmWorkerLocation) { + const workerUrl = getWorkerBootstrapUrl(label, esmWorkerLocation.toString(true)); + const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: isESM ? 'module' : undefined }); + if (isESM) { + return whenESMWorkerReady(worker); + } else { + return worker; + } + } throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`); } -// ESM-comment-begin -export function getWorkerBootstrapUrl(scriptPath: string, label: string): string { - if (/^((http:)|(https:)|(file:))/.test(scriptPath) && scriptPath.substring(0, globalThis.origin.length) !== globalThis.origin) { +function getWorkerBootstrapUrl(label: string, workerScriptUrl: string, workerBaseUrl?: string): string { + if (/^((http:)|(https:)|(file:))/.test(workerScriptUrl) && workerScriptUrl.substring(0, globalThis.origin.length) !== globalThis.origin) { // this is the cross-origin case // i.e. the webpage is running at a different origin than where the scripts are loaded from - const myPath = 'vs/base/worker/defaultWorkerFactory.js'; - const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321 - const js = `/*${label}*/globalThis.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });importScripts(ttPolicy?.createScriptURL('${scriptPath}') ?? '${scriptPath}');/*${label}*/`; - const blob = new Blob([js], { type: 'application/javascript' }); - return URL.createObjectURL(blob); + } else { + const start = workerScriptUrl.lastIndexOf('?'); + const end = workerScriptUrl.lastIndexOf('#', start); + const params = start > 0 + ? new URLSearchParams(workerScriptUrl.substring(start + 1, ~end ? end : undefined)) + : new URLSearchParams(); + + COI.addSearchParam(params, true, true); + const search = params.toString(); + if (!search) { + workerScriptUrl = `${workerScriptUrl}#${label}`; + } else { + workerScriptUrl = `${workerScriptUrl}?${params.toString()}#${label}`; + } } - const start = scriptPath.lastIndexOf('?'); - const end = scriptPath.lastIndexOf('#', start); - const params = start > 0 - ? new URLSearchParams(scriptPath.substring(start + 1, ~end ? end : undefined)) - : new URLSearchParams(); - - COI.addSearchParam(params, true, true); - const search = params.toString(); + const blob = new Blob([coalesce([ + `/*${label}*/`, + workerBaseUrl ? `globalThis.MonacoEnvironment = { baseUrl: ${JSON.stringify(workerBaseUrl)} };` : undefined, + `globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(getNLSMessages())};`, + `globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(getNLSLanguage())};`, + `globalThis._VSCODE_FILE_ROOT = ${JSON.stringify(globalThis._VSCODE_FILE_ROOT)};`, + `const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });`, + `globalThis.workerttPolicy = ttPolicy;`, + isESM ? `await import(ttPolicy?.createScriptURL(${JSON.stringify(workerScriptUrl)}) ?? ${JSON.stringify(workerScriptUrl)});` : `importScripts(ttPolicy?.createScriptURL(${JSON.stringify(workerScriptUrl)}) ?? ${JSON.stringify(workerScriptUrl)});`, + isESM ? `globalThis.postMessage({ type: 'vscode-worker-ready' });` : undefined, // in ESM signal we are ready after the async import + `/*${label}*/` + ]).join('')], { type: 'application/javascript' }); + return URL.createObjectURL(blob); +} - if (!search) { - return `${scriptPath}#${label}`; - } else { - return `${scriptPath}?${params.toString()}#${label}`; - } +function whenESMWorkerReady(worker: Worker): Promise { + return new Promise((resolve, reject) => { + worker.onmessage = function (e) { + if (e.data.type === 'vscode-worker-ready') { + worker.onmessage = null; + resolve(worker); + } + }; + worker.onerror = reject; + }); } -// ESM-comment-end function isPromiseLike(obj: any): obj is PromiseLike { if (typeof obj.then === 'function') { @@ -91,17 +137,17 @@ class WebWorker extends Disposable implements IWorker { private readonly label: string; private worker: Promise | null; - constructor(moduleId: string, id: number, label: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void) { + constructor(esmWorkerLocation: URI | undefined, amdModuleId: string, id: number, label: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void) { super(); this.id = id; this.label = label; - const workerOrPromise = getWorker(label); + const workerOrPromise = getWorker(esmWorkerLocation, label); if (isPromiseLike(workerOrPromise)) { this.worker = workerOrPromise; } else { this.worker = Promise.resolve(workerOrPromise); } - this.postMessage(moduleId, []); + this.postMessage(amdModuleId, []); this.worker.then((w) => { w.onmessage = function (ev) { onMessageCallback(ev.data); @@ -136,33 +182,47 @@ class WebWorker extends Disposable implements IWorker { } }); } +} +export class WorkerDescriptor implements IWorkerDescriptor { + public readonly esmModuleLocation: URI | undefined; + + constructor( + public readonly amdModuleId: string, + readonly label: string | undefined, + ) { + this.esmModuleLocation = (isESM ? FileAccess.asBrowserUri(`${amdModuleId}.esm.js` as AppResourcePath) : undefined); + } } -export class DefaultWorkerFactory implements IWorkerFactory { +class DefaultWorkerFactory implements IWorkerFactory { private static LAST_WORKER_ID = 0; - - private _label: string | undefined; private _webWorkerFailedBeforeError: any; - constructor(label: string | undefined) { - this._label = label; + constructor() { this._webWorkerFailedBeforeError = false; } - public create(moduleId: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker { + public create(desc: IWorkerDescriptor, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker { const workerId = (++DefaultWorkerFactory.LAST_WORKER_ID); if (this._webWorkerFailedBeforeError) { throw this._webWorkerFailedBeforeError; } - return new WebWorker(moduleId, workerId, this._label || 'anonymous' + workerId, onMessageCallback, (err) => { + return new WebWorker(desc.esmModuleLocation, desc.amdModuleId, workerId, desc.label || 'anonymous' + workerId, onMessageCallback, (err) => { logOnceWebWorkerWarning(err); this._webWorkerFailedBeforeError = err; onErrorCallback(err); }); } } + +export function createWebWorker(amdModuleId: string, label: string | undefined): IWorkerClient; +export function createWebWorker(workerDescriptor: IWorkerDescriptor): IWorkerClient; +export function createWebWorker(arg0: string | IWorkerDescriptor, arg1?: string | undefined): IWorkerClient { + const workerDescriptor = (typeof arg0 === 'string' ? new WorkerDescriptor(arg0, arg1) : arg0); + return new SimpleWorkerClient(new DefaultWorkerFactory(), workerDescriptor); +} diff --git a/patched-vscode/src/vs/base/browser/dnd.ts b/patched-vscode/src/vs/base/browser/dnd.ts index e55b238b..96259a4e 100644 --- a/patched-vscode/src/vs/base/browser/dnd.ts +++ b/patched-vscode/src/vs/base/browser/dnd.ts @@ -100,7 +100,7 @@ export function applyDragImage(event: DragEvent, label: string | null, clazz: st event.dataTransfer.setDragImage(dragImage, -10, -10); // Removes the element when the DND operation is done - setTimeout(() => ownerDocument.body.removeChild(dragImage), 0); + setTimeout(() => dragImage.remove(), 0); } } diff --git a/patched-vscode/src/vs/base/browser/dom.ts b/patched-vscode/src/vs/base/browser/dom.ts index 3614986c..5df7c874 100644 --- a/patched-vscode/src/vs/base/browser/dom.ts +++ b/patched-vscode/src/vs/base/browser/dom.ts @@ -18,6 +18,7 @@ import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { hash } from 'vs/base/common/hash'; import { CodeWindow, ensureCodeWindow, mainWindow } from 'vs/base/browser/window'; +import { isPointWithinTriangle } from 'vs/base/common/numbers'; export interface IRegisteredCodeWindow { readonly window: CodeWindow; @@ -967,7 +968,7 @@ export function createStyleSheet(container: HTMLElement = mainWindow.document.he container.appendChild(style); if (disposableStore) { - disposableStore.add(toDisposable(() => container.removeChild(style))); + disposableStore.add(toDisposable(() => style.remove())); } // With as container, the stylesheet becomes global and is tracked @@ -1004,7 +1005,7 @@ function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesh const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement; targetWindow.document.head.appendChild(clone); - disposables.add(toDisposable(() => targetWindow.document.head.removeChild(clone))); + disposables.add(toDisposable(() => clone.remove())); for (const rule of getDynamicStyleSheetRules(globalStylesheet)) { clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length); @@ -1183,6 +1184,11 @@ export function isHTMLDivElement(e: unknown): e is HTMLDivElement { return e instanceof HTMLDivElement || e instanceof getWindow(e as Node).HTMLDivElement; } +export function isSVGElement(e: unknown): e is SVGElement { + // eslint-disable-next-line no-restricted-syntax + return e instanceof SVGElement || e instanceof getWindow(e as Node).SVGElement; +} + export function isMouseEvent(e: unknown): e is MouseEvent { // eslint-disable-next-line no-restricted-syntax return e instanceof MouseEvent || e instanceof getWindow(e as UIEvent).MouseEvent; @@ -1726,7 +1732,7 @@ export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void anchor.click(); // Ensure to remove the element from DOM eventually - setTimeout(() => activeWindow.document.body.removeChild(anchor)); + setTimeout(() => anchor.remove()); } export function triggerUpload(): Promise { @@ -1749,7 +1755,7 @@ export function triggerUpload(): Promise { input.click(); // Ensure to remove the element from DOM eventually - setTimeout(() => activeWindow.document.body.removeChild(input)); + setTimeout(() => input.remove()); }); } @@ -2372,6 +2378,107 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia return result; } +export function svgElem + (tag: TTag): + TagToRecord extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function svgElem + (tag: TTag, children: [...T]): + (ArrayToObj & TagToRecord) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function svgElem + (tag: TTag, attributes: Partial>>): + TagToRecord extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function svgElem + (tag: TTag, attributes: Partial>>, children: [...T]): + (ArrayToObj & TagToRecord) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function svgElem(tag: string, ...args: [] | [attributes: { $: string } & Partial> | Record, children?: any[]] | [children: any[]]): Record { + let attributes: { $?: string } & Partial>; + let children: (Record | HTMLElement)[] | undefined; + + if (Array.isArray(args[0])) { + attributes = {}; + children = args[0]; + } else { + attributes = args[0] as any || {}; + children = args[1]; + } + + const match = H_REGEX.exec(tag); + + if (!match || !match.groups) { + throw new Error('Bad use of h'); + } + + const tagName = match.groups['tag'] || 'div'; + const el = document.createElementNS('http://www.w3.org/2000/svg', tagName) as any as HTMLElement; + + if (match.groups['id']) { + el.id = match.groups['id']; + } + + const classNames = []; + if (match.groups['class']) { + for (const className of match.groups['class'].split('.')) { + if (className !== '') { + classNames.push(className); + } + } + } + if (attributes.className !== undefined) { + for (const className of attributes.className.split('.')) { + if (className !== '') { + classNames.push(className); + } + } + } + if (classNames.length > 0) { + el.className = classNames.join(' '); + } + + const result: Record = {}; + + if (match.groups['name']) { + result[match.groups['name']] = el; + } + + if (children) { + for (const c of children) { + if (isHTMLElement(c)) { + el.appendChild(c); + } else if (typeof c === 'string') { + el.append(c); + } else if ('root' in c) { + Object.assign(result, c); + el.appendChild(c.root); + } + } + } + + for (const [key, value] of Object.entries(attributes)) { + if (key === 'className') { + continue; + } else if (key === 'style') { + for (const [cssKey, cssValue] of Object.entries(value)) { + el.style.setProperty( + camelCaseToHyphenCase(cssKey), + typeof cssValue === 'number' ? cssValue + 'px' : '' + cssValue + ); + } + } else if (key === 'tabIndex') { + el.tabIndex = value; + } else { + el.setAttribute(camelCaseToHyphenCase(key), value.toString()); + } + } + + result['root'] = el; + + return result; +} + function camelCaseToHyphenCase(str: string) { return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); } @@ -2408,3 +2515,47 @@ export function trackAttributes(from: Element, to: Element, filter?: string[]): return disposables; } + +/** + * Helper for calculating the "safe triangle" occluded by hovers to avoid early dismissal. + * @see https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/ for example + */ +export class SafeTriangle { + // 4 points (x, y), 8 length + private points = new Int16Array(8); + + constructor( + private readonly originX: number, + private readonly originY: number, + target: HTMLElement + ) { + const { top, left, right, bottom } = target.getBoundingClientRect(); + const t = this.points; + let i = 0; + + t[i++] = left; + t[i++] = top; + + t[i++] = right; + t[i++] = top; + + t[i++] = left; + t[i++] = bottom; + + t[i++] = right; + t[i++] = bottom; + } + + public contains(x: number, y: number) { + const { points, originX, originY } = this; + for (let i = 0; i < 4; i++) { + const p1 = 2 * i; + const p2 = 2 * ((i + 1) % 4); + if (isPointWithinTriangle(x, y, originX, originY, points[p1], points[p1 + 1], points[p2], points[p2 + 1])) { + return true; + } + } + + return false; + } +} diff --git a/patched-vscode/src/vs/base/browser/domObservable.ts b/patched-vscode/src/vs/base/browser/domObservable.ts new file mode 100644 index 00000000..dd206377 --- /dev/null +++ b/patched-vscode/src/vs/base/browser/domObservable.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createStyleSheet2 } from 'vs/base/browser/dom'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { autorun, IObservable } from 'vs/base/common/observable'; + +export function createStyleSheetFromObservable(css: IObservable): IDisposable { + const store = new DisposableStore(); + const w = store.add(createStyleSheet2()); + store.add(autorun(reader => { + w.setStyle(css.read(reader)); + })); + return store; +} diff --git a/patched-vscode/src/vs/base/browser/markdownRenderer.ts b/patched-vscode/src/vs/base/browser/markdownRenderer.ts index b1a30484..73de1a35 100644 --- a/patched-vscode/src/vs/base/browser/markdownRenderer.ts +++ b/patched-vscode/src/vs/base/browser/markdownRenderer.ts @@ -18,7 +18,7 @@ import { defaultGenerator } from 'vs/base/common/idGenerator'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { marked } from 'vs/base/common/marked/marked'; +import * as marked from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { cloneAndChange } from 'vs/base/common/objects'; @@ -45,7 +45,7 @@ export interface ISanitizerOptions { } const defaultMarkedRenderers = Object.freeze({ - image: (href: string | null, title: string | null, text: string): string => { + image: ({ href, title, text }: marked.Tokens.Image): string => { let dimensions: string[] = []; let attributes: string[] = []; if (href) { @@ -64,11 +64,12 @@ const defaultMarkedRenderers = Object.freeze({ return ''; }, - paragraph: (text: string): string => { - return `

${text}

`; + paragraph(this: marked.Renderer, { tokens }: marked.Tokens.Paragraph): string { + return `

${this.parser.parseInline(tokens)}

`; }, - link: (href: string | null, title: string | null, text: string): string => { + link(this: marked.Renderer, { href, title, tokens }: marked.Tokens.Link): string { + let text = this.parser.parseInline(tokens); if (typeof href !== 'string') { return ''; } @@ -162,18 +163,18 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const syncCodeBlocks: [string, HTMLElement][] = []; if (options.codeBlockRendererSync) { - renderer.code = (code, lang) => { + renderer.code = ({ text, lang }: marked.Tokens.Code) => { const id = defaultGenerator.nextId(); - const value = options.codeBlockRendererSync!(postProcessCodeBlockLanguageId(lang), code); + const value = options.codeBlockRendererSync!(postProcessCodeBlockLanguageId(lang), text); syncCodeBlocks.push([id, value]); - return `
${escape(code)}
`; + return `
${escape(text)}
`; }; } else if (options.codeBlockRenderer) { - renderer.code = (code, lang) => { + renderer.code = ({ text, lang }: marked.Tokens.Code) => { const id = defaultGenerator.nextId(); - const value = options.codeBlockRenderer!(postProcessCodeBlockLanguageId(lang), code); + const value = options.codeBlockRenderer!(postProcessCodeBlockLanguageId(lang), text); codeBlocks.push(value.then(element => [id, element])); - return `
${escape(code)}
`; + return `
${escape(text)}
`; }; } @@ -219,23 +220,16 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } if (!markdown.supportHtml) { - // TODO: Can we deprecated this in favor of 'supportHtml'? - - // Use our own sanitizer so that we can let through only spans. - // Otherwise, we'd be letting all html be rendered. - // If we want to allow markdown permitted tags, then we can delete sanitizer and sanitize. - // We always pass the output through dompurify after this so that we don't rely on - // marked for sanitization. - markedOptions.sanitizer = (html: string): string => { + // Note: we always pass the output through dompurify after this so that we don't rely on + // marked for real sanitization. + renderer.html = ({ text }) => { if (options.sanitizerOptions?.replaceWithPlaintext) { - return escape(html); + return escape(text); } - const match = markdown.isTrusted ? html.match(/^(]+>)|(<\/\s*span>)$/) : undefined; - return match ? html : ''; + const match = markdown.isTrusted ? text.match(/^(]+>)|(<\/\s*span>)$/) : undefined; + return match ? text : ''; }; - markedOptions.sanitize = true; - markedOptions.silent = true; } markedOptions.renderer = renderer; @@ -261,7 +255,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const newTokens = fillInIncompleteTokens(tokens); renderedMarkdown = marked.parser(newTokens, opts); } else { - renderedMarkdown = marked.parse(value, markedOptions); + renderedMarkdown = marked.parse(value, { ...markedOptions, async: false }); } // Rewrite theme icons @@ -404,7 +398,7 @@ function sanitizeRenderedMarkdown( if (e.attrName === 'style' || e.attrName === 'class') { if (element.tagName === 'SPAN') { if (e.attrName === 'style') { - e.keepAttr = /^(color\:(#[0-9a-fA-F]+|var\(--vscode(-[a-zA-Z]+)+\));)?(background-color\:(#[0-9a-fA-F]+|var\(--vscode(-[a-zA-Z]+)+\));)?$/.test(e.attrValue); + e.keepAttr = /^(color\:(#[0-9a-fA-F]+|var\(--vscode(-[a-zA-Z]+)+\));)?(background-color\:(#[0-9a-fA-F]+|var\(--vscode(-[a-zA-Z]+)+\));)?(border-radius:[0-9]+px;)?$/.test(e.attrValue); return; } else if (e.attrName === 'class') { e.keepAttr = /^codicon codicon-[a-z\-]+( codicon-modifier-[a-z\-]+)?$/.test(e.attrValue); @@ -427,7 +421,7 @@ function sanitizeRenderedMarkdown( if (element.attributes.getNamedItem('type')?.value === 'checkbox') { element.setAttribute('disabled', ''); } else if (!options.replaceWithPlaintext) { - element.parentElement?.removeChild(element); + element.remove(); } } @@ -482,6 +476,7 @@ export const allowedMarkdownAttr = [ 'alt', 'checked', 'class', + 'colspan', 'controls', 'data-code', 'data-href', @@ -493,6 +488,7 @@ export const allowedMarkdownAttr = [ 'muted', 'playsinline', 'poster', + 'rowspan', 'src', 'style', 'target', @@ -551,7 +547,7 @@ export function renderMarkdownAsPlaintext(markdown: IMarkdownString, withCodeBlo value = `${value.substr(0, 100_000)}…`; } - const html = marked.parse(value, { renderer: withCodeBlocks ? plainTextWithCodeBlocksRenderer.value : plainTextRenderer.value }).replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m); + const html = marked.parse(value, { async: false, renderer: withCodeBlocks ? plainTextWithCodeBlocksRenderer.value : plainTextRenderer.value }).replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m); return sanitizeRenderedMarkdown({ isTrusted: false }, html).toString(); } @@ -568,64 +564,61 @@ const unescapeInfo = new Map([ function createRenderer(): marked.Renderer { const renderer = new marked.Renderer(); - renderer.code = (code: string): string => { - return code; + renderer.code = ({ text }: marked.Tokens.Code): string => { + return text; }; - renderer.blockquote = (quote: string): string => { - return quote; + renderer.blockquote = ({ text }: marked.Tokens.Blockquote): string => { + return text + '\n'; }; - renderer.html = (_html: string): string => { + renderer.html = (_: marked.Tokens.HTML): string => { return ''; }; - renderer.heading = (text: string, _level: 1 | 2 | 3 | 4 | 5 | 6, _raw: string): string => { - return text + '\n'; + renderer.heading = function ({ tokens }: marked.Tokens.Heading): string { + return this.parser.parseInline(tokens) + '\n'; }; renderer.hr = (): string => { return ''; }; - renderer.list = (body: string, _ordered: boolean): string => { - return body; + renderer.list = function ({ items }: marked.Tokens.List): string { + return items.map(x => this.listitem(x)).join('\n') + '\n'; }; - renderer.listitem = (text: string): string => { + renderer.listitem = ({ text }: marked.Tokens.ListItem): string => { return text + '\n'; }; - renderer.paragraph = (text: string): string => { - return text + '\n'; + renderer.paragraph = function ({ tokens }: marked.Tokens.Paragraph): string { + return this.parser.parseInline(tokens) + '\n'; }; - renderer.table = (header: string, body: string): string => { - return header + body + '\n'; + renderer.table = function ({ header, rows }: marked.Tokens.Table): string { + return header.map(cell => this.tablecell(cell)).join(' ') + '\n' + rows.map(cells => cells.map(cell => this.tablecell(cell)).join(' ')).join('\n') + '\n'; }; - renderer.tablerow = (content: string): string => { - return content; + renderer.tablerow = ({ text }: marked.Tokens.TableRow): string => { + return text; }; - renderer.tablecell = (content: string, _flags: { - header: boolean; - align: 'center' | 'left' | 'right' | null; - }): string => { - return content + ' '; + renderer.tablecell = function ({ tokens }: marked.Tokens.TableCell): string { + return this.parser.parseInline(tokens); }; - renderer.strong = (text: string): string => { + renderer.strong = ({ text }: marked.Tokens.Strong): string => { return text; }; - renderer.em = (text: string): string => { + renderer.em = ({ text }: marked.Tokens.Em): string => { return text; }; - renderer.codespan = (code: string): string => { - return code; + renderer.codespan = ({ text }: marked.Tokens.Codespan): string => { + return text; }; - renderer.br = (): string => { + renderer.br = (_: marked.Tokens.Br): string => { return '\n'; }; - renderer.del = (text: string): string => { + renderer.del = ({ text }: marked.Tokens.Del): string => { return text; }; - renderer.image = (_href: string, _title: string, _text: string): string => { + renderer.image = (_: marked.Tokens.Image): string => { return ''; }; - renderer.text = (text: string): string => { + renderer.text = ({ text }: marked.Tokens.Text): string => { return text; }; - renderer.link = (_href: string, _title: string, text: string): string => { + renderer.link = ({ text }: marked.Tokens.Link): string => { return text; }; return renderer; @@ -633,8 +626,8 @@ function createRenderer(): marked.Renderer { const plainTextRenderer = new Lazy((withCodeBlocks?: boolean) => createRenderer()); const plainTextWithCodeBlocksRenderer = new Lazy(() => { const renderer = createRenderer(); - renderer.code = (code: string): string => { - return '\n' + '```' + code + '```' + '\n'; + renderer.code = ({ text }: marked.Tokens.Code): string => { + return `\n\`\`\`\n${text}\n\`\`\`\n`; }; return renderer; }); @@ -766,8 +759,8 @@ function completeListItemPattern(list: marked.Tokens.List): marked.Tokens.List | const previousListItemsText = mergeRawTokenText(list.items.slice(0, -1)); - // Grabbing the `- ` or `1. ` off the list item because I can't find a better way to do this - const lastListItemLead = lastListItem.raw.match(/^(\s*(-|\d+\.) +)/)?.[0]; + // Grabbing the `- ` or `1. ` or `* ` off the list item because I can't find a better way to do this + const lastListItemLead = lastListItem.raw.match(/^(\s*(-|\d+\.|\*) +)/)?.[0]; if (!lastListItemLead) { // Is badly formatted return; @@ -805,13 +798,6 @@ function fillInIncompleteTokensOnce(tokens: marked.TokensList): marked.TokensLis let newTokens: marked.Token[] | undefined; for (i = 0; i < tokens.length; i++) { const token = tokens[i]; - let codeblockStart: RegExpMatchArray | null; - if (token.type === 'paragraph' && (codeblockStart = token.raw.match(/(\n|^)(````*)/))) { - const codeblockLead = codeblockStart[2]; - // If the code block was complete, it would be in a type='code' - newTokens = completeCodeBlock(tokens.slice(i), codeblockLead); - break; - } if (token.type === 'paragraph' && token.raw.match(/(\n|^)\|/)) { newTokens = completeTable(tokens.slice(i)); @@ -819,7 +805,7 @@ function fillInIncompleteTokensOnce(tokens: marked.TokensList): marked.TokensLis } if (i === tokens.length - 1 && token.type === 'list') { - const newListToken = completeListItemPattern(token); + const newListToken = completeListItemPattern(token as marked.Tokens.List); if (newListToken) { newTokens = [newListToken]; break; @@ -828,7 +814,7 @@ function fillInIncompleteTokensOnce(tokens: marked.TokensList): marked.TokensLis if (i === tokens.length - 1 && token.type === 'paragraph') { // Only operates on a single token, because any newline that follows this should break these patterns - const newToken = completeSingleLinePattern(token); + const newToken = completeSingleLinePattern(token as marked.Tokens.Paragraph); if (newToken) { newTokens = [newToken]; break; @@ -848,10 +834,6 @@ function fillInIncompleteTokensOnce(tokens: marked.TokensList): marked.TokensLis return null; } -function completeCodeBlock(tokens: marked.Token[], leader: string): marked.Token[] { - const mergedRawText = mergeRawTokenText(tokens); - return marked.lexer(mergedRawText + `\n${leader}`); -} function completeCodespan(token: marked.Token): marked.Token { return completeWithString(token, '`'); diff --git a/patched-vscode/src/vs/base/browser/trustedTypes.ts b/patched-vscode/src/vs/base/browser/trustedTypes.ts index 48c02ca8..0ef4b084 100644 --- a/patched-vscode/src/vs/base/browser/trustedTypes.ts +++ b/patched-vscode/src/vs/base/browser/trustedTypes.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { mainWindow } from 'vs/base/browser/window'; import { onUnexpectedError } from 'vs/base/common/errors'; export function createTrustedTypesPolicy( @@ -28,7 +27,7 @@ export function createTrustedTypesPolicy= 0 && index < this.viewItems.length) { - this.actionsList.removeChild(this.actionsList.childNodes[index]); + this.actionsList.childNodes[index].remove(); this.viewItemDisposables.deleteAndDispose(this.viewItems[index]); dispose(this.viewItems.splice(index, 1)); this.refreshRole(); @@ -485,7 +486,7 @@ export class ActionBar extends Disposable implements IActionRunner { return this.focusPrevious(true); } - protected focusNext(forceLoop?: boolean): boolean { + protected focusNext(forceLoop?: boolean, forceFocus?: boolean): boolean { if (typeof this.focusedItem === 'undefined') { this.focusedItem = this.viewItems.length - 1; } else if (this.viewItems.length <= 1) { @@ -505,7 +506,7 @@ export class ActionBar extends Disposable implements IActionRunner { item = this.viewItems[this.focusedItem]; } while (this.focusedItem !== startIndex && ((this.options.focusOnlyEnabledItems && !item.isEnabled()) || item.action.id === Separator.ID)); - this.updateFocus(); + this.updateFocus(undefined, undefined, forceFocus); return true; } diff --git a/patched-vscode/src/vs/base/browser/ui/button/button.ts b/patched-vscode/src/vs/base/browser/ui/button/button.ts index 3c42632c..4095afa6 100644 --- a/patched-vscode/src/vs/base/browser/ui/button/button.ts +++ b/patched-vscode/src/vs/base/browser/ui/button/button.ts @@ -22,7 +22,7 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc import { ThemeIcon } from 'vs/base/common/themables'; import 'vs/css!./button'; import { localize } from 'vs/nls'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { IActionProvider } from 'vs/base/browser/ui/dropdown/dropdown'; @@ -64,6 +64,7 @@ export interface IButton extends IDisposable { set label(value: string | IMarkdownString); set icon(value: ThemeIcon); set enabled(value: boolean); + set checked(value: boolean); focus(): void; hasFocus(): boolean; @@ -80,7 +81,7 @@ export class Button extends Disposable implements IButton { protected _label: string | IMarkdownString = ''; protected _labelElement: HTMLElement | undefined; protected _labelShortElement: HTMLElement | undefined; - private _hover: IUpdatableHover | undefined; + private _hover: IManagedHover | undefined; private _onDidClick = this._register(new Emitter()); get onDidClick(): BaseEvent { return this._onDidClick.event; } @@ -304,9 +305,23 @@ export class Button extends Disposable implements IButton { return !this._element.classList.contains('disabled'); } + set checked(value: boolean) { + if (value) { + this._element.classList.add('checked'); + this._element.setAttribute('aria-checked', 'true'); + } else { + this._element.classList.remove('checked'); + this._element.setAttribute('aria-checked', 'false'); + } + } + + get checked() { + return this._element.classList.contains('checked'); + } + setTitle(title: string) { if (!this._hover && title !== '') { - this._hover = this._register(getBaseLayerHoverDelegate().setupUpdatableHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this._element, title)); + this._hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this._element, title)); } else if (this._hover) { this._hover.update(title); } @@ -370,7 +385,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.separator.style.backgroundColor = options.buttonSeparator ?? ''; this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); - this._register(getBaseLayerHoverDelegate().setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.dropdownButton.element, localize("button dropdown more actions", 'More Actions...'))); + this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.dropdownButton.element, localize("button dropdown more actions", 'More Actions...'))); this.dropdownButton.element.setAttribute('aria-haspopup', 'true'); this.dropdownButton.element.setAttribute('aria-expanded', 'false'); this.dropdownButton.element.classList.add('monaco-dropdown-button'); @@ -412,6 +427,14 @@ export class ButtonWithDropdown extends Disposable implements IButton { return this.button.enabled; } + set checked(value: boolean) { + this.button.checked = value; + } + + get checked() { + return this.button.checked; + } + focus(): void { this.button.focus(); } @@ -462,6 +485,14 @@ export class ButtonWithDescription implements IButtonWithDescription { this._button.enabled = enabled; } + set checked(value: boolean) { + this._button.checked = value; + } + + get checked(): boolean { + return this._button.checked; + } + focus(): void { this._button.focus(); } diff --git a/patched-vscode/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/patched-vscode/src/vs/base/browser/ui/centered/centeredViewLayout.ts index db139072..b6bc80b1 100644 --- a/patched-vscode/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ b/patched-vscode/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -166,7 +166,7 @@ export class CenteredViewLayout implements IDisposable { } if (active) { - this.container.removeChild(this.view.element); + this.view.element.remove(); this.splitView = new SplitView(this.container, { inverseAltBehavior: true, orientation: Orientation.HORIZONTAL, @@ -195,9 +195,7 @@ export class CenteredViewLayout implements IDisposable { this.resizeSplitViews(); } else { - if (this.splitView) { - this.container.removeChild(this.splitView.el); - } + this.splitView?.el.remove(); this.splitViewDisposables.clear(); this.splitView?.dispose(); this.splitView = undefined; diff --git a/patched-vscode/src/vs/base/browser/ui/contextview/contextview.ts b/patched-vscode/src/vs/base/browser/ui/contextview/contextview.ts index 4f07df5b..1edda5b7 100644 --- a/patched-vscode/src/vs/base/browser/ui/contextview/contextview.ts +++ b/patched-vscode/src/vs/base/browser/ui/contextview/contextview.ts @@ -169,13 +169,11 @@ export class ContextView extends Disposable { if (this.container) { this.toDisposeOnSetContainer.dispose(); + this.view.remove(); if (this.shadowRoot) { - this.shadowRoot.removeChild(this.view); this.shadowRoot = null; this.shadowRootHostElement?.remove(); this.shadowRootHostElement = null; - } else { - this.container.removeChild(this.view); } this.container = null; diff --git a/patched-vscode/src/vs/base/browser/ui/dialog/dialog.css b/patched-vscode/src/vs/base/browser/ui/dialog/dialog.css index 9b7681e1..60dba786 100644 --- a/patched-vscode/src/vs/base/browser/ui/dialog/dialog.css +++ b/patched-vscode/src/vs/base/browser/ui/dialog/dialog.css @@ -30,6 +30,7 @@ min-height: 75px; padding: 10px; transform: translate3d(0px, 0px, 0px); + border-radius: 3px; } /** Dialog: Title Actions Row */ diff --git a/patched-vscode/src/vs/base/browser/ui/dialog/dialog.ts b/patched-vscode/src/vs/base/browser/ui/dialog/dialog.ts index 3842592d..9b5c3073 100644 --- a/patched-vscode/src/vs/base/browser/ui/dialog/dialog.ts +++ b/patched-vscode/src/vs/base/browser/ui/dialog/dialog.ts @@ -209,7 +209,7 @@ export class Dialog extends Disposable { // Handle button clicks buttonMap.forEach((entry, index) => { const primary = buttonMap[index].index === 0; - const button = this.options.buttonDetails ? this._register(buttonBar.addButtonWithDescription({ title: true, secondary: !primary, ...this.buttonStyles })) : this._register(buttonBar.addButton({ title: true, secondary: !primary, ...this.buttonStyles })); + const button = this.options.buttonDetails ? this._register(buttonBar.addButtonWithDescription({ secondary: !primary, ...this.buttonStyles })) : this._register(buttonBar.addButton({ secondary: !primary, ...this.buttonStyles })); button.label = mnemonicButtonLabel(buttonMap[index].label, true); if (button instanceof ButtonWithDescription) { button.description = this.options.buttonDetails![buttonMap[index].index]; diff --git a/patched-vscode/src/vs/base/browser/ui/dropdown/dropdown.ts b/patched-vscode/src/vs/base/browser/ui/dropdown/dropdown.ts index 1089d827..ba003576 100644 --- a/patched-vscode/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/patched-vscode/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -8,7 +8,7 @@ import { $, addDisposableListener, append, EventHelper, EventType, isMouseEvent import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; @@ -37,7 +37,7 @@ class BaseDropdown extends ActionRunner { private _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; - private hover: IUpdatableHover | undefined; + private hover: IManagedHover | undefined; constructor(container: HTMLElement, options: IBaseDropdownOptions) { super(); @@ -107,7 +107,7 @@ class BaseDropdown extends ActionRunner { set tooltip(tooltip: string) { if (this._label) { if (!this.hover && tooltip !== '') { - this.hover = this._register(getBaseLayerHoverDelegate().setupUpdatableHover(getDefaultHoverDelegate('mouse'), this._label, tooltip)); + this.hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this._label, tooltip)); } else if (this.hover) { this.hover.update(tooltip); } diff --git a/patched-vscode/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/patched-vscode/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 18cfd87d..007e1de2 100644 --- a/patched-vscode/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/patched-vscode/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -93,7 +93,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.element.setAttribute('aria-haspopup', 'true'); this.element.setAttribute('aria-expanded', 'false'); if (this._action.label) { - this._register(getBaseLayerHoverDelegate().setupUpdatableHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.element, this._action.label)); + this._register(getBaseLayerHoverDelegate().setupManagedHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.element, this._action.label)); } this.element.ariaLabel = this._action.label || ''; diff --git a/patched-vscode/src/vs/base/browser/ui/grid/gridview.ts b/patched-vscode/src/vs/base/browser/ui/grid/gridview.ts index 1d7eacf9..17b24550 100644 --- a/patched-vscode/src/vs/base/browser/ui/grid/gridview.ts +++ b/patched-vscode/src/vs/base/browser/ui/grid/gridview.ts @@ -1063,7 +1063,7 @@ export class GridView implements IDisposable { const oldRoot = this._root; if (oldRoot) { - this.element.removeChild(oldRoot.element); + oldRoot.element.remove(); oldRoot.dispose(); } @@ -1831,6 +1831,6 @@ export class GridView implements IDisposable { dispose(): void { this.onDidSashResetRelay.dispose(); this.root.dispose(); - this.element.parentElement?.removeChild(this.element); + this.element.remove(); } } diff --git a/patched-vscode/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/patched-vscode/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 724075ad..83b0c26b 100644 --- a/patched-vscode/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/patched-vscode/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; @@ -43,7 +43,7 @@ export class HighlightedLabel extends Disposable { private highlights: readonly IHighlight[] = []; private supportIcons: boolean; private didEverRender: boolean = false; - private customHover: IUpdatableHover | undefined; + private customHover: IManagedHover | undefined; /** * Create a new {@link HighlightedLabel}. @@ -141,7 +141,7 @@ export class HighlightedLabel extends Disposable { } else { if (!this.customHover && this.title !== '') { const hoverDelegate = this.options?.hoverDelegate ?? getDefaultHoverDelegate('mouse'); - this.customHover = this._register(getBaseLayerHoverDelegate().setupUpdatableHover(hoverDelegate, this.domNode, this.title)); + this.customHover = this._register(getBaseLayerHoverDelegate().setupManagedHover(hoverDelegate, this.domNode, this.title)); } else if (this.customHover) { this.customHover.update(this.title); } diff --git a/patched-vscode/src/vs/base/browser/ui/hover/hover.ts b/patched-vscode/src/vs/base/browser/ui/hover/hover.ts index f2b7582d..55dfdacf 100644 --- a/patched-vscode/src/vs/base/browser/ui/hover/hover.ts +++ b/patched-vscode/src/vs/base/browser/ui/hover/hover.ts @@ -14,11 +14,12 @@ import type { IDisposable } from 'vs/base/common/lifecycle'; */ export interface IHoverDelegate2 { /** - * Shows a hover, provided a hover with the same options object is not already visible. + * Shows a hover, provided a hover with the same {@link options} object is not already visible. + * * @param options A set of options defining the characteristics of the hover. * @param focus Whether to focus the hover (useful for keyboard accessibility). * - * **Example:** A simple usage with a single element target. + * @example A simple usage with a single element target. * * ```typescript * showHover({ @@ -27,7 +28,10 @@ export interface IHoverDelegate2 { * }); * ``` */ - showHover(options: IHoverOptions, focus?: boolean): IHoverWidget | undefined; + showHover( + options: IHoverOptions, + focus?: boolean + ): IHoverWidget | undefined; /** * Hides the hover if it was visible. This call will be ignored if the the hover is currently @@ -41,16 +45,37 @@ export interface IHoverDelegate2 { */ showAndFocusLastHover(): void; - // TODO: Change hoverDelegate arg to exclude the actual delegate and instead use the new options - setupUpdatableHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IUpdatableHoverContentOrFactory, options?: IUpdatableHoverOptions): IUpdatableHover; + /** + * Sets up a managed hover for the given element. A managed hover will set up listeners for + * mouse events, show the hover after a delay and provide hooks to easily update the content. + * + * This should be used over {@link showHover} when fine-grained control is not needed. The + * managed hover also does not scale well, consider using {@link showHover} when showing hovers + * for many elements. + * + * @param hoverDelegate The hover delegate containing hooks and configuration for the hover. + * @param targetElement The target element to show the hover for. + * @param content The content of the hover or a factory that creates it at the time it's shown. + * @param options Additional options for the managed hover. + */ + // TODO: The hoverDelegate parameter should be removed in favor of just a set of options. This + // will avoid confusion around IHoverDelegate/IHoverDelegate2 as well as align more with + // the design of the hover service. + // TODO: Align prototype closer to showHover, deriving options from IHoverOptions if possible. + setupManagedHover(hoverDelegate: IHoverDelegate, targetElement: HTMLElement, content: IManagedHoverContentOrFactory, options?: IManagedHoverOptions): IManagedHover; /** * Shows the hover for the given element if one has been setup. + * + * @param targetElement The target element of the hover, as set up in {@link setupManagedHover}. */ - triggerUpdatableHover(htmlElement: HTMLElement): void; + showManagedHover(targetElement: HTMLElement): void; } export interface IHoverWidget extends IDisposable { + /** + * Whether the hover widget has been disposed. + */ readonly isDisposed: boolean; } @@ -229,33 +254,30 @@ export interface IHoverTarget extends IDisposable { * An optional absolute x coordinate to position the hover with, for example to position the * hover using `MouseEvent.pageX`. */ - x?: number; + readonly x?: number; /** * An optional absolute y coordinate to position the hover with, for example to position the * hover using `MouseEvent.pageY`. */ - y?: number; + readonly y?: number; } -// #region Updatable hover +// #region Managed hover -export interface IUpdatableHoverTooltipMarkdownString { +export interface IManagedHoverTooltipMarkdownString { markdown: IMarkdownString | string | undefined | ((token: CancellationToken) => Promise); markdownNotSupportedFallback: string | undefined; } -export type IUpdatableHoverContent = string | IUpdatableHoverTooltipMarkdownString | HTMLElement | undefined; -export type IUpdatableHoverContentOrFactory = IUpdatableHoverContent | (() => IUpdatableHoverContent); +export type IManagedHoverContent = string | IManagedHoverTooltipMarkdownString | HTMLElement | undefined; +export type IManagedHoverContentOrFactory = IManagedHoverContent | (() => IManagedHoverContent); -export interface IUpdatableHoverOptions { - actions?: IHoverAction[]; - linkHandler?(url: string): void; - trapFocus?: boolean; +export interface IManagedHoverOptions extends Pick { + appearance?: Pick; } -export interface IUpdatableHover extends IDisposable { - +export interface IManagedHover extends IDisposable { /** * Allows to programmatically open the hover. */ @@ -269,7 +291,7 @@ export interface IUpdatableHover extends IDisposable { /** * Updates the contents of the hover. */ - update(tooltip: IUpdatableHoverContent, options?: IUpdatableHoverOptions): void; + update(tooltip: IManagedHoverContent, options?: IManagedHoverOptions): void; } -// #endregion Updatable hover +// #endregion Managed hover diff --git a/patched-vscode/src/vs/base/browser/ui/hover/hoverDelegate.ts b/patched-vscode/src/vs/base/browser/ui/hover/hoverDelegate.ts index d2f1d788..0db89381 100644 --- a/patched-vscode/src/vs/base/browser/ui/hover/hoverDelegate.ts +++ b/patched-vscode/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { IHoverWidget, IUpdatableHoverOptions } from 'vs/base/browser/ui/hover/hover'; +import type { IHoverWidget, IManagedHoverOptions } from 'vs/base/browser/ui/hover/hover'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -13,7 +13,7 @@ export interface IHoverDelegateTarget extends IDisposable { x?: number; } -export interface IHoverDelegateOptions extends IUpdatableHoverOptions { +export interface IHoverDelegateOptions extends IManagedHoverOptions { /** * The content to display in the primary section of the hover. The type of text determines the * default `hideOnHover` behavior. @@ -49,6 +49,13 @@ export interface IHoverDelegateOptions extends IUpdatableHoverOptions { * Whether to show the hover pointer */ showPointer?: boolean; + /** + * When {@link hideOnHover} is explicitly true or undefined and its auto value is detected to + * hide, show a hint at the bottom of the hover explaining how to mouse over the widget. This + * should be used in the cases where despite the hover having no interactive content, it's + * likely the user may want to interact with it somehow. + */ + showHoverHint?: boolean; /** * Whether to skip the fade in animation, this should be used when hovering from one hover to * another in the same group so it looks like the hover is moving from one element to the other. diff --git a/patched-vscode/src/vs/base/browser/ui/hover/hoverDelegate2.ts b/patched-vscode/src/vs/base/browser/ui/hover/hoverDelegate2.ts index 13a37922..1d6a312f 100644 --- a/patched-vscode/src/vs/base/browser/ui/hover/hoverDelegate2.ts +++ b/patched-vscode/src/vs/base/browser/ui/hover/hoverDelegate2.ts @@ -9,8 +9,8 @@ let baseHoverDelegate: IHoverDelegate2 = { showHover: () => undefined, hideHover: () => undefined, showAndFocusLastHover: () => undefined, - setupUpdatableHover: () => null!, - triggerUpdatableHover: () => undefined + setupManagedHover: () => null!, + showManagedHover: () => undefined }; /** diff --git a/patched-vscode/src/vs/base/browser/ui/hover/hoverWidget.css b/patched-vscode/src/vs/base/browser/ui/hover/hoverWidget.css index a1d72c2e..d6912265 100644 --- a/patched-vscode/src/vs/base/browser/ui/hover/hoverWidget.css +++ b/patched-vscode/src/vs/base/browser/ui/hover/hoverWidget.css @@ -134,6 +134,11 @@ padding-right: 4px; } +.monaco-hover .hover-row.status-bar .actions .action-container a { + color: var(--vscode-textLink-foreground); + text-decoration: var(--text-link-decoration); +} + .monaco-hover .markdown-hover .hover-contents .codicon { color: inherit; font-size: inherit; @@ -171,6 +176,10 @@ display: inline-block; } +.monaco-hover .markdown-hover .hover-contents:not(.code-hover-contents):not(.html-hover-contents) span.codicon { + margin-bottom: 2px; +} + .monaco-hover-content .action-container a { -webkit-user-select: none; user-select: none; diff --git a/patched-vscode/src/vs/base/browser/ui/hover/hoverWidget.ts b/patched-vscode/src/vs/base/browser/ui/hover/hoverWidget.ts index 2e9ecbdd..9d836121 100644 --- a/patched-vscode/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/patched-vscode/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -50,12 +50,18 @@ export class HoverAction extends Disposable { return new HoverAction(parent, actionOptions, keybindingLabel); } + public readonly actionLabel: string; + public readonly actionKeybindingLabel: string | null; + private readonly actionContainer: HTMLElement; private readonly action: HTMLElement; private constructor(parent: HTMLElement, actionOptions: { label: string; iconClass?: string; run: (target: HTMLElement) => void; commandId: string }, keybindingLabel: string | null) { super(); + this.actionLabel = actionOptions.label; + this.actionKeybindingLabel = keybindingLabel; + this.actionContainer = dom.append(parent, $('div.action-container')); this.actionContainer.setAttribute('tabindex', '0'); diff --git a/patched-vscode/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/patched-vscode/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 214af037..5439f567 100644 --- a/patched-vscode/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/patched-vscode/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -12,7 +12,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; import { Range } from 'vs/base/common/range'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; -import type { IUpdatableHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { isString } from 'vs/base/common/types'; import { stripIcons } from 'vs/base/common/iconLabels'; @@ -26,8 +26,8 @@ export interface IIconLabelCreationOptions { } export interface IIconLabelValueOptions { - title?: string | IUpdatableHoverTooltipMarkdownString; - descriptionTitle?: string | IUpdatableHoverTooltipMarkdownString; + title?: string | IManagedHoverTooltipMarkdownString; + descriptionTitle?: string | IManagedHoverTooltipMarkdownString; suffix?: string; hideIcon?: boolean; extraClasses?: readonly string[]; @@ -45,7 +45,7 @@ export interface IIconLabelValueOptions { class FastLabelNode { private disposed: boolean | undefined; private _textContent: string | undefined; - private _className: string | undefined; + private _classNames: string[] | undefined; private _empty: boolean | undefined; constructor(private _element: HTMLElement) { @@ -64,13 +64,14 @@ class FastLabelNode { this._element.textContent = content; } - set className(className: string) { - if (this.disposed || className === this._className) { + set classNames(classNames: string[]) { + if (this.disposed || equals(classNames, this._classNames)) { return; } - this._className = className; - this._element.className = className; + this._classNames = classNames; + this._element.classList.value = ''; + this._element.classList.add(...classNames); } set empty(empty: boolean) { @@ -169,9 +170,10 @@ export class IconLabel extends Disposable { existingIconNode.remove(); } - this.domNode.className = labelClasses.join(' '); + this.domNode.classNames = labelClasses; this.domNode.element.setAttribute('aria-label', ariaLabel); - this.labelContainer.className = containerClasses.join(' '); + this.labelContainer.classList.value = ''; + this.labelContainer.classList.add(...containerClasses); this.setupHover(options?.descriptionTitle ? this.labelContainer : this.element, options?.title); this.nameNode.setLabel(label, options); @@ -194,7 +196,7 @@ export class IconLabel extends Disposable { } } - private setupHover(htmlElement: HTMLElement, tooltip: string | IUpdatableHoverTooltipMarkdownString | undefined): void { + private setupHover(htmlElement: HTMLElement, tooltip: string | IManagedHoverTooltipMarkdownString | undefined): void { const previousCustomHover = this.customHovers.get(htmlElement); if (previousCustomHover) { previousCustomHover.dispose(); @@ -207,7 +209,7 @@ export class IconLabel extends Disposable { } if (this.hoverDelegate.showNativeHover) { - function setupNativeHover(htmlElement: HTMLElement, tooltip: string | IUpdatableHoverTooltipMarkdownString | undefined): void { + function setupNativeHover(htmlElement: HTMLElement, tooltip: string | IManagedHoverTooltipMarkdownString | undefined): void { if (isString(tooltip)) { // Icons don't render in the native hover so we strip them out htmlElement.title = stripIcons(tooltip); @@ -219,7 +221,7 @@ export class IconLabel extends Disposable { } setupNativeHover(htmlElement, tooltip); } else { - const hoverDisposable = getBaseLayerHoverDelegate().setupUpdatableHover(this.hoverDelegate, htmlElement, tooltip); + const hoverDisposable = getBaseLayerHoverDelegate().setupManagedHover(this.hoverDelegate, htmlElement, tooltip); if (hoverDisposable) { this.customHovers.set(htmlElement, hoverDisposable); } diff --git a/patched-vscode/src/vs/base/browser/ui/iconLabel/simpleIconLabel.ts b/patched-vscode/src/vs/base/browser/ui/iconLabel/simpleIconLabel.ts index 6f960b8a..ea8179ad 100644 --- a/patched-vscode/src/vs/base/browser/ui/iconLabel/simpleIconLabel.ts +++ b/patched-vscode/src/vs/base/browser/ui/iconLabel/simpleIconLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { reset } from 'vs/base/browser/dom'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; @@ -12,7 +12,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; export class SimpleIconLabel implements IDisposable { - private hover?: IUpdatableHover; + private hover?: IManagedHover; constructor( private readonly _container: HTMLElement @@ -24,7 +24,7 @@ export class SimpleIconLabel implements IDisposable { set title(title: string) { if (!this.hover && title) { - this.hover = getBaseLayerHoverDelegate().setupUpdatableHover(getDefaultHoverDelegate('mouse'), this._container, title); + this.hover = getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this._container, title); } else if (this.hover) { this.hover.update(title); } diff --git a/patched-vscode/src/vs/base/browser/ui/inputbox/inputBox.ts b/patched-vscode/src/vs/base/browser/ui/inputbox/inputBox.ts index e4215ad7..870afb65 100644 --- a/patched-vscode/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/patched-vscode/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -11,7 +11,7 @@ import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -114,7 +114,7 @@ export class InputBox extends Widget { private cachedContentHeight: number | undefined; private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; - private hover: IUpdatableHover | undefined; + private hover: IManagedHover | undefined; private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -235,7 +235,7 @@ export class InputBox extends Widget { public setTooltip(tooltip: string): void { this.tooltip = tooltip; if (!this.hover) { - this.hover = this._register(getBaseLayerHoverDelegate().setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.input, tooltip)); + this.hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.input, tooltip)); } else { this.hover.update(tooltip); } diff --git a/patched-vscode/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts b/patched-vscode/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts index b6c8e1e4..189317b4 100644 --- a/patched-vscode/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts +++ b/patched-vscode/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { UILabelProvider } from 'vs/base/common/keybindingLabels'; @@ -61,7 +61,7 @@ export class KeybindingLabel extends Disposable { private readonly keyElements = new Set(); - private hover: IUpdatableHover; + private hover: IManagedHover; private keybinding: ResolvedKeybinding | undefined; private matches: Matches | undefined; private didEverRender: boolean; @@ -78,7 +78,7 @@ export class KeybindingLabel extends Disposable { this.domNode.style.color = labelForeground; } - this.hover = this._register(getBaseLayerHoverDelegate().setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.domNode, '')); + this.hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.domNode, '')); this.didEverRender = false; container.appendChild(this.domNode); diff --git a/patched-vscode/src/vs/base/browser/ui/list/listView.ts b/patched-vscode/src/vs/base/browser/ui/list/listView.ts index 005d34d6..6d2af1db 100644 --- a/patched-vscode/src/vs/base/browser/ui/list/listView.ts +++ b/patched-vscode/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, getWindow, isAncestor, isHTMLElement, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, isAncestor, isHTMLElement, isSVGElement, 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'; @@ -519,7 +519,7 @@ export class ListView implements IListView { if (typeof size === 'undefined') { if (!this.supportDynamicHeights) { - console.warn('Dynamic heights not supported'); + console.warn('Dynamic heights not supported', new Error().stack); return; } @@ -1158,7 +1158,7 @@ export class ListView implements IListView { const container = getDragImageContainer(this.domNode); container.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => container.removeChild(dragImage), 0); + setTimeout(() => dragImage.remove(), 0); } this.domNode.classList.add('dragging'); @@ -1382,9 +1382,9 @@ export class ListView implements IListView { private getItemIndexFromEventTarget(target: EventTarget | null): number | undefined { const scrollableElement = this.scrollableElement.getDomNode(); - let element: HTMLElement | null = target as (HTMLElement | null); + let element: HTMLElement | SVGElement | null = target as (HTMLElement | SVGElement | null); - while (isHTMLElement(element) && element !== this.rowsContainer && scrollableElement.contains(element)) { + while ((isHTMLElement(element) || isSVGElement(element)) && element !== this.rowsContainer && scrollableElement.contains(element)) { const rawIndex = element.getAttribute('data-index'); if (rawIndex) { @@ -1519,7 +1519,7 @@ export class ListView implements IListView { item.row.domNode.style.height = ''; item.size = item.row.domNode.offsetHeight; if (item.size === 0 && !isAncestor(item.row.domNode, getWindow(item.row.domNode).document.body)) { - console.warn('Measuring item node that is not in DOM! Add ListView to the DOM before measuring row height!'); + console.warn('Measuring item node that is not in DOM! Add ListView to the DOM before measuring row height!', new Error().stack); } item.lastDynamicHeightWidth = this.renderWidth; return item.size - size; @@ -1542,7 +1542,7 @@ export class ListView implements IListView { this.virtualDelegate.setDynamicHeight?.(item.element, item.size); item.lastDynamicHeightWidth = this.renderWidth; - this.rowsContainer.removeChild(row.domNode); + row.domNode.remove(); this.cache.release(row); return item.size - size; @@ -1570,9 +1570,7 @@ export class ListView implements IListView { this.items = []; - if (this.domNode && this.domNode.parentNode) { - this.domNode.parentNode.removeChild(this.domNode); - } + this.domNode?.remove(); this.dragOverAnimationDisposable?.dispose(); this.disposables.dispose(); diff --git a/patched-vscode/src/vs/base/browser/ui/list/rowCache.ts b/patched-vscode/src/vs/base/browser/ui/list/rowCache.ts index f71bdfd0..ff605b09 100644 --- a/patched-vscode/src/vs/base/browser/ui/list/rowCache.ts +++ b/patched-vscode/src/vs/base/browser/ui/list/rowCache.ts @@ -13,14 +13,6 @@ export interface IRow { templateData: any; } -function removeFromParent(element: HTMLElement): void { - try { - element.parentElement?.removeChild(element); - } catch (e) { - // this will throw if this happens due to a blur event, nasty business - } -} - export class RowCache implements IDisposable { private cache = new Map(); @@ -104,7 +96,7 @@ export class RowCache implements IDisposable { private doRemoveNode(domNode: HTMLElement) { domNode.classList.remove('scrolling'); - removeFromParent(domNode); + domNode.remove(); } private getTemplateCache(templateId: string): IRow[] { diff --git a/patched-vscode/src/vs/base/browser/ui/radio/radio.css b/patched-vscode/src/vs/base/browser/ui/radio/radio.css new file mode 100644 index 00000000..259c1570 --- /dev/null +++ b/patched-vscode/src/vs/base/browser/ui/radio/radio.css @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-custom-radio { + display: flex; +} + +.monaco-custom-radio > .monaco-button { + border-radius: 0; + font-size: 0.9em; + line-height: 1em; + padding-left: 0.5em; + padding-right: 0.5em; +} + +.monaco-custom-radio > .monaco-button:first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.monaco-custom-radio > .monaco-button:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.monaco-custom-radio > .monaco-button:not(.active):not(:last-child) { + border-right: none; +} + +.monaco-custom-radio > .monaco-button.previous-active { + border-left: none; +} + +/* default color styles - based on CSS variables */ + +.monaco-custom-radio > .monaco-button { + color: var(--vscode-radio-inactiveForeground); + background-color: var(--vscode-radio-inactiveBackground); + border-color: var(--vscode-radio-inactiveBorder, transparent); +} + +.monaco-custom-radio > .monaco-button.active:hover, +.monaco-custom-radio > .monaco-button.active { + color: var(--vscode-radio-activeForeground); + background-color: var(--vscode-radio-activeBackground); + border-color: var(--vscode-radio-activeBorder, transparent); +} + +.hc-black .monaco-custom-radio > .monaco-button.active, +.hc-light .monaco-custom-radio > .monaco-button.active { + border-color: var(--vscode-radio-activeBorder, transparent); +} + +.hc-black .monaco-custom-radio > .monaco-button:not(.active), +.hc-light .monaco-custom-radio > .monaco-button:not(.active) { + border-color: var(--vscode-radio-inactiveBorder, transparent); +} + +.hc-black .monaco-custom-radio > .monaco-button:not(.active):hover, +.hc-light .monaco-custom-radio > .monaco-button:not(.active):hover { + outline: 1px dashed var(--vscode-toolbar-hoverOutline); + outline-offset: -1px +} + +.monaco-custom-radio > .monaco-button:hover:not(.active) { + background-color: var(--vscode-radio-inactiveHoverBackground); +} diff --git a/patched-vscode/src/vs/base/browser/ui/radio/radio.ts b/patched-vscode/src/vs/base/browser/ui/radio/radio.ts new file mode 100644 index 00000000..5ab1edc4 --- /dev/null +++ b/patched-vscode/src/vs/base/browser/ui/radio/radio.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Widget } from 'vs/base/browser/ui/widget'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { Emitter } from 'vs/base/common/event'; +import 'vs/css!./radio'; +import { $ } from 'vs/base/browser/dom'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; + +export interface IRadioStyles { + readonly activeForeground?: string; + readonly activeBackground?: string; + readonly activeBorder?: string; + readonly inactiveForeground?: string; + readonly inactiveBackground?: string; + readonly inactiveHoverBackground?: string; + readonly inactiveBorder?: string; +} + +export interface IRadioOptionItem { + readonly text: string; + readonly tooltip?: string; + readonly isActive?: boolean; + readonly disabled?: boolean; +} + +export interface IRadioOptions { + readonly items: ReadonlyArray; + readonly activeIcon?: ThemeIcon; + readonly hoverDelegate?: IHoverDelegate; +} + +export class Radio extends Widget { + + private readonly _onDidSelect = this._register(new Emitter()); + readonly onDidSelect = this._onDidSelect.event; + + readonly domNode: HTMLElement; + + private readonly hoverDelegate: IHoverDelegate; + + private items: ReadonlyArray = []; + private activeItem: IRadioOptionItem | undefined; + + private readonly buttons = this._register(new DisposableMap()); + + constructor(opts: IRadioOptions) { + super(); + + this.hoverDelegate = opts.hoverDelegate ?? this._register(createInstantHoverDelegate()); + + this.domNode = $('.monaco-custom-radio'); + this.domNode.setAttribute('role', 'radio'); + + this.setItems(opts.items); + } + + setItems(items: ReadonlyArray): void { + this.buttons.clearAndDisposeAll(); + this.items = items; + this.activeItem = this.items.find(item => item.isActive) ?? this.items[0]; + for (let index = 0; index < this.items.length; index++) { + const item = this.items[index]; + const disposables = new DisposableStore(); + const button = disposables.add(new Button(this.domNode, { + hoverDelegate: this.hoverDelegate, + title: item.tooltip, + supportIcons: true, + })); + button.enabled = !item.disabled; + disposables.add(button.onDidClick(() => { + if (this.activeItem !== item) { + this.activeItem = item; + this.updateButtons(); + this._onDidSelect.fire(index); + } + })); + this.buttons.set(button, { item, dispose: () => disposables.dispose() }); + } + this.updateButtons(); + } + + setActiveItem(index: number): void { + if (index < 0 || index >= this.items.length) { + throw new Error('Invalid Index'); + } + this.activeItem = this.items[index]; + this.updateButtons(); + } + + setEnabled(enabled: boolean): void { + for (const [button] of this.buttons) { + button.enabled = enabled; + } + } + + private updateButtons(): void { + let isActive = false; + for (const [button, { item }] of this.buttons) { + const isPreviousActive = isActive; + isActive = item === this.activeItem; + button.element.classList.toggle('active', isActive); + button.element.classList.toggle('previous-active', isPreviousActive); + button.label = item.text; + } + } + +} diff --git a/patched-vscode/src/vs/base/browser/ui/sash/sash.ts b/patched-vscode/src/vs/base/browser/ui/sash/sash.ts index dfacef7d..210b98af 100644 --- a/patched-vscode/src/vs/base/browser/ui/sash/sash.ts +++ b/patched-vscode/src/vs/base/browser/ui/sash/sash.ts @@ -575,7 +575,7 @@ export class Sash extends Disposable { const onPointerUp = (e: PointerEvent) => { EventHelper.stop(e, false); - this.el.removeChild(style); + style.remove(); this.el.classList.remove('active'); this._onDidEnd.fire(); diff --git a/patched-vscode/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/patched-vscode/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index a58782d9..40ffcf6f 100644 --- a/patched-vscode/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/patched-vscode/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -9,7 +9,7 @@ import { IContentActionHandler } from 'vs/base/browser/formattedTextRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { AnchorPosition, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IListEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -104,7 +104,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private selectionDetailsPane!: HTMLElement; private _skipLayout: boolean = false; private _cachedMaxDetailsHeight?: number; - private _hover?: IUpdatableHover; + private _hover?: IManagedHover; private _sticky: boolean = false; // for dev purposes only @@ -153,7 +153,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private setTitle(title: string): void { if (!this._hover && title) { - this._hover = this._register(getBaseLayerHoverDelegate().setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.selectElement, title)); + this._hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.selectElement, title)); } else if (this._hover) { this._hover.update(title); } @@ -520,12 +520,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi return { dispose: () => { // contextView will dispose itself if moving from one View to another - try { - container.removeChild(this.selectDropDownContainer); // remove to take out the CSS rules we add - } - catch (error) { - // Ignore, removed already by change of focus - } + this.selectDropDownContainer.remove(); // remove to take out the CSS rules we add } }; } @@ -612,8 +607,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi && this.options.length > maxVisibleOptionsBelow ) { this._dropDownPosition = AnchorPosition.ABOVE; - this.selectDropDownContainer.removeChild(this.selectDropDownListContainer); - this.selectDropDownContainer.removeChild(this.selectionDetailsPane); + this.selectDropDownListContainer.remove(); + this.selectionDetailsPane.remove(); this.selectDropDownContainer.appendChild(this.selectionDetailsPane); this.selectDropDownContainer.appendChild(this.selectDropDownListContainer); @@ -622,8 +617,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } else { this._dropDownPosition = AnchorPosition.BELOW; - this.selectDropDownContainer.removeChild(this.selectDropDownListContainer); - this.selectDropDownContainer.removeChild(this.selectionDetailsPane); + this.selectDropDownListContainer.remove(); + this.selectionDetailsPane.remove(); this.selectDropDownContainer.appendChild(this.selectDropDownListContainer); this.selectDropDownContainer.appendChild(this.selectionDetailsPane); @@ -735,7 +730,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.listRenderer = new SelectListRenderer(); - this.selectList = new List('SelectBoxCustom', this.selectDropDownListContainer, this, [this.listRenderer], { + this.selectList = this._register(new List('SelectBoxCustom', this.selectDropDownListContainer, this, [this.listRenderer], { useShadows: false, verticalScrollMode: ScrollbarVisibility.Visible, keyboardSupport: false, @@ -761,7 +756,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi getRole: () => isMacintosh ? '' : 'option', getWidgetRole: () => 'listbox' } - }); + })); if (this.selectBoxOptions.ariaLabel) { this.selectList.ariaLabel = this.selectBoxOptions.ariaLabel; } @@ -879,7 +874,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi const tagName = child.tagName && child.tagName.toLowerCase(); if (tagName === 'img') { - element.removeChild(child); + child.remove(); } else { cleanRenderedMarkdown(child); } diff --git a/patched-vscode/src/vs/base/browser/ui/splitview/paneview.ts b/patched-vscode/src/vs/base/browser/ui/splitview/paneview.ts index a934addd..6a78dee3 100644 --- a/patched-vscode/src/vs/base/browser/ui/splitview/paneview.ts +++ b/patched-vscode/src/vs/base/browser/ui/splitview/paneview.ts @@ -382,7 +382,7 @@ class PaneDraggable extends Disposable { const dragImage = append(this.pane.element.ownerDocument.body, $('.monaco-drag-image', {}, this.pane.draggableElement.textContent || '')); e.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => this.pane.element.ownerDocument.body.removeChild(dragImage), 0); + setTimeout(() => dragImage.remove(), 0); this.context.draggable = this; } diff --git a/patched-vscode/src/vs/base/browser/ui/splitview/splitview.ts b/patched-vscode/src/vs/base/browser/ui/splitview/splitview.ts index ae7ad670..ac966adf 100644 --- a/patched-vscode/src/vs/base/browser/ui/splitview/splitview.ts +++ b/patched-vscode/src/vs/base/browser/ui/splitview/splitview.ts @@ -1127,7 +1127,7 @@ export class SplitView this.onViewChange(item, size)); - const containerDisposable = toDisposable(() => this.viewContainer.removeChild(container)); + const containerDisposable = toDisposable(() => container.remove()); const disposable = combinedDisposable(onChangeDisposable, containerDisposable); let viewSize: ViewItemSize; diff --git a/patched-vscode/src/vs/base/browser/ui/table/tableWidget.ts b/patched-vscode/src/vs/base/browser/ui/table/tableWidget.ts index 631c0015..db005027 100644 --- a/patched-vscode/src/vs/base/browser/ui/table/tableWidget.ts +++ b/patched-vscode/src/vs/base/browser/ui/table/tableWidget.ts @@ -134,7 +134,7 @@ class ColumnHeader extends Disposable implements IView { this.element = $('.monaco-table-th', { 'data-col-index': index }, column.label); if (column.tooltip) { - this._register(getBaseLayerHoverDelegate().setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip)); + this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip)); } } @@ -193,7 +193,7 @@ export class Table implements ISpliceable, IDisposable { user: string, container: HTMLElement, private virtualDelegate: ITableVirtualDelegate, - columns: ITableColumn[], + private columns: ITableColumn[], renderers: ITableRenderer[], _options?: ITableOptions ) { @@ -231,6 +231,15 @@ export class Table implements ISpliceable, IDisposable { this.style(unthemedListStyles); } + getColumnLabels(): string[] { + return this.columns.map(c => c.label); + } + + resizeColumn(index: number, percentage: number): void { + const size = Math.round((percentage / 100.00) * this.cachedWidth); + this.splitview.resizeView(index, size); + } + updateOptions(options: ITableOptionsUpdate): void { this.list.updateOptions(options); } diff --git a/patched-vscode/src/vs/base/browser/ui/toggle/toggle.css b/patched-vscode/src/vs/base/browser/ui/toggle/toggle.css index 1b13a3b1..6244ed4a 100644 --- a/patched-vscode/src/vs/base/browser/ui/toggle/toggle.css +++ b/patched-vscode/src/vs/base/browser/ui/toggle/toggle.css @@ -52,6 +52,12 @@ .monaco-action-bar .checkbox-action-item { display: flex; align-items: center; + border-radius: 2px; + padding-right: 2px; +} + +.monaco-action-bar .checkbox-action-item:hover { + background-color: var(--vscode-toolbar-hoverBackground); } .monaco-action-bar .checkbox-action-item > .monaco-custom-toggle.monaco-checkbox { diff --git a/patched-vscode/src/vs/base/browser/ui/toggle/toggle.ts b/patched-vscode/src/vs/base/browser/ui/toggle/toggle.ts index a52c0028..c141f381 100644 --- a/patched-vscode/src/vs/base/browser/ui/toggle/toggle.ts +++ b/patched-vscode/src/vs/base/browser/ui/toggle/toggle.ts @@ -15,7 +15,7 @@ import 'vs/css!./toggle'; import { isActiveElement, $, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; export interface IToggleOpts extends IToggleStyles { @@ -113,7 +113,7 @@ export class Toggle extends Widget { readonly domNode: HTMLElement; private _checked: boolean; - private _hover: IUpdatableHover; + private _hover: IManagedHover; constructor(opts: IToggleOpts) { super(); @@ -134,7 +134,7 @@ export class Toggle extends Widget { } this.domNode = document.createElement('div'); - this._hover = this._register(getBaseLayerHoverDelegate().setupUpdatableHover(opts.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); + this._hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(opts.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); this.domNode.classList.add(...classes); if (!this._opts.notFocusable) { this.domNode.tabIndex = 0; diff --git a/patched-vscode/src/vs/base/browser/ui/toolbar/toolbar.ts b/patched-vscode/src/vs/base/browser/ui/toolbar/toolbar.ts index 57aac5ed..ce42a2a9 100644 --- a/patched-vscode/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/patched-vscode/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -38,6 +38,16 @@ export interface IToolBarOptions { * If true, toggled primary items are highlighted with a background color. */ highlightToggledItems?: boolean; + + /** + * Render action with icons (default: `true`) + */ + icon?: boolean; + + /** + * Render action with label (default: `false`) + */ + label?: boolean; } /** @@ -50,7 +60,6 @@ export class ToolBar extends Disposable { private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined; private submenuActionViewItems: DropdownMenuActionViewItem[] = []; private hasSecondaryActions: boolean = false; - private readonly lookupKeybindings: boolean; private readonly element: HTMLElement; private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer()); @@ -62,7 +71,6 @@ export class ToolBar extends Disposable { options.hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate()); this.options = options; - this.lookupKeybindings = typeof this.options.getKeyBinding === 'function'; this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem?.show(), options.toggleMenuTitle)); @@ -198,7 +206,7 @@ export class ToolBar extends Disposable { } primaryActionsToSet.forEach(action => { - this.actionBar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) }); + this.actionBar.push(action, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(action) }); }); } @@ -207,7 +215,7 @@ export class ToolBar extends Disposable { } private getKeybindingLabel(action: IAction): string | undefined { - const key = this.lookupKeybindings ? this.options.getKeyBinding?.(action) : undefined; + const key = this.options.getKeyBinding?.(action); return key?.getLabel() ?? undefined; } diff --git a/patched-vscode/src/vs/base/browser/ui/tree/abstractTree.ts b/patched-vscode/src/vs/base/browser/ui/tree/abstractTree.ts index 7868ee55..0223c2d4 100644 --- a/patched-vscode/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/patched-vscode/src/vs/base/browser/ui/tree/abstractTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement, asCssValueWithDefault, isKeyboardEvent } from 'vs/base/browser/dom'; +import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement, asCssValueWithDefault, isKeyboardEvent, addDisposableListener } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -36,6 +36,7 @@ import { localize } from 'vs/nls'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { createInstantHoverDelegate, getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { autorun, constObservable } from 'vs/base/common/observable'; +import { alert } from 'vs/base/browser/ui/aria/aria'; class TreeElementsDragAndDropData extends ElementsDragAndDropData { @@ -796,7 +797,7 @@ class FindWidget extends Disposable { super(); container.appendChild(this.elements.root); - this._register(toDisposable(() => container.removeChild(this.elements.root))); + this._register(toDisposable(() => this.elements.root.remove())); const styles = options?.styles ?? unthemedFindWidgetStyles; @@ -1144,6 +1145,7 @@ class FindController implements IDisposable { const noMatches = this.filter.totalCount > 0 && this.filter.matchCount === 0; if (this.pattern && noMatches) { + alert(localize('replFindNoResults', "No results")); if (this.tree.options.showNotFoundMessage ?? true) { this.widget?.showMessage({ type: MessageType.WARNING, content: localize('not found', "No elements found.") }); } else { @@ -1151,6 +1153,9 @@ class FindController implements IDisposable { } } else { this.widget?.clearMessage(); + if (this.pattern) { + alert(localize('replFindResults', "{0} results", this.filter.matchCount)); + } } } @@ -1807,9 +1812,8 @@ class StickyScrollFocus extends Disposable { ) { super(); - this.container.addEventListener('focus', () => this.onFocus()); - this.container.addEventListener('blur', () => this.onBlur()); - + this._register(addDisposableListener(this.container, 'focus', () => this.onFocus())); + this._register(addDisposableListener(this.container, 'blur', () => this.onBlur())); this._register(this.view.onDidFocus(() => this.toggleStickyScrollFocused(false))); this._register(this.view.onKeyDown((e) => this.onKeyDown(e))); this._register(this.view.onMouseDown((e) => this.onMouseDown(e))); diff --git a/patched-vscode/src/vs/base/browser/ui/tree/asyncDataTree.ts b/patched-vscode/src/vs/base/browser/ui/tree/asyncDataTree.ts index bfd5af68..debb1a04 100644 --- a/patched-vscode/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/patched-vscode/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -569,10 +569,6 @@ export class AsyncDataTree implements IDisposable this.tree.resort(this.getDataNode(element), recursive); } - hasElement(element: TInput | T): boolean { - return this.tree.hasElement(this.getDataNode(element)); - } - hasNode(element: TInput | T): boolean { return element === this.root.element || this.nodes.has(element as T); } diff --git a/patched-vscode/src/vs/base/common/amd.ts b/patched-vscode/src/vs/base/common/amd.ts index 6d228840..9e813737 100644 --- a/patched-vscode/src/vs/base/common/amd.ts +++ b/patched-vscode/src/vs/base/common/amd.ts @@ -5,9 +5,11 @@ // ESM-comment-begin export const isESM = false; +export const canASAR = true; // ESM-comment-end // ESM-uncomment-begin // export const isESM = true; +// export const canASAR = false; // TODO@esm: ASAR disabled in ESM // ESM-uncomment-end export const enum LoaderEventType { diff --git a/patched-vscode/src/vs/base/common/arrays.ts b/patched-vscode/src/vs/base/common/arrays.ts index 52e542c0..d6e9fa88 100644 --- a/patched-vscode/src/vs/base/common/arrays.ts +++ b/patched-vscode/src/vs/base/common/arrays.ts @@ -885,3 +885,18 @@ export class Permutation { return new Permutation(inverseIndexMap); } } + +/** + * Asynchronous variant of `Array.find()`, returning the first element in + * the array for which the predicate returns true. + * + * This implementation does not bail early and waits for all promises to + * resolve before returning. + */ +export async function findAsync(array: readonly T[], predicate: (element: T, index: number) => Promise): Promise { + const results = await Promise.all(array.map( + async (element, index) => ({ element, ok: await predicate(element, index) }) + )); + + return results.find(r => r.ok)?.element; +} diff --git a/patched-vscode/src/vs/base/common/collections.ts b/patched-vscode/src/vs/base/common/collections.ts index 0b306144..d0df190c 100644 --- a/patched-vscode/src/vs/base/common/collections.ts +++ b/patched-vscode/src/vs/base/common/collections.ts @@ -80,3 +80,61 @@ export function intersection(setA: Set, setB: Iterable): Set { } return result; } + +export class SetWithKey implements Set { + private _map = new Map(); + + constructor(values: T[], private toKey: (t: T) => any) { + for (const value of values) { + this.add(value); + } + } + + get size(): number { + return this._map.size; + } + + add(value: T): this { + const key = this.toKey(value); + this._map.set(key, value); + return this; + } + + delete(value: T): boolean { + return this._map.delete(this.toKey(value)); + } + + has(value: T): boolean { + return this._map.has(this.toKey(value)); + } + + *entries(): IterableIterator<[T, T]> { + for (const entry of this._map.values()) { + yield [entry, entry]; + } + } + + keys(): IterableIterator { + return this.values(); + } + + *values(): IterableIterator { + for (const entry of this._map.values()) { + yield entry; + } + } + + clear(): void { + this._map.clear(); + } + + forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void { + this._map.forEach(entry => callbackfn.call(thisArg, entry, entry, this)); + } + + [Symbol.iterator](): IterableIterator { + return this.values(); + } + + [Symbol.toStringTag]: string = 'SetWithKey'; +} diff --git a/patched-vscode/src/vs/base/common/date.ts b/patched-vscode/src/vs/base/common/date.ts index 2ae03a71..663f2144 100644 --- a/patched-vscode/src/vs/base/common/date.ts +++ b/patched-vscode/src/vs/base/common/date.ts @@ -199,6 +199,27 @@ export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTi } } +export function fromNowByDay(date: number | Date, appendAgoLabel?: boolean, useFullTimeWords?: boolean): string { + if (typeof date !== 'number') { + date = date.getTime(); + } + + const todayMidnightTime = new Date(); + todayMidnightTime.setHours(0, 0, 0, 0); + const yesterdayMidnightTime = new Date(todayMidnightTime.getTime()); + yesterdayMidnightTime.setDate(yesterdayMidnightTime.getDate() - 1); + + if (date > todayMidnightTime.getTime()) { + return localize('today', 'Today'); + } + + if (date > yesterdayMidnightTime.getTime()) { + return localize('yesterday', 'Yesterday'); + } + + return fromNow(date, appendAgoLabel, useFullTimeWords); +} + /** * Gets a readable duration with intelligent/lossy precision. For example "40ms" or "3.040s") * @param ms The duration to get in milliseconds. diff --git a/patched-vscode/src/vs/base/common/equals.ts b/patched-vscode/src/vs/base/common/equals.ts index 22825c59..6e2ae850 100644 --- a/patched-vscode/src/vs/base/common/equals.ts +++ b/patched-vscode/src/vs/base/common/equals.ts @@ -6,6 +6,10 @@ import * as arrays from 'vs/base/common/arrays'; export type EqualityComparer = (a: T, b: T) => boolean; + +/** + * Compares two items for equality using strict equality. +*/ export const strictEquals: EqualityComparer = (a, b) => a === b; /** @@ -30,11 +34,30 @@ export function itemEquals(): EqualityC return (a, b) => a.equals(b); } -export function equalsIfDefined(v1: T | undefined, v2: T | undefined, equals: EqualityComparer): boolean { - if (!v1 || !v2) { - return v1 === v2; +/** + * Checks if two items are both null or undefined, or are equal according to the provided equality comparer. +*/ +export function equalsIfDefined(v1: T | undefined | null, v2: T | undefined | null, equals: EqualityComparer): boolean; +/** + * Returns an equality comparer that checks if two items are both null or undefined, or are equal according to the provided equality comparer. +*/ +export function equalsIfDefined(equals: EqualityComparer): EqualityComparer; +export function equalsIfDefined(equalsOrV1: EqualityComparer | T, v2?: T | undefined | null, equals?: EqualityComparer): EqualityComparer | boolean { + if (equals !== undefined) { + const v1 = equalsOrV1 as T | undefined; + if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) { + return v2 === v1; + } + return equals(v1, v2); + } else { + const equals = equalsOrV1 as EqualityComparer; + return (v1, v2) => { + if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) { + return v2 === v1; + } + return equals(v1, v2); + }; } - return equals(v1, v2); } /** diff --git a/patched-vscode/src/vs/base/common/errors.ts b/patched-vscode/src/vs/base/common/errors.ts index f0d92960..ce5d8b29 100644 --- a/patched-vscode/src/vs/base/common/errors.ts +++ b/patched-vscode/src/vs/base/common/errors.ts @@ -137,6 +137,19 @@ export function transformErrorForSerialization(error: any): any { return error; } +export function transformErrorFromSerialization(data: SerializedError): Error { + let error: Error; + if (data.noTelemetry) { + error = new ErrorNoTelemetry(); + } else { + error = new Error(); + error.name = data.name; + } + error.message = data.message; + error.stack = data.stack; + return error; +} + // see https://github.com/v8/v8/wiki/Stack%20Trace%20API#basic-stack-traces export interface V8CallSite { getThis(): unknown; diff --git a/patched-vscode/src/vs/base/common/event.ts b/patched-vscode/src/vs/base/common/event.ts index f94fa367..95bb4678 100644 --- a/patched-vscode/src/vs/base/common/event.ts +++ b/patched-vscode/src/vs/base/common/event.ts @@ -111,6 +111,15 @@ export namespace Event { }; } + /** + * Given an event, returns another event which only fires once, and only when the condition is met. + * + * @param event The event source for the new event. + */ + export function onceIf(event: Event, condition: (e: T) => boolean): Event { + return Event.once(Event.filter(event, condition)); + } + /** * Maps an event of one type into an event of another type using a mapping function, similar to how * `Array.prototype.map` works. @@ -615,6 +624,25 @@ export namespace Event { return result.event; } + /** + * A convenience function for forwarding an event to another emitter which + * improves readability. + * + * This is similar to {@link Relay} but allows instantiating and forwarding + * on a single line and also allows for multiple source events. + * @param from The event to forward. + * @param to The emitter to forward the event to. + * @example + * Event.forward(event, emitter); + * // equivalent to + * event(e => emitter.fire(e)); + * // equivalent to + * event(emitter.fire, emitter); + */ + export function forward(from: Event, to: Emitter): IDisposable { + return from(e => to.fire(e)); + } + /** * Adds a listener to an event and calls the listener immediately with undefined as the event object. * @@ -642,6 +670,9 @@ export namespace Event { const options: EmitterOptions = { onWillAddFirstListener: () => { _observable.addObserver(this); + + // Communicate to the observable that we received its current value and would like to be notified about future changes. + this._observable.reportChanges(); }, onDidRemoveLastListener: () => { _observable.removeObserver(this); @@ -831,13 +862,15 @@ export function setGlobalLeakWarningThreshold(n: number): IDisposable { class LeakageMonitor { + private static _idPool = 1; + private _stacks: Map | undefined; private _warnCountdown: number = 0; constructor( private readonly _errorHandler: (err: Error) => void, readonly threshold: number, - readonly name: string = Math.random().toString(18).slice(2, 5), + readonly name: string = (LeakageMonitor._idPool++).toString(16).padStart(3, '0') ) { } dispose(): void { @@ -952,14 +985,26 @@ const forEachListener = (listeners: ListenerOrListeners, fn: (c: ListenerC }; -const _listenerFinalizers = _enableListenerGCedWarning - ? new FinalizationRegistry(heldValue => { +let _listenerFinalizers: FinalizationRegistry | undefined; + +if (_enableListenerGCedWarning) { + const leaks: string[] = []; + + setInterval(() => { + if (leaks.length === 0) { + return; + } + console.warn('[LEAKING LISTENERS] GC\'ed these listeners that were NOT yet disposed:'); + console.warn(leaks.join('\n')); + leaks.length = 0; + }, 3000); + + _listenerFinalizers = new FinalizationRegistry(heldValue => { if (typeof heldValue === 'string') { - console.warn('[LEAKING LISTENER] GC\'ed a listener that was NOT yet disposed. This is where is was created:'); - console.warn(heldValue); + leaks.push(heldValue); } - }) - : undefined; + }); +} /** * The Emitter can be used to expose an Event to the public @@ -1126,8 +1171,9 @@ export class Emitter { } if (_listenerFinalizers) { - const stack = new Error().stack!.split('\n').slice(2).join('\n').trim(); - _listenerFinalizers.register(result, stack, result); + const stack = new Error().stack!.split('\n').slice(2, 3).join('\n').trim(); + const match = /(file:|vscode-file:\/\/vscode-app)?(\/[^:]*:\d+:\d+)/.exec(stack); + _listenerFinalizers.register(result, match?.[2] ?? stack, result); } return result; diff --git a/patched-vscode/src/vs/base/common/history.ts b/patched-vscode/src/vs/base/common/history.ts index de578810..9d644a85 100644 --- a/patched-vscode/src/vs/base/common/history.ts +++ b/patched-vscode/src/vs/base/common/history.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { SetWithKey } from 'vs/base/common/collections'; import { ArrayNavigator, INavigator } from 'vs/base/common/navigator'; export class HistoryNavigator implements INavigator { @@ -114,6 +115,10 @@ interface HistoryNode { next: HistoryNode | undefined; } +/** + * The right way to use HistoryNavigator2 is for the last item in the list to be the user's uncommitted current text. eg empty string, or whatever has been typed. Then + * the user can navigate away from the last item through the list, and back to it. When updating the last item, call replaceLast. + */ export class HistoryNavigator2 { private valueSet: Set; @@ -123,7 +128,7 @@ export class HistoryNavigator2 { private _size: number; get size(): number { return this._size; } - constructor(history: readonly T[], private capacity: number = 10) { + constructor(history: readonly T[], private capacity: number = 10, private identityFn: (t: T) => any = t => t) { if (history.length < 1) { throw new Error('not supported'); } @@ -135,7 +140,7 @@ export class HistoryNavigator2 { next: undefined }; - this.valueSet = new Set([history[0]]); + this.valueSet = new SetWithKey([history[0]], identityFn); for (let i = 1; i < history.length; i++) { this.add(history[i]); } @@ -172,7 +177,7 @@ export class HistoryNavigator2 { * @returns old last value */ replaceLast(value: T): T { - if (this.tail.value === value) { + if (this.identityFn(this.tail.value) === this.identityFn(value)) { return value; } @@ -252,8 +257,9 @@ export class HistoryNavigator2 { private _deleteFromList(value: T): void { let temp = this.head; + const valueKey = this.identityFn(value); while (temp !== this.tail) { - if (temp.value === value) { + if (this.identityFn(temp.value) === valueKey) { if (temp === this.head) { this.head = this.head.next!; this.head.previous = undefined; diff --git a/patched-vscode/src/vs/base/common/hotReload.ts b/patched-vscode/src/vs/base/common/hotReload.ts index 94dec8e9..609fd9d8 100644 --- a/patched-vscode/src/vs/base/common/hotReload.ts +++ b/patched-vscode/src/vs/base/common/hotReload.ts @@ -41,9 +41,23 @@ function registerGlobalHotReloadHandler() { g.$hotReload_applyNewExports = args => { const args2 = { config: { mode: undefined }, ...args }; + const results: AcceptNewExportsHandler[] = []; for (const h of hotReloadHandlers!) { const result = h(args2); - if (result) { return result; } + if (result) { + results.push(result); + } + } + if (results.length > 0) { + return newExports => { + let result = false; + for (const r of results) { + if (r(newExports)) { + result = true; + } + } + return result; + }; } return undefined; }; diff --git a/patched-vscode/src/vs/base/common/hotReloadHelpers.ts b/patched-vscode/src/vs/base/common/hotReloadHelpers.ts new file mode 100644 index 00000000..174b1adc --- /dev/null +++ b/patched-vscode/src/vs/base/common/hotReloadHelpers.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isHotReloadEnabled, registerHotReloadHandler } from 'vs/base/common/hotReload'; +import { IReader, observableSignalFromEvent } from 'vs/base/common/observable'; + +export function readHotReloadableExport(value: T, reader: IReader | undefined): T { + observeHotReloadableExports([value], reader); + return value; +} + +export function observeHotReloadableExports(values: any[], reader: IReader | undefined): void { + if (isHotReloadEnabled()) { + const o = observableSignalFromEvent( + 'reload', + event => registerHotReloadHandler(({ oldExports }) => { + if (![...Object.values(oldExports)].some(v => values.includes(v))) { + return undefined; + } + return (_newExports) => { + event(undefined); + return true; + }; + }) + ); + o.read(reader); + } +} diff --git a/patched-vscode/src/vs/base/common/iterator.ts b/patched-vscode/src/vs/base/common/iterator.ts index 0bbc2413..c329ed6d 100644 --- a/patched-vscode/src/vs/base/common/iterator.ts +++ b/patched-vscode/src/vs/base/common/iterator.ts @@ -44,9 +44,10 @@ export namespace Iterable { return iterable[Symbol.iterator]().next().value; } - export function some(iterable: Iterable, predicate: (t: T) => unknown): boolean { + export function some(iterable: Iterable, predicate: (t: T, i: number) => unknown): boolean { + let i = 0; for (const element of iterable) { - if (predicate(element)) { + if (predicate(element, i++)) { return true; } } @@ -82,6 +83,13 @@ export namespace Iterable { } } + export function* flatMap(iterable: Iterable, fn: (t: T, index: number) => Iterable): Iterable { + let index = 0; + for (const element of iterable) { + yield* fn(element, index++); + } + } + export function* concat(...iterables: Iterable[]): Iterable { for (const iterable of iterables) { yield* iterable; diff --git a/patched-vscode/src/vs/base/common/json.ts b/patched-vscode/src/vs/base/common/json.ts index dadcbaf7..e4adc590 100644 --- a/patched-vscode/src/vs/base/common/json.ts +++ b/patched-vscode/src/vs/base/common/json.ts @@ -1308,40 +1308,6 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions return true; } -/** - * Takes JSON with JavaScript-style comments and remove - * them. Optionally replaces every none-newline character - * of comments with a replaceCharacter - */ -export function stripComments(text: string, replaceCh?: string): string { - - const _scanner = createScanner(text); - const parts: string[] = []; - let kind: SyntaxKind; - let offset = 0; - let pos: number; - - do { - pos = _scanner.getPosition(); - kind = _scanner.scan(); - switch (kind) { - case SyntaxKind.LineCommentTrivia: - case SyntaxKind.BlockCommentTrivia: - case SyntaxKind.EOF: - if (offset !== pos) { - parts.push(text.substring(offset, pos)); - } - if (replaceCh !== undefined) { - parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh)); - } - offset = _scanner.getPosition(); - break; - } - } while (kind !== SyntaxKind.EOF); - - return parts.join(''); -} - export function getNodeType(value: any): NodeType { switch (typeof value) { case 'boolean': return 'boolean'; diff --git a/patched-vscode/src/vs/base/common/jsonSchema.ts b/patched-vscode/src/vs/base/common/jsonSchema.ts index 4216b0e5..fbf6d981 100644 --- a/patched-vscode/src/vs/base/common/jsonSchema.ts +++ b/patched-vscode/src/vs/base/common/jsonSchema.ts @@ -118,3 +118,150 @@ export type SchemaToType = T extends { type: 'string' } : T extends { type: 'array'; items: infer I } ? Array> : never; + +interface Equals { schemas: IJSONSchema[]; id?: string } + +export function getCompressedContent(schema: IJSONSchema): string { + let hasDups = false; + + + // visit all schema nodes and collect the ones that are equal + const equalsByString = new Map(); + const nodeToEquals = new Map(); + const visitSchemas = (next: IJSONSchema) => { + if (schema === next) { + return true; + } + const val = JSON.stringify(next); + if (val.length < 30) { + // the $ref takes around 25 chars, so we don't save anything + return true; + } + const eq = equalsByString.get(val); + if (!eq) { + const newEq = { schemas: [next] }; + equalsByString.set(val, newEq); + nodeToEquals.set(next, newEq); + return true; + } + eq.schemas.push(next); + nodeToEquals.set(next, eq); + hasDups = true; + return false; + }; + traverseNodes(schema, visitSchemas); + equalsByString.clear(); + + if (!hasDups) { + return JSON.stringify(schema); + } + + let defNodeName = '$defs'; + while (schema.hasOwnProperty(defNodeName)) { + defNodeName += '_'; + } + + // used to collect all schemas that are later put in `$defs`. The index in the array is the id of the schema. + const definitions: IJSONSchema[] = []; + + function stringify(root: IJSONSchema): string { + return JSON.stringify(root, (_key: string, value: any) => { + if (value !== root) { + const eq = nodeToEquals.get(value); + if (eq && eq.schemas.length > 1) { + if (!eq.id) { + eq.id = `_${definitions.length}`; + definitions.push(eq.schemas[0]); + } + return { $ref: `#/${defNodeName}/${eq.id}` }; + } + } + return value; + }); + } + + // stringify the schema and replace duplicate subtrees with $ref + // this will add new items to the definitions array + const str = stringify(schema); + + // now stringify the definitions. Each invication of stringify cann add new items to the definitions array, so the length can grow while we iterate + const defStrings: string[] = []; + for (let i = 0; i < definitions.length; i++) { + defStrings.push(`"_${i}":${stringify(definitions[i])}`); + } + if (defStrings.length) { + return `${str.substring(0, str.length - 1)},"${defNodeName}":{${defStrings.join(',')}}}`; + } + return str; +} + +type IJSONSchemaRef = IJSONSchema | boolean; + +function isObject(thing: any): thing is object { + return typeof thing === 'object' && thing !== null; +} + +/* + * Traverse a JSON schema and visit each schema node +*/ +function traverseNodes(root: IJSONSchema, visit: (schema: IJSONSchema) => boolean) { + if (!root || typeof root !== 'object') { + return; + } + const collectEntries = (...entries: (IJSONSchemaRef | undefined)[]) => { + for (const entry of entries) { + if (isObject(entry)) { + toWalk.push(entry); + } + } + }; + const collectMapEntries = (...maps: (IJSONSchemaMap | undefined)[]) => { + for (const map of maps) { + if (isObject(map)) { + for (const key in map) { + const entry = map[key]; + if (isObject(entry)) { + toWalk.push(entry); + } + } + } + } + }; + const collectArrayEntries = (...arrays: (IJSONSchemaRef[] | undefined)[]) => { + for (const array of arrays) { + if (Array.isArray(array)) { + for (const entry of array) { + if (isObject(entry)) { + toWalk.push(entry); + } + } + } + } + }; + const collectEntryOrArrayEntries = (items: (IJSONSchemaRef[] | IJSONSchemaRef | undefined)) => { + if (Array.isArray(items)) { + for (const entry of items) { + if (isObject(entry)) { + toWalk.push(entry); + } + } + } else if (isObject(items)) { + toWalk.push(items); + } + }; + + const toWalk: IJSONSchema[] = [root]; + + let next = toWalk.pop(); + while (next) { + const visitChildern = visit(next); + if (visitChildern) { + collectEntries(next.additionalItems, next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else, next.unevaluatedItems, next.unevaluatedProperties); + collectMapEntries(next.definitions, next.$defs, next.properties, next.patternProperties, next.dependencies, next.dependentSchemas); + collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.prefixItems); + collectEntryOrArrayEntries(next.items); + } + next = toWalk.pop(); + } +} + diff --git a/patched-vscode/src/vs/base/common/stripComments.d.ts b/patched-vscode/src/vs/base/common/jsonc.d.ts similarity index 74% rename from patched-vscode/src/vs/base/common/stripComments.d.ts rename to patched-vscode/src/vs/base/common/jsonc.d.ts index af5b182b..504e6c60 100644 --- a/patched-vscode/src/vs/base/common/stripComments.d.ts +++ b/patched-vscode/src/vs/base/common/jsonc.d.ts @@ -3,11 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/** + * A drop-in replacement for JSON.parse that can parse + * JSON with comments and trailing commas. + * + * @param content the content to strip comments from + * @returns the parsed content as JSON +*/ +export function parse(content: string): any; + /** * Strips single and multi line JavaScript comments from JSON * content. Ignores characters in strings BUT doesn't support * string continuation across multiple lines since it is not * supported in JSON. + * * @param content the content to strip comments from * @returns the content without comments */ diff --git a/patched-vscode/src/vs/base/common/stripComments.js b/patched-vscode/src/vs/base/common/jsonc.js similarity index 69% rename from patched-vscode/src/vs/base/common/stripComments.js rename to patched-vscode/src/vs/base/common/jsonc.js index c59205e1..8f1c75aa 100644 --- a/patched-vscode/src/vs/base/common/stripComments.js +++ b/patched-vscode/src/vs/base/common/jsonc.js @@ -3,12 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; +/// //@ts-check +'use strict'; + +// ESM-uncomment-begin +// /** @type any */ +// const module = { exports: {} }; +// ESM-uncomment-end (function () { - function factory(path, os, productName, cwd) { + + function factory() { // First group matches a double quoted string // Second group matches a single quoted string // Third group matches a multi line comment @@ -17,7 +24,6 @@ const regexp = /("[^"\\]*(?:\\.[^"\\]*)*")|('[^'\\]*(?:\\.[^'\\]*)*')|(\/\*[^\/\*]*(?:(?:\*|\/)[^\/\*]*)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))|(,\s*[}\]])/g; /** - * * @param {string} content * @returns {string} */ @@ -46,12 +52,27 @@ } }); } + + /** + * @param {string} content + * @returns {any} + */ + function parse(content) { + const commentsStripped = stripComments(content); + + try { + return JSON.parse(commentsStripped); + } catch (error) { + const trailingCommasStriped = commentsStripped.replace(/,\s*([}\]])/g, '$1'); + return JSON.parse(trailingCommasStriped); + } + } return { - stripComments + stripComments, + parse }; } - if (typeof define === 'function') { // amd define([], function () { return factory(); }); @@ -59,6 +80,11 @@ // commonjs module.exports = factory(); } else { - console.trace('strip comments defined in UNKNOWN context (neither requirejs or commonjs)'); + console.trace('jsonc defined in UNKNOWN context (neither requirejs or commonjs)'); } })(); + +// ESM-uncomment-begin +// export const stripComments = module.exports.stripComments; +// export const parse = module.exports.parse; +// ESM-uncomment-end diff --git a/patched-vscode/src/vs/base/common/marked/cgmanifest.json b/patched-vscode/src/vs/base/common/marked/cgmanifest.json index 4dffc3ff..2c76256e 100644 --- a/patched-vscode/src/vs/base/common/marked/cgmanifest.json +++ b/patched-vscode/src/vs/base/common/marked/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "marked", "repositoryUrl": "https://github.com/markedjs/marked", - "commitHash": "7e2ef307846427650114591f9257b5545868e928" + "commitHash": "7972d7f9b578a31b32f469c14fc97c39ceb2b6c6" } }, "license": "MIT", - "version": "4.1.0" + "version": "14.0.0" } ], "version": 1 diff --git a/patched-vscode/src/vs/base/common/marked/marked.d.ts b/patched-vscode/src/vs/base/common/marked/marked.d.ts index 7aeab2e8..bbd49c48 100644 --- a/patched-vscode/src/vs/base/common/marked/marked.d.ts +++ b/patched-vscode/src/vs/base/common/marked/marked.d.ts @@ -1,600 +1,704 @@ -// Type definitions for Marked 4.0 -// Project: https://github.com/markedjs/marked, https://marked.js.org -// Definitions by: William Orr -// BendingBender -// CrossR -// Mike Wickett -// Hitomi Hatsukaze -// Ezra Celli -// Romain LE BARO -// Sarun Intaralawan -// Tony Brix -// Anatolii Titov -// Jean-Francois Cere -// Mykhaylo Stolyarchuk -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - +// Generated by dts-bundle-generator v9.5.1 + +export type MarkedToken = (Tokens.Space | Tokens.Code | Tokens.Heading | Tokens.Table | Tokens.Hr | Tokens.Blockquote | Tokens.List | Tokens.ListItem | Tokens.Paragraph | Tokens.HTML | Tokens.Text | Tokens.Def | Tokens.Escape | Tokens.Tag | Tokens.Image | Tokens.Link | Tokens.Strong | Tokens.Em | Tokens.Codespan | Tokens.Br | Tokens.Del); +export type Token = (MarkedToken | Tokens.Generic); +export declare namespace Tokens { + interface Space { + type: "space"; + raw: string; + } + interface Code { + type: "code"; + raw: string; + codeBlockStyle?: "indented" | undefined; + lang?: string | undefined; + text: string; + escaped?: boolean; + } + interface Heading { + type: "heading"; + raw: string; + depth: number; + text: string; + tokens: Token[]; + } + interface Table { + type: "table"; + raw: string; + align: Array<"center" | "left" | "right" | null>; + header: TableCell[]; + rows: TableCell[][]; + } + interface TableRow { + text: string; + } + interface TableCell { + text: string; + tokens: Token[]; + header: boolean; + align: "center" | "left" | "right" | null; + } + interface Hr { + type: "hr"; + raw: string; + } + interface Blockquote { + type: "blockquote"; + raw: string; + text: string; + tokens: Token[]; + } + interface List { + type: "list"; + raw: string; + ordered: boolean; + start: number | ""; + loose: boolean; + items: ListItem[]; + } + interface ListItem { + type: "list_item"; + raw: string; + task: boolean; + checked?: boolean | undefined; + loose: boolean; + text: string; + tokens: Token[]; + } + interface Checkbox { + checked: boolean; + } + interface Paragraph { + type: "paragraph"; + raw: string; + pre?: boolean | undefined; + text: string; + tokens: Token[]; + } + interface HTML { + type: "html"; + raw: string; + pre: boolean; + text: string; + block: boolean; + } + interface Text { + type: "text"; + raw: string; + text: string; + tokens?: Token[]; + } + interface Def { + type: "def"; + raw: string; + tag: string; + href: string; + title: string; + } + interface Escape { + type: "escape"; + raw: string; + text: string; + } + interface Tag { + type: "text" | "html"; + raw: string; + inLink: boolean; + inRawBlock: boolean; + text: string; + block: boolean; + } + interface Link { + type: "link"; + raw: string; + href: string; + title?: string | null; + text: string; + tokens: Token[]; + } + interface Image { + type: "image"; + raw: string; + href: string; + title: string | null; + text: string; + } + interface Strong { + type: "strong"; + raw: string; + text: string; + tokens: Token[]; + } + interface Em { + type: "em"; + raw: string; + text: string; + tokens: Token[]; + } + interface Codespan { + type: "codespan"; + raw: string; + text: string; + } + interface Br { + type: "br"; + raw: string; + } + interface Del { + type: "del"; + raw: string; + text: string; + tokens: Token[]; + } + interface Generic { + [index: string]: any; + type: string; + raw: string; + tokens?: Token[] | undefined; + } +} +export type Links = Record>; +export type TokensList = Token[] & { + links: Links; +}; /** - * Compiles markdown to HTML synchronously. - * - * @param src String of markdown source to be compiled - * @param options Optional hash of options - * @return String of compiled HTML + * Renderer */ -export function marked(src: string, options?: marked.MarkedOptions): string; - +declare class _Renderer { + options: MarkedOptions; + parser: _Parser; + constructor(options?: MarkedOptions); + space(token: Tokens.Space): string; + code({ text, lang, escaped }: Tokens.Code): string; + blockquote({ tokens }: Tokens.Blockquote): string; + html({ text }: Tokens.HTML | Tokens.Tag): string; + heading({ tokens, depth }: Tokens.Heading): string; + hr(token: Tokens.Hr): string; + list(token: Tokens.List): string; + listitem(item: Tokens.ListItem): string; + checkbox({ checked }: Tokens.Checkbox): string; + paragraph({ tokens }: Tokens.Paragraph): string; + table(token: Tokens.Table): string; + tablerow({ text }: Tokens.TableRow): string; + tablecell(token: Tokens.TableCell): string; + /** + * span level renderer + */ + strong({ tokens }: Tokens.Strong): string; + em({ tokens }: Tokens.Em): string; + codespan({ text }: Tokens.Codespan): string; + br(token: Tokens.Br): string; + del({ tokens }: Tokens.Del): string; + link({ href, title, tokens }: Tokens.Link): string; + image({ href, title, text }: Tokens.Image): string; + text(token: Tokens.Text | Tokens.Escape | Tokens.Tag): string; +} /** - * Compiles markdown to HTML asynchronously. - * - * @param src String of markdown source to be compiled - * @param callback Function called when the markdownString has been fully parsed when using async highlighting + * TextRenderer + * returns only the textual part of the token */ -export function marked(src: string, callback: (error: any, parseResult: string) => void): void; - +declare class _TextRenderer { + strong({ text }: Tokens.Strong): string; + em({ text }: Tokens.Em): string; + codespan({ text }: Tokens.Codespan): string; + del({ text }: Tokens.Del): string; + html({ text }: Tokens.HTML | Tokens.Tag): string; + text({ text }: Tokens.Text | Tokens.Escape | Tokens.Tag): string; + link({ text }: Tokens.Link): string; + image({ text }: Tokens.Image): string; + br(): string; +} /** - * Compiles markdown to HTML asynchronously. - * - * @param src String of markdown source to be compiled - * @param options Hash of options - * @param callback Function called when the markdownString has been fully parsed when using async highlighting + * Parsing & Compiling */ -export function marked( - src: string, - options: marked.MarkedOptions, - callback: (error: any, parseResult: string) => void, -): void; - -export class Lexer extends marked.Lexer { } -export class Parser extends marked.Parser { } -export class Tokenizer extends marked.Tokenizer { } -export class Renderer extends marked.Renderer { } -export class TextRenderer extends marked.TextRenderer { } -export class Slugger extends marked.Slugger { } - -export namespace marked { - const defaults: MarkedOptions; - +declare class _Parser { + options: MarkedOptions; + renderer: _Renderer; + textRenderer: _TextRenderer; + constructor(options?: MarkedOptions); /** - * @param src String of markdown source to be compiled - * @param options Hash of options + * Static Parse Method */ - function lexer(src: string, options?: MarkedOptions): TokensList; - + static parse(tokens: Token[], options?: MarkedOptions): string; /** - * Compiles markdown to HTML. - * - * @param src String of markdown source to be compiled - * @param callback Function called when the markdownString has been fully parsed when using async highlighting - * @return String of compiled HTML + * Static Parse Inline Method */ - function parse(src: string, callback: (error: any, parseResult: string) => void): string; - + static parseInline(tokens: Token[], options?: MarkedOptions): string; /** - * Compiles markdown to HTML. - * - * @param src String of markdown source to be compiled - * @param options Hash of options - * @param callback Function called when the markdownString has been fully parsed when using async highlighting - * @return String of compiled HTML + * Parse Loop */ - function parse(src: string, options?: MarkedOptions, callback?: (error: any, parseResult: string) => void): string; - + parse(tokens: Token[], top?: boolean): string; /** - * @param src Tokenized source as array of tokens - * @param options Hash of options + * Parse Inline Tokens */ - function parser(src: Token[] | TokensList, options?: MarkedOptions): string; - + parseInline(tokens: Token[], renderer?: _Renderer | _TextRenderer): string; +} +declare const blockNormal: { + blockquote: RegExp; + code: RegExp; + def: RegExp; + fences: RegExp; + heading: RegExp; + hr: RegExp; + html: RegExp; + lheading: RegExp; + list: RegExp; + newline: RegExp; + paragraph: RegExp; + table: RegExp; + text: RegExp; +}; +export type BlockKeys = keyof typeof blockNormal; +declare const inlineNormal: { + _backpedal: RegExp; + anyPunctuation: RegExp; + autolink: RegExp; + blockSkip: RegExp; + br: RegExp; + code: RegExp; + del: RegExp; + emStrongLDelim: RegExp; + emStrongRDelimAst: RegExp; + emStrongRDelimUnd: RegExp; + escape: RegExp; + link: RegExp; + nolink: RegExp; + punctuation: RegExp; + reflink: RegExp; + reflinkSearch: RegExp; + tag: RegExp; + text: RegExp; + url: RegExp; +}; +export type InlineKeys = keyof typeof inlineNormal; +/** + * exports + */ +export declare const block: { + normal: { + blockquote: RegExp; + code: RegExp; + def: RegExp; + fences: RegExp; + heading: RegExp; + hr: RegExp; + html: RegExp; + lheading: RegExp; + list: RegExp; + newline: RegExp; + paragraph: RegExp; + table: RegExp; + text: RegExp; + }; + gfm: Record<"code" | "blockquote" | "hr" | "html" | "table" | "text" | "heading" | "list" | "paragraph" | "def" | "fences" | "lheading" | "newline", RegExp>; + pedantic: Record<"code" | "blockquote" | "hr" | "html" | "table" | "text" | "heading" | "list" | "paragraph" | "def" | "fences" | "lheading" | "newline", RegExp>; +}; +export declare const inline: { + normal: { + _backpedal: RegExp; + anyPunctuation: RegExp; + autolink: RegExp; + blockSkip: RegExp; + br: RegExp; + code: RegExp; + del: RegExp; + emStrongLDelim: RegExp; + emStrongRDelimAst: RegExp; + emStrongRDelimUnd: RegExp; + escape: RegExp; + link: RegExp; + nolink: RegExp; + punctuation: RegExp; + reflink: RegExp; + reflinkSearch: RegExp; + tag: RegExp; + text: RegExp; + url: RegExp; + }; + gfm: Record<"link" | "code" | "url" | "br" | "del" | "text" | "escape" | "tag" | "reflink" | "autolink" | "nolink" | "_backpedal" | "anyPunctuation" | "blockSkip" | "emStrongLDelim" | "emStrongRDelimAst" | "emStrongRDelimUnd" | "punctuation" | "reflinkSearch", RegExp>; + breaks: Record<"link" | "code" | "url" | "br" | "del" | "text" | "escape" | "tag" | "reflink" | "autolink" | "nolink" | "_backpedal" | "anyPunctuation" | "blockSkip" | "emStrongLDelim" | "emStrongRDelimAst" | "emStrongRDelimUnd" | "punctuation" | "reflinkSearch", RegExp>; + pedantic: Record<"link" | "code" | "url" | "br" | "del" | "text" | "escape" | "tag" | "reflink" | "autolink" | "nolink" | "_backpedal" | "anyPunctuation" | "blockSkip" | "emStrongLDelim" | "emStrongRDelimAst" | "emStrongRDelimUnd" | "punctuation" | "reflinkSearch", RegExp>; +}; +export interface Rules { + block: Record; + inline: Record; +} +/** + * Tokenizer + */ +declare class _Tokenizer { + options: MarkedOptions; + rules: Rules; + lexer: _Lexer; + constructor(options?: MarkedOptions); + space(src: string): Tokens.Space | undefined; + code(src: string): Tokens.Code | undefined; + fences(src: string): Tokens.Code | undefined; + heading(src: string): Tokens.Heading | undefined; + hr(src: string): Tokens.Hr | undefined; + blockquote(src: string): Tokens.Blockquote | undefined; + list(src: string): Tokens.List | undefined; + html(src: string): Tokens.HTML | undefined; + def(src: string): Tokens.Def | undefined; + table(src: string): Tokens.Table | undefined; + lheading(src: string): Tokens.Heading | undefined; + paragraph(src: string): Tokens.Paragraph | undefined; + text(src: string): Tokens.Text | undefined; + escape(src: string): Tokens.Escape | undefined; + tag(src: string): Tokens.Tag | undefined; + link(src: string): Tokens.Link | Tokens.Image | undefined; + reflink(src: string, links: Links): Tokens.Link | Tokens.Image | Tokens.Text | undefined; + emStrong(src: string, maskedSrc: string, prevChar?: string): Tokens.Em | Tokens.Strong | undefined; + codespan(src: string): Tokens.Codespan | undefined; + br(src: string): Tokens.Br | undefined; + del(src: string): Tokens.Del | undefined; + autolink(src: string): Tokens.Link | undefined; + url(src: string): Tokens.Link | undefined; + inlineText(src: string): Tokens.Text | undefined; +} +declare class _Hooks { + options: MarkedOptions; + constructor(options?: MarkedOptions); + static passThroughHooks: Set; /** - * Compiles markdown to HTML without enclosing `p` tag. - * - * @param src String of markdown source to be compiled - * @param options Hash of options - * @return String of compiled HTML + * Process markdown before marked */ - function parseInline(src: string, options?: MarkedOptions): string; - + preprocess(markdown: string): string; + /** + * Process HTML after marked is finished + */ + postprocess(html: string): string; + /** + * Process all tokens before walk tokens + */ + processAllTokens(tokens: Token[] | TokensList): Token[] | TokensList; +} +export interface TokenizerThis { + lexer: _Lexer; +} +export type TokenizerExtensionFunction = (this: TokenizerThis, src: string, tokens: Token[] | TokensList) => Tokens.Generic | undefined; +export type TokenizerStartFunction = (this: TokenizerThis, src: string) => number | void; +export interface TokenizerExtension { + name: string; + level: "block" | "inline"; + start?: TokenizerStartFunction | undefined; + tokenizer: TokenizerExtensionFunction; + childTokens?: string[] | undefined; +} +export interface RendererThis { + parser: _Parser; +} +export type RendererExtensionFunction = (this: RendererThis, token: Tokens.Generic) => string | false | undefined; +export interface RendererExtension { + name: string; + renderer: RendererExtensionFunction; +} +export type TokenizerAndRendererExtension = TokenizerExtension | RendererExtension | (TokenizerExtension & RendererExtension); +export type HooksApi = Omit<_Hooks, "constructor" | "options">; +export type HooksObject = { + [K in keyof HooksApi]?: (this: _Hooks, ...args: Parameters) => ReturnType | Promise>; +}; +export type RendererApi = Omit<_Renderer, "constructor" | "options" | "parser">; +export type RendererObject = { + [K in keyof RendererApi]?: (this: _Renderer, ...args: Parameters) => ReturnType | false; +}; +export type TokenizerApi = Omit<_Tokenizer, "constructor" | "options" | "rules" | "lexer">; +export type TokenizerObject = { + [K in keyof TokenizerApi]?: (this: _Tokenizer, ...args: Parameters) => ReturnType | false; +}; +export interface MarkedExtension { + /** + * True will tell marked to await any walkTokens functions before parsing the tokens and returning an HTML string. + */ + async?: boolean; + /** + * Enable GFM line breaks. This option requires the gfm option to be true. + */ + breaks?: boolean | undefined; + /** + * Add tokenizers and renderers to marked + */ + extensions?: TokenizerAndRendererExtension[] | undefined | null; + /** + * Enable GitHub flavored markdown. + */ + gfm?: boolean | undefined; /** - * Sets the default options. + * Hooks are methods that hook into some part of marked. + * preprocess is called to process markdown before sending it to marked. + * processAllTokens is called with the TokensList before walkTokens. + * postprocess is called to process html after marked has finished parsing. + */ + hooks?: HooksObject | undefined | null; + /** + * Conform to obscure parts of markdown.pl as much as possible. Don't fix any of the original markdown bugs or poor behavior. + */ + pedantic?: boolean | undefined; + /** + * Type: object Default: new Renderer() * - * @param options Hash of options + * An object containing functions to render tokens to HTML. */ - function options(options: MarkedOptions): typeof marked; - + renderer?: RendererObject | undefined | null; + /** + * Shows an HTML error message when rendering fails. + */ + silent?: boolean | undefined; + /** + * The tokenizer defines how to turn markdown text into tokens. + */ + tokenizer?: TokenizerObject | undefined | null; + /** + * The walkTokens function gets called with every token. + * Child tokens are called before moving on to sibling tokens. + * Each token is passed by reference so updates are persisted when passed to the parser. + * The return value of the function is ignored. + */ + walkTokens?: ((token: Token) => void | Promise) | undefined | null; +} +export interface MarkedOptions extends Omit { + /** + * Hooks are methods that hook into some part of marked. + */ + hooks?: _Hooks | undefined | null; /** - * Sets the default options. + * Type: object Default: new Renderer() * - * @param options Hash of options + * An object containing functions to render tokens to HTML. */ - function setOptions(options: MarkedOptions): typeof marked; - + renderer?: _Renderer | undefined | null; /** - * Gets the original marked default options. + * The tokenizer defines how to turn markdown text into tokens. */ - function getDefaults(): MarkedOptions; - - function walkTokens(tokens: Token[] | TokensList, callback: (token: Token) => void): typeof marked; - + tokenizer?: _Tokenizer | undefined | null; /** - * Use Extension - * @param MarkedExtension + * Custom extensions */ - function use(...extensions: MarkedExtension[]): void; - - class Tokenizer { - constructor(options?: MarkedOptions); - options: MarkedOptions; - space(this: Tokenizer & TokenizerThis, src: string): Tokens.Space | T; - code(this: Tokenizer & TokenizerThis, src: string): Tokens.Code | T; - fences(this: Tokenizer & TokenizerThis, src: string): Tokens.Code | T; - heading(this: Tokenizer & TokenizerThis, src: string): Tokens.Heading | T; - hr(this: Tokenizer & TokenizerThis, src: string): Tokens.Hr | T; - blockquote(this: Tokenizer & TokenizerThis, src: string): Tokens.Blockquote | T; - list(this: Tokenizer & TokenizerThis, src: string): Tokens.List | T; - html(this: Tokenizer & TokenizerThis, src: string): Tokens.HTML | T; - def(this: Tokenizer & TokenizerThis, src: string): Tokens.Def | T; - table(this: Tokenizer & TokenizerThis, src: string): Tokens.Table | T; - lheading(this: Tokenizer & TokenizerThis, src: string): Tokens.Heading | T; - paragraph(this: Tokenizer & TokenizerThis, src: string): Tokens.Paragraph | T; - text(this: Tokenizer & TokenizerThis, src: string): Tokens.Text | T; - escape(this: Tokenizer & TokenizerThis, src: string): Tokens.Escape | T; - tag(this: Tokenizer & TokenizerThis, src: string): Tokens.Tag | T; - link(this: Tokenizer & TokenizerThis, src: string): Tokens.Image | Tokens.Link | T; - reflink( - this: Tokenizer & TokenizerThis, - src: string, - links: Tokens.Link[] | Tokens.Image[], - ): Tokens.Link | Tokens.Image | Tokens.Text | T; - emStrong( - this: Tokenizer & TokenizerThis, - src: string, - maskedSrc: string, - prevChar: string, - ): Tokens.Em | Tokens.Strong | T; - codespan(this: Tokenizer & TokenizerThis, src: string): Tokens.Codespan | T; - br(this: Tokenizer & TokenizerThis, src: string): Tokens.Br | T; - del(this: Tokenizer & TokenizerThis, src: string): Tokens.Del | T; - autolink(this: Tokenizer & TokenizerThis, src: string, mangle: (cap: string) => string): Tokens.Link | T; - url(this: Tokenizer & TokenizerThis, src: string, mangle: (cap: string) => string): Tokens.Link | T; - inlineText(this: Tokenizer & TokenizerThis, src: string, smartypants: (cap: string) => string): Tokens.Text | T; - } - - type TokenizerObject = Partial, 'constructor' | 'options'>>; - - class Renderer { - constructor(options?: MarkedOptions); - options: MarkedOptions; - code(this: Renderer | RendererThis, code: string, language: string | undefined, isEscaped: boolean): string | T; - blockquote(this: Renderer | RendererThis, quote: string): string | T; - html(this: Renderer | RendererThis, html: string): string | T; - heading( - this: Renderer | RendererThis, - text: string, - level: 1 | 2 | 3 | 4 | 5 | 6, - raw: string, - slugger: Slugger, - ): string | T; - hr(this: Renderer | RendererThis): string | T; - list(this: Renderer | RendererThis, body: string, ordered: boolean, start: number): string | T; - listitem(this: Renderer | RendererThis, text: string, task: boolean, checked: boolean): string | T; - checkbox(this: Renderer | RendererThis, checked: boolean): string | T; - paragraph(this: Renderer | RendererThis, text: string): string | T; - table(this: Renderer | RendererThis, header: string, body: string): string | T; - tablerow(this: Renderer | RendererThis, content: string): string | T; - tablecell( - this: Renderer | RendererThis, - content: string, - flags: { - header: boolean; - align: 'center' | 'left' | 'right' | null; - }, - ): string | T; - strong(this: Renderer | RendererThis, text: string): string | T; - em(this: Renderer | RendererThis, text: string): string | T; - codespan(this: Renderer | RendererThis, code: string): string | T; - br(this: Renderer | RendererThis): string | T; - del(this: Renderer | RendererThis, text: string): string | T; - link(this: Renderer | RendererThis, href: string | null, title: string | null, text: string): string | T; - image(this: Renderer | RendererThis, href: string | null, title: string | null, text: string): string | T; - text(this: Renderer | RendererThis, text: string): string | T; - } - - type RendererObject = Partial, 'constructor' | 'options'>>; - - class TextRenderer { - strong(text: string): string; - em(text: string): string; - codespan(text: string): string; - del(text: string): string; - text(text: string): string; - link(href: string | null, title: string | null, text: string): string; - image(href: string | null, title: string | null, text: string): string; - br(): string; - html(text: string): string; - } - - class Parser { - constructor(options?: MarkedOptions); - tokens: Token[] | TokensList; - token: Token | null; - options: MarkedOptions; - renderer: Renderer; - textRenderer: TextRenderer; - slugger: Slugger; - static parse(src: Token[] | TokensList, options?: MarkedOptions): string; - static parseInline(src: Token[], options?: MarkedOptions): string; - parse(src: Token[] | TokensList): string; - parseInline(src: Token[], renderer?: Renderer): string; - next(): Token; - } - - class Lexer { - constructor(options?: MarkedOptions); - tokens: TokensList; - options: MarkedOptions; - rules: Rules; - static rules: Rules; - static lex(src: string, options?: MarkedOptions): TokensList; - static lexInline(src: string, options?: MarkedOptions): Token[]; - lex(src: string): TokensList; - blockTokens(src: string, tokens: Token[]): Token[]; - blockTokens(src: string, tokens: TokensList): TokensList; - inline(src: string, tokens?: Token[]): Token[]; - inlineTokens(src: string, tokens?: Token[]): Token[]; - state: { - inLink: boolean; - inRawBlock: boolean; - top: boolean; + extensions?: null | { + renderers: { + [name: string]: RendererExtensionFunction; }; - } - - class Slugger { - seen: { [slugValue: string]: number }; - slug(value: string, options?: SluggerOptions): string; - } - - interface SluggerOptions { - dryrun: boolean; - } - - interface Rules { - [ruleName: string]: RegExp | Rules; - } - - type TokensList = Token[] & { - links: { - [key: string]: { href: string | null; title: string | null }; + childTokens: { + [name: string]: string[]; }; + inline?: TokenizerExtensionFunction[]; + block?: TokenizerExtensionFunction[]; + startInline?: TokenizerStartFunction[]; + startBlock?: TokenizerStartFunction[]; }; - - type Token = - | Tokens.Space - | Tokens.Code - | Tokens.Heading - | Tokens.Table - | Tokens.Hr - | Tokens.Blockquote - | Tokens.List - | Tokens.ListItem - | Tokens.Paragraph - | Tokens.HTML - | Tokens.Text - | Tokens.Def - | Tokens.Escape - | Tokens.Tag - | Tokens.Image - | Tokens.Link - | Tokens.Strong - | Tokens.Em - | Tokens.Codespan - | Tokens.Br - | Tokens.Del; - - namespace Tokens { - interface Space { - type: 'space'; - raw: string; - } - - interface Code { - type: 'code'; - raw: string; - codeBlockStyle?: 'indented' | undefined; - lang?: string | undefined; - text: string; - } - - interface Heading { - type: 'heading'; - raw: string; - depth: number; - text: string; - tokens: Token[]; - } - - interface Table { - type: 'table'; - raw: string; - align: Array<'center' | 'left' | 'right' | null>; - header: TableCell[]; - rows: TableCell[][]; - } - - interface TableCell { - text: string; - tokens: Token[]; - } - - interface Hr { - type: 'hr'; - raw: string; - } - - interface Blockquote { - type: 'blockquote'; - raw: string; - text: string; - tokens: Token[]; - } - - interface List { - type: 'list'; - raw: string; - ordered: boolean; - start: number | ''; - loose: boolean; - items: ListItem[]; - } - - interface ListItem { - type: 'list_item'; - raw: string; - task: boolean; - checked?: boolean | undefined; - loose: boolean; - text: string; - tokens: Token[]; - } - - interface Paragraph { - type: 'paragraph'; - raw: string; - pre?: boolean | undefined; - text: string; - tokens: Token[]; - } - - interface HTML { - type: 'html'; - raw: string; - pre: boolean; - text: string; - } - - interface Text { - type: 'text'; - raw: string; - text: string; - tokens?: Token[] | undefined; - } - - interface Def { - type: 'def'; - raw: string; - tag: string; - href: string; - title: string; - } - - interface Escape { - type: 'escape'; - raw: string; - text: string; - } - - interface Tag { - type: 'text' | 'html'; - raw: string; - inLink: boolean; - inRawBlock: boolean; - text: string; - } - - interface Link { - type: 'link'; - raw: string; - href: string; - title: string; - text: string; - tokens: Token[]; - } - - interface Image { - type: 'image'; - raw: string; - href: string; - title: string; - text: string; - } - - interface Strong { - type: 'strong'; - raw: string; - text: string; - tokens: Token[]; - } - - interface Em { - type: 'em'; - raw: string; - text: string; - tokens: Token[]; - } - - interface Codespan { - type: 'codespan'; - raw: string; - text: string; - } - - interface Br { - type: 'br'; - raw: string; - } - - interface Del { - type: 'del'; - raw: string; - text: string; - tokens: Token[]; - } - - interface Generic { - [index: string]: any; - type: string; - raw: string; - tokens?: Token[] | undefined; - } - } - - interface TokenizerThis { - lexer: Lexer; - } - - interface TokenizerExtension { - name: string; - level: 'block' | 'inline'; - start?: ((this: TokenizerThis, src: string) => number | void) | undefined; - tokenizer: (this: TokenizerThis, src: string, tokens: Token[] | TokensList) => Tokens.Generic | void; - childTokens?: string[] | undefined; - } - - interface RendererThis { - parser: Parser; - } - - interface RendererExtension { - name: string; - renderer: (this: RendererThis, token: Tokens.Generic) => string | false; - } - - interface MarkedExtension { - /** - * A prefix URL for any relative link. - */ - baseUrl?: string | undefined; - - /** - * Enable GFM line breaks. This option requires the gfm option to be true. - */ - breaks?: boolean | undefined; - - /** - * Add tokenizers and renderers to marked - */ - extensions?: - | Array - | undefined; - - /** - * Enable GitHub flavored markdown. - */ - gfm?: boolean | undefined; - - /** - * Include an id attribute when emitting headings. - */ - headerIds?: boolean | undefined; - - /** - * Set the prefix for header tag ids. - */ - headerPrefix?: string | undefined; - - /** - * A function to highlight code blocks. The function can either be - * synchronous (returning a string) or asynchronous (callback invoked - * with an error if any occurred during highlighting and a string - * if highlighting was successful) - */ - highlight?(code: string, lang: string, callback?: (error: any, code?: string) => void): string | void; - - /** - * Set the prefix for code block classes. - */ - langPrefix?: string | undefined; - - /** - * Mangle autolinks (). - */ - mangle?: boolean | undefined; - - /** - * Conform to obscure parts of markdown.pl as much as possible. Don't fix any of the original markdown bugs or poor behavior. - */ - pedantic?: boolean | undefined; - - /** - * Type: object Default: new Renderer() - * - * An object containing functions to render tokens to HTML. - */ - renderer?: Renderer | RendererObject | undefined; - - /** - * Sanitize the output. Ignore any HTML that has been input. - */ - sanitize?: boolean | undefined; - - /** - * Optionally sanitize found HTML with a sanitizer function. - */ - sanitizer?(html: string): string; - - /** - * Shows an HTML error message when rendering fails. - */ - silent?: boolean | undefined; - - /** - * Use smarter list behavior than the original markdown. May eventually be default with the old behavior moved into pedantic. - */ - smartLists?: boolean | undefined; - - /** - * Use "smart" typograhic punctuation for things like quotes and dashes. - */ - smartypants?: boolean | undefined; - - /** - * The tokenizer defines how to turn markdown text into tokens. - */ - tokenizer?: Tokenizer | TokenizerObject | undefined; - - /** - * The walkTokens function gets called with every token. - * Child tokens are called before moving on to sibling tokens. - * Each token is passed by reference so updates are persisted when passed to the parser. - * The return value of the function is ignored. - */ - walkTokens?: ((token: Token) => void) | undefined; - /** - * Generate closing slash for self-closing tags (
instead of
) - */ - xhtml?: boolean | undefined; - } - - interface MarkedOptions extends Omit { - /** - * Type: object Default: new Renderer() - * - * An object containing functions to render tokens to HTML. - */ - renderer?: Renderer | undefined; - - /** - * The tokenizer defines how to turn markdown text into tokens. - */ - tokenizer?: Tokenizer | undefined; - } + /** + * walkTokens function returns array of values for Promise.all + */ + walkTokens?: null | ((token: Token) => void | Promise | (void | Promise)[]); +} +/** + * Block Lexer + */ +declare class _Lexer { + tokens: TokensList; + options: MarkedOptions; + state: { + inLink: boolean; + inRawBlock: boolean; + top: boolean; + }; + private tokenizer; + private inlineQueue; + constructor(options?: MarkedOptions); + /** + * Expose Rules + */ + static get rules(): { + block: { + normal: { + blockquote: RegExp; + code: RegExp; + def: RegExp; + fences: RegExp; + heading: RegExp; + hr: RegExp; + html: RegExp; + lheading: RegExp; + list: RegExp; + newline: RegExp; + paragraph: RegExp; + table: RegExp; + text: RegExp; + }; + gfm: Record<"code" | "blockquote" | "hr" | "html" | "table" | "text" | "heading" | "list" | "paragraph" | "def" | "fences" | "lheading" | "newline", RegExp>; + pedantic: Record<"code" | "blockquote" | "hr" | "html" | "table" | "text" | "heading" | "list" | "paragraph" | "def" | "fences" | "lheading" | "newline", RegExp>; + }; + inline: { + normal: { + _backpedal: RegExp; + anyPunctuation: RegExp; + autolink: RegExp; + blockSkip: RegExp; + br: RegExp; + code: RegExp; + del: RegExp; + emStrongLDelim: RegExp; + emStrongRDelimAst: RegExp; + emStrongRDelimUnd: RegExp; + escape: RegExp; + link: RegExp; + nolink: RegExp; + punctuation: RegExp; + reflink: RegExp; + reflinkSearch: RegExp; + tag: RegExp; + text: RegExp; + url: RegExp; + }; + gfm: Record<"link" | "code" | "url" | "br" | "del" | "text" | "escape" | "tag" | "reflink" | "autolink" | "nolink" | "_backpedal" | "anyPunctuation" | "blockSkip" | "emStrongLDelim" | "emStrongRDelimAst" | "emStrongRDelimUnd" | "punctuation" | "reflinkSearch", RegExp>; + breaks: Record<"link" | "code" | "url" | "br" | "del" | "text" | "escape" | "tag" | "reflink" | "autolink" | "nolink" | "_backpedal" | "anyPunctuation" | "blockSkip" | "emStrongLDelim" | "emStrongRDelimAst" | "emStrongRDelimUnd" | "punctuation" | "reflinkSearch", RegExp>; + pedantic: Record<"link" | "code" | "url" | "br" | "del" | "text" | "escape" | "tag" | "reflink" | "autolink" | "nolink" | "_backpedal" | "anyPunctuation" | "blockSkip" | "emStrongLDelim" | "emStrongRDelimAst" | "emStrongRDelimUnd" | "punctuation" | "reflinkSearch", RegExp>; + }; + }; + /** + * Static Lex Method + */ + static lex(src: string, options?: MarkedOptions): TokensList; + /** + * Static Lex Inline Method + */ + static lexInline(src: string, options?: MarkedOptions): Token[]; + /** + * Preprocessing + */ + lex(src: string): TokensList; + /** + * Lexing + */ + blockTokens(src: string, tokens?: Token[], lastParagraphClipped?: boolean): Token[]; + blockTokens(src: string, tokens?: TokensList, lastParagraphClipped?: boolean): TokensList; + inline(src: string, tokens?: Token[]): Token[]; + /** + * Lexing/Compiling + */ + inlineTokens(src: string, tokens?: Token[]): Token[]; +} +/** + * Gets the original marked default options. + */ +declare function _getDefaults(): MarkedOptions; +declare let _defaults: MarkedOptions; +export type MaybePromise = void | Promise; +export declare class Marked { + defaults: MarkedOptions; + options: (opt: MarkedOptions) => this; + parse: { + (src: string, options: MarkedOptions & { + async: true; + }): Promise; + (src: string, options: MarkedOptions & { + async: false; + }): string; + (src: string, options?: MarkedOptions | undefined | null): string | Promise; + }; + parseInline: { + (src: string, options: MarkedOptions & { + async: true; + }): Promise; + (src: string, options: MarkedOptions & { + async: false; + }): string; + (src: string, options?: MarkedOptions | undefined | null): string | Promise; + }; + Parser: typeof _Parser; + Renderer: typeof _Renderer; + TextRenderer: typeof _TextRenderer; + Lexer: typeof _Lexer; + Tokenizer: typeof _Tokenizer; + Hooks: typeof _Hooks; + constructor(...args: MarkedExtension[]); + /** + * Run callback for every token + */ + walkTokens(tokens: Token[] | TokensList, callback: (token: Token) => MaybePromise | MaybePromise[]): MaybePromise[]; + use(...args: MarkedExtension[]): this; + setOptions(opt: MarkedOptions): this; + lexer(src: string, options?: MarkedOptions): TokensList; + parser(tokens: Token[], options?: MarkedOptions): string; + private parseMarkdown; + private onError; +} +/** + * Compiles markdown to HTML asynchronously. + * + * @param src String of markdown source to be compiled + * @param options Hash of options, having async: true + * @return Promise of string of compiled HTML + */ +export declare function marked(src: string, options: MarkedOptions & { + async: true; +}): Promise; +/** + * Compiles markdown to HTML. + * + * @param src String of markdown source to be compiled + * @param options Optional hash of options + * @return String of compiled HTML. Will be a Promise of string if async is set to true by any extensions. + */ +export declare function marked(src: string, options: MarkedOptions & { + async: false; +}): string; +export declare function marked(src: string, options: MarkedOptions & { + async: true; +}): Promise; +export declare function marked(src: string, options?: MarkedOptions | undefined | null): string | Promise; +export declare namespace marked { + var options: (options: MarkedOptions) => typeof marked; + var setOptions: (options: MarkedOptions) => typeof marked; + var getDefaults: typeof _getDefaults; + var defaults: MarkedOptions; + var use: (...args: MarkedExtension[]) => typeof marked; + var walkTokens: (tokens: Token[] | TokensList, callback: (token: Token) => MaybePromise | MaybePromise[]) => MaybePromise[]; + var parseInline: { + (src: string, options: MarkedOptions & { + async: true; + }): Promise; + (src: string, options: MarkedOptions & { + async: false; + }): string; + (src: string, options?: MarkedOptions | undefined | null): string | Promise; + }; + var Parser: typeof _Parser; + var parser: typeof _Parser.parse; + var Renderer: typeof _Renderer; + var TextRenderer: typeof _TextRenderer; + var Lexer: typeof _Lexer; + var lexer: typeof _Lexer.lex; + var Tokenizer: typeof _Tokenizer; + var Hooks: typeof _Hooks; + var parse: typeof marked; } +export declare const options: (options: MarkedOptions) => typeof marked; +export declare const setOptions: (options: MarkedOptions) => typeof marked; +export declare const use: (...args: MarkedExtension[]) => typeof marked; +export declare const walkTokens: (tokens: Token[] | TokensList, callback: (token: Token) => MaybePromise | MaybePromise[]) => MaybePromise[]; +export declare const parseInline: { + (src: string, options: MarkedOptions & { + async: true; + }): Promise; + (src: string, options: MarkedOptions & { + async: false; + }): string; + (src: string, options?: MarkedOptions | undefined | null): string | Promise; +}; +export declare const parse: typeof marked; +export declare const parser: typeof _Parser.parse; +export declare const lexer: typeof _Lexer.lex; + +export { + _Hooks as Hooks, + _Lexer as Lexer, + _Parser as Parser, + _Renderer as Renderer, + _TextRenderer as TextRenderer, + _Tokenizer as Tokenizer, + _defaults as defaults, + _getDefaults as getDefaults, +}; + +export { }; diff --git a/patched-vscode/src/vs/base/common/marked/marked.js b/patched-vscode/src/vs/base/common/marked/marked.js index eb8cde82..cfd89402 100644 --- a/patched-vscode/src/vs/base/common/marked/marked.js +++ b/patched-vscode/src/vs/base/common/marked/marked.js @@ -1,6 +1,6 @@ /** - * marked - a markdown parser - * Copyright (c) 2011-2022, Christopher Jeffrey. (MIT Licensed) + * marked v14.0.0 - a markdown parser + * Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed) * https://github.com/markedjs/marked */ @@ -18,3075 +18,2511 @@ // define.amd = true; // ESM-uncomment-end - (function (global, factory) { - typeof define === 'function' && define.amd ? define(['exports'], factory) : - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.marked = {})); -})(this, (function (exports) { 'use strict'; - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - Object.defineProperty(Constructor, "prototype", { - writable: false - }); - return Constructor; - } - - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); - } - - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - - return arr2; - } - - function _createForOfIteratorHelperLoose(o, allowArrayLike) { - var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; - if (it) return (it = it.call(o)).next.bind(it); - - if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { - if (it) o = it; - var i = 0; - return function () { - if (i >= o.length) return { - done: true - }; - return { - done: false, - value: o[i++] - }; - }; - } - - throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); - } - - function getDefaults() { - return { - async: false, - baseUrl: null, - breaks: false, - extensions: null, - gfm: true, - headerIds: true, - headerPrefix: '', - highlight: null, - langPrefix: 'language-', - mangle: true, - pedantic: false, - renderer: null, - sanitize: false, - sanitizer: null, - silent: false, - smartLists: false, - smartypants: false, - tokenizer: null, - walkTokens: null, - xhtml: false - }; - } - exports.defaults = getDefaults(); - function changeDefaults(newDefaults) { - exports.defaults = newDefaults; - } - - /** - * Helpers - */ - var escapeTest = /[&<>"']/; - var escapeReplace = /[&<>"']/g; - var escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; - var escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; - var escapeReplacements = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - - var getEscapeReplacement = function getEscapeReplacement(ch) { - return escapeReplacements[ch]; - }; - - function escape(html, encode) { - if (encode) { - if (escapeTest.test(html)) { - return html.replace(escapeReplace, getEscapeReplacement); - } - } else { - if (escapeTestNoEncode.test(html)) { - return html.replace(escapeReplaceNoEncode, getEscapeReplacement); - } - } - - return html; - } - var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig; - /** - * @param {string} html - */ - - function unescape(html) { - // explicitly match decimal, hex, and named HTML entities - return html.replace(unescapeTest, function (_, n) { - n = n.toLowerCase(); - if (n === 'colon') return ':'; - - if (n.charAt(0) === '#') { - return n.charAt(1) === 'x' ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1)); - } - - return ''; - }); - } - var caret = /(^|[^\[])\^/g; - /** - * @param {string | RegExp} regex - * @param {string} opt - */ - - function edit(regex, opt) { - regex = typeof regex === 'string' ? regex : regex.source; - opt = opt || ''; - var obj = { - replace: function replace(name, val) { - val = val.source || val; - val = val.replace(caret, '$1'); - regex = regex.replace(name, val); - return obj; - }, - getRegex: function getRegex() { - return new RegExp(regex, opt); - } - }; - return obj; - } - var nonWordAndColonTest = /[^\w:]/g; - var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; - /** - * @param {boolean} sanitize - * @param {string} base - * @param {string} href - */ - - function cleanUrl(sanitize, base, href) { - if (sanitize) { - var prot; - - try { - prot = decodeURIComponent(unescape(href)).replace(nonWordAndColonTest, '').toLowerCase(); - } catch (e) { - return null; - } - - if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { - return null; - } - } - - if (base && !originIndependentUrl.test(href)) { - href = resolveUrl(base, href); - } - - try { - href = encodeURI(href).replace(/%25/g, '%'); - } catch (e) { - return null; - } - - return href; - } - var baseUrls = {}; - var justDomain = /^[^:]+:\/*[^/]*$/; - var protocol = /^([^:]+:)[\s\S]*$/; - var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/; - /** - * @param {string} base - * @param {string} href - */ - - function resolveUrl(base, href) { - if (!baseUrls[' ' + base]) { - // we can ignore everything in base after the last slash of its path component, - // but we might need to add _that_ - // https://tools.ietf.org/html/rfc3986#section-3 - if (justDomain.test(base)) { - baseUrls[' ' + base] = base + '/'; - } else { - baseUrls[' ' + base] = rtrim(base, '/', true); - } - } - - base = baseUrls[' ' + base]; - var relativeBase = base.indexOf(':') === -1; - - if (href.substring(0, 2) === '//') { - if (relativeBase) { - return href; - } - - return base.replace(protocol, '$1') + href; - } else if (href.charAt(0) === '/') { - if (relativeBase) { - return href; - } - - return base.replace(domain, '$1') + href; - } else { - return base + href; - } - } - var noopTest = { - exec: function noopTest() {} - }; - function merge(obj) { - var i = 1, - target, - key; - - for (; i < arguments.length; i++) { - target = arguments[i]; - - for (key in target) { - if (Object.prototype.hasOwnProperty.call(target, key)) { - obj[key] = target[key]; - } - } - } - - return obj; - } - function splitCells(tableRow, count) { - // ensure that every cell-delimiting pipe has a space - // before it to distinguish it from an escaped pipe - var row = tableRow.replace(/\|/g, function (match, offset, str) { - var escaped = false, - curr = offset; - - while (--curr >= 0 && str[curr] === '\\') { - escaped = !escaped; - } - - if (escaped) { - // odd number of slashes means | is escaped - // so we leave it alone - return '|'; - } else { - // add space before unescaped | - return ' |'; - } - }), - cells = row.split(/ \|/); - var i = 0; // First/last cell in a row cannot be empty if it has no leading/trailing pipe - - if (!cells[0].trim()) { - cells.shift(); - } - - if (cells.length > 0 && !cells[cells.length - 1].trim()) { - cells.pop(); - } - - if (cells.length > count) { - cells.splice(count); - } else { - while (cells.length < count) { - cells.push(''); - } - } - - for (; i < cells.length; i++) { - // leading or trailing whitespace is ignored per the gfm spec - cells[i] = cells[i].trim().replace(/\\\|/g, '|'); - } - - return cells; - } - /** - * Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). - * /c*$/ is vulnerable to REDOS. - * - * @param {string} str - * @param {string} c - * @param {boolean} invert Remove suffix of non-c chars instead. Default falsey. - */ - - function rtrim(str, c, invert) { - var l = str.length; - - if (l === 0) { - return ''; - } // Length of suffix matching the invert condition. - - - var suffLen = 0; // Step left until we fail to match the invert condition. - - while (suffLen < l) { - var currChar = str.charAt(l - suffLen - 1); - - if (currChar === c && !invert) { - suffLen++; - } else if (currChar !== c && invert) { - suffLen++; - } else { - break; - } - } - - return str.slice(0, l - suffLen); - } - function findClosingBracket(str, b) { - if (str.indexOf(b[1]) === -1) { - return -1; - } - - var l = str.length; - var level = 0, - i = 0; - - for (; i < l; i++) { - if (str[i] === '\\') { - i++; - } else if (str[i] === b[0]) { - level++; - } else if (str[i] === b[1]) { - level--; - - if (level < 0) { - return i; - } - } - } - - return -1; - } - function checkSanitizeDeprecation(opt) { - if (opt && opt.sanitize && !opt.silent) { - console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options'); - } - } // copied from https://stackoverflow.com/a/5450113/806777 - - /** - * @param {string} pattern - * @param {number} count - */ - - function repeatString(pattern, count) { - if (count < 1) { - return ''; - } - - var result = ''; - - while (count > 1) { - if (count & 1) { - result += pattern; - } - - count >>= 1; - pattern += pattern; - } - - return result + pattern; - } - - function outputLink(cap, link, raw, lexer) { - var href = link.href; - var title = link.title ? escape(link.title) : null; - var text = cap[1].replace(/\\([\[\]])/g, '$1'); - - if (cap[0].charAt(0) !== '!') { - lexer.state.inLink = true; - var token = { - type: 'link', - raw: raw, - href: href, - title: title, - text: text, - tokens: lexer.inlineTokens(text) - }; - lexer.state.inLink = false; - return token; - } - - return { - type: 'image', - raw: raw, - href: href, - title: title, - text: escape(text) - }; - } - - function indentCodeCompensation(raw, text) { - var matchIndentToCode = raw.match(/^(\s+)(?:```)/); - - if (matchIndentToCode === null) { - return text; - } - - var indentToCode = matchIndentToCode[1]; - return text.split('\n').map(function (node) { - var matchIndentInNode = node.match(/^\s+/); - - if (matchIndentInNode === null) { - return node; - } - - var indentInNode = matchIndentInNode[0]; - - if (indentInNode.length >= indentToCode.length) { - return node.slice(indentToCode.length); - } - - return node; - }).join('\n'); - } - /** - * Tokenizer - */ - - - var Tokenizer = /*#__PURE__*/function () { - function Tokenizer(options) { - this.options = options || exports.defaults; - } - - var _proto = Tokenizer.prototype; - - _proto.space = function space(src) { - var cap = this.rules.block.newline.exec(src); - - if (cap && cap[0].length > 0) { - return { - type: 'space', - raw: cap[0] - }; - } - }; - - _proto.code = function code(src) { - var cap = this.rules.block.code.exec(src); - - if (cap) { - var text = cap[0].replace(/^ {1,4}/gm, ''); - return { - type: 'code', - raw: cap[0], - codeBlockStyle: 'indented', - text: !this.options.pedantic ? rtrim(text, '\n') : text - }; - } - }; - - _proto.fences = function fences(src) { - var cap = this.rules.block.fences.exec(src); - - if (cap) { - var raw = cap[0]; - var text = indentCodeCompensation(raw, cap[3] || ''); - return { - type: 'code', - raw: raw, - lang: cap[2] ? cap[2].trim() : cap[2], - text: text - }; - } - }; - - _proto.heading = function heading(src) { - var cap = this.rules.block.heading.exec(src); - - if (cap) { - var text = cap[2].trim(); // remove trailing #s - - if (/#$/.test(text)) { - var trimmed = rtrim(text, '#'); - - if (this.options.pedantic) { - text = trimmed.trim(); - } else if (!trimmed || / $/.test(trimmed)) { - // CommonMark requires space before trailing #s - text = trimmed.trim(); - } - } - - return { - type: 'heading', - raw: cap[0], - depth: cap[1].length, - text: text, - tokens: this.lexer.inline(text) - }; - } - }; - - _proto.hr = function hr(src) { - var cap = this.rules.block.hr.exec(src); - - if (cap) { - return { - type: 'hr', - raw: cap[0] - }; - } - }; - - _proto.blockquote = function blockquote(src) { - var cap = this.rules.block.blockquote.exec(src); - - if (cap) { - var text = cap[0].replace(/^ *>[ \t]?/gm, ''); - return { - type: 'blockquote', - raw: cap[0], - tokens: this.lexer.blockTokens(text, []), - text: text - }; - } - }; - - _proto.list = function list(src) { - var cap = this.rules.block.list.exec(src); - - if (cap) { - var raw, istask, ischecked, indent, i, blankLine, endsWithBlankLine, line, nextLine, rawLine, itemContents, endEarly; - var bull = cap[1].trim(); - var isordered = bull.length > 1; - var list = { - type: 'list', - raw: '', - ordered: isordered, - start: isordered ? +bull.slice(0, -1) : '', - loose: false, - items: [] - }; - bull = isordered ? "\\d{1,9}\\" + bull.slice(-1) : "\\" + bull; - - if (this.options.pedantic) { - bull = isordered ? bull : '[*+-]'; - } // Get next list item - - - var itemRegex = new RegExp("^( {0,3}" + bull + ")((?:[\t ][^\\n]*)?(?:\\n|$))"); // Check if current bullet point can start a new List Item - - while (src) { - endEarly = false; - - if (!(cap = itemRegex.exec(src))) { - break; - } - - if (this.rules.block.hr.test(src)) { - // End list if bullet was actually HR (possibly move into itemRegex?) - break; - } - - raw = cap[0]; - src = src.substring(raw.length); - line = cap[2].split('\n', 1)[0]; - nextLine = src.split('\n', 1)[0]; - - if (this.options.pedantic) { - indent = 2; - itemContents = line.trimLeft(); - } else { - indent = cap[2].search(/[^ ]/); // Find first non-space char - - indent = indent > 4 ? 1 : indent; // Treat indented code blocks (> 4 spaces) as having only 1 indent - - itemContents = line.slice(indent); - indent += cap[1].length; - } - - blankLine = false; - - if (!line && /^ *$/.test(nextLine)) { - // Items begin with at most one blank line - raw += nextLine + '\n'; - src = src.substring(nextLine.length + 1); - endEarly = true; - } - - if (!endEarly) { - var nextBulletRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:[*+-]|\\d{1,9}[.)])((?: [^\\n]*)?(?:\\n|$))"); - var hrRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)"); - var fencesBeginRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:```|~~~)"); - var headingBeginRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}#"); // Check if following lines should be included in List Item - - while (src) { - rawLine = src.split('\n', 1)[0]; - line = rawLine; // Re-align to follow commonmark nesting rules - - if (this.options.pedantic) { - line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); - } // End list item if found code fences - - - if (fencesBeginRegex.test(line)) { - break; - } // End list item if found start of new heading - - - if (headingBeginRegex.test(line)) { - break; - } // End list item if found start of new bullet - - - if (nextBulletRegex.test(line)) { - break; - } // Horizontal rule found - - - if (hrRegex.test(src)) { - break; - } - - if (line.search(/[^ ]/) >= indent || !line.trim()) { - // Dedent if possible - itemContents += '\n' + line.slice(indent); - } else if (!blankLine) { - // Until blank line, item doesn't need indentation - itemContents += '\n' + line; - } else { - // Otherwise, improper indentation ends this item - break; - } - - if (!blankLine && !line.trim()) { - // Check if current line is blank - blankLine = true; - } - - raw += rawLine + '\n'; - src = src.substring(rawLine.length + 1); - } - } - - if (!list.loose) { - // If the previous item ended with a blank line, the list is loose - if (endsWithBlankLine) { - list.loose = true; - } else if (/\n *\n *$/.test(raw)) { - endsWithBlankLine = true; - } - } // Check for task list items - - - if (this.options.gfm) { - istask = /^\[[ xX]\] /.exec(itemContents); - - if (istask) { - ischecked = istask[0] !== '[ ] '; - itemContents = itemContents.replace(/^\[[ xX]\] +/, ''); - } - } - - list.items.push({ - type: 'list_item', - raw: raw, - task: !!istask, - checked: ischecked, - loose: false, - text: itemContents - }); - list.raw += raw; - } // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic - - - list.items[list.items.length - 1].raw = raw.trimRight(); - list.items[list.items.length - 1].text = itemContents.trimRight(); - list.raw = list.raw.trimRight(); - var l = list.items.length; // Item child tokens handled here at end because we needed to have the final item to trim it first - - for (i = 0; i < l; i++) { - this.lexer.state.top = false; - list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []); - var spacers = list.items[i].tokens.filter(function (t) { - return t.type === 'space'; - }); - var hasMultipleLineBreaks = spacers.every(function (t) { - var chars = t.raw.split(''); - var lineBreaks = 0; - - for (var _iterator = _createForOfIteratorHelperLoose(chars), _step; !(_step = _iterator()).done;) { - var _char = _step.value; - - if (_char === '\n') { - lineBreaks += 1; - } - - if (lineBreaks > 1) { - return true; - } - } - - return false; - }); - - if (!list.loose && spacers.length && hasMultipleLineBreaks) { - // Having a single line break doesn't mean a list is loose. A single line break is terminating the last list item - list.loose = true; - list.items[i].loose = true; - } - } - - return list; - } - }; - - _proto.html = function html(src) { - var cap = this.rules.block.html.exec(src); - - if (cap) { - var token = { - type: 'html', - raw: cap[0], - pre: !this.options.sanitizer && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), - text: cap[0] - }; - - if (this.options.sanitize) { - var text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]); - token.type = 'paragraph'; - token.text = text; - token.tokens = this.lexer.inline(text); - } - - return token; - } - }; - - _proto.def = function def(src) { - var cap = this.rules.block.def.exec(src); - - if (cap) { - if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); - var tag = cap[1].toLowerCase().replace(/\s+/g, ' '); - return { - type: 'def', - tag: tag, - raw: cap[0], - href: cap[2], - title: cap[3] - }; - } - }; - - _proto.table = function table(src) { - var cap = this.rules.block.table.exec(src); - - if (cap) { - var item = { - type: 'table', - header: splitCells(cap[1]).map(function (c) { - return { - text: c - }; - }), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - rows: cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - item.raw = cap[0]; - var l = item.align.length; - var i, j, k, row; - - for (i = 0; i < l; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - l = item.rows.length; - - for (i = 0; i < l; i++) { - item.rows[i] = splitCells(item.rows[i], item.header.length).map(function (c) { - return { - text: c - }; - }); - } // parse child tokens inside headers and cells - // header child tokens - - - l = item.header.length; - - for (j = 0; j < l; j++) { - item.header[j].tokens = this.lexer.inline(item.header[j].text); - } // cell child tokens - - - l = item.rows.length; - - for (j = 0; j < l; j++) { - row = item.rows[j]; - - for (k = 0; k < row.length; k++) { - row[k].tokens = this.lexer.inline(row[k].text); - } - } - - return item; - } - } - }; - - _proto.lheading = function lheading(src) { - var cap = this.rules.block.lheading.exec(src); - - if (cap) { - return { - type: 'heading', - raw: cap[0], - depth: cap[2].charAt(0) === '=' ? 1 : 2, - text: cap[1], - tokens: this.lexer.inline(cap[1]) - }; - } - }; - - _proto.paragraph = function paragraph(src) { - var cap = this.rules.block.paragraph.exec(src); - - if (cap) { - var text = cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]; - return { - type: 'paragraph', - raw: cap[0], - text: text, - tokens: this.lexer.inline(text) - }; - } - }; - - _proto.text = function text(src) { - var cap = this.rules.block.text.exec(src); - - if (cap) { - return { - type: 'text', - raw: cap[0], - text: cap[0], - tokens: this.lexer.inline(cap[0]) - }; - } - }; - - _proto.escape = function escape$1(src) { - var cap = this.rules.inline.escape.exec(src); - - if (cap) { - return { - type: 'escape', - raw: cap[0], - text: escape(cap[1]) - }; - } - }; - - _proto.tag = function tag(src) { - var cap = this.rules.inline.tag.exec(src); - - if (cap) { - if (!this.lexer.state.inLink && /^/i.test(cap[0])) { - this.lexer.state.inLink = false; - } - - if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - this.lexer.state.inRawBlock = true; - } else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - this.lexer.state.inRawBlock = false; - } - - return { - type: this.options.sanitize ? 'text' : 'html', - raw: cap[0], - inLink: this.lexer.state.inLink, - inRawBlock: this.lexer.state.inRawBlock, - text: this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]) : cap[0] - }; - } - }; - - _proto.link = function link(src) { - var cap = this.rules.inline.link.exec(src); - - if (cap) { - var trimmedUrl = cap[2].trim(); - - if (!this.options.pedantic && /^$/.test(trimmedUrl)) { - return; - } // ending angle bracket cannot be escaped - - - var rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\'); - - if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { - return; - } - } else { - // find closing parenthesis - var lastParenIndex = findClosingBracket(cap[2], '()'); - - if (lastParenIndex > -1) { - var start = cap[0].indexOf('!') === 0 ? 5 : 4; - var linkLen = start + cap[1].length + lastParenIndex; - cap[2] = cap[2].substring(0, lastParenIndex); - cap[0] = cap[0].substring(0, linkLen).trim(); - cap[3] = ''; - } - } - - var href = cap[2]; - var title = ''; - - if (this.options.pedantic) { - // split pedantic href and title - var link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); - - if (link) { - href = link[1]; - title = link[3]; - } - } else { - title = cap[3] ? cap[3].slice(1, -1) : ''; - } - - href = href.trim(); - - if (/^$/.test(trimmedUrl)) { - // pedantic allows starting angle bracket without ending angle bracket - href = href.slice(1); - } else { - href = href.slice(1, -1); - } - } - - return outputLink(cap, { - href: href ? href.replace(this.rules.inline._escapes, '$1') : href, - title: title ? title.replace(this.rules.inline._escapes, '$1') : title - }, cap[0], this.lexer); - } - }; - - _proto.reflink = function reflink(src, links) { - var cap; - - if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) { - var link = (cap[2] || cap[1]).replace(/\s+/g, ' '); - link = links[link.toLowerCase()]; - - if (!link || !link.href) { - var text = cap[0].charAt(0); - return { - type: 'text', - raw: text, - text: text - }; - } - - return outputLink(cap, link, cap[0], this.lexer); - } - }; - - _proto.emStrong = function emStrong(src, maskedSrc, prevChar) { - if (prevChar === void 0) { - prevChar = ''; - } - - var match = this.rules.inline.emStrong.lDelim.exec(src); - if (!match) return; // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well - - if (match[3] && prevChar.match(/(?:[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDF70-\uDF81\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF3B\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDE70-\uDEBE\uDEC0-\uDEC9\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE96\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD837[\uDF00-\uDF1E]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])/)) return; - var nextChar = match[1] || match[2] || ''; - - if (!nextChar || nextChar && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar))) { - var lLength = match[0].length - 1; - var rDelim, - rLength, - delimTotal = lLength, - midDelimTotal = 0; - var endReg = match[0][0] === '*' ? this.rules.inline.emStrong.rDelimAst : this.rules.inline.emStrong.rDelimUnd; - endReg.lastIndex = 0; // Clip maskedSrc to same section of string as src (move to lexer?) - - maskedSrc = maskedSrc.slice(-1 * src.length + lLength); - - while ((match = endReg.exec(maskedSrc)) != null) { - rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6]; - if (!rDelim) continue; // skip single * in __abc*abc__ - - rLength = rDelim.length; - - if (match[3] || match[4]) { - // found another Left Delim - delimTotal += rLength; - continue; - } else if (match[5] || match[6]) { - // either Left or Right Delim - if (lLength % 3 && !((lLength + rLength) % 3)) { - midDelimTotal += rLength; - continue; // CommonMark Emphasis Rules 9-10 - } - } - - delimTotal -= rLength; - if (delimTotal > 0) continue; // Haven't found enough closing delimiters - // Remove extra characters. *a*** -> *a* - - rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); // Create `em` if smallest delimiter has odd char count. *a*** - - if (Math.min(lLength, rLength) % 2) { - var _text = src.slice(1, lLength + match.index + rLength); - - return { - type: 'em', - raw: src.slice(0, lLength + match.index + rLength + 1), - text: _text, - tokens: this.lexer.inlineTokens(_text) - }; - } // Create 'strong' if smallest delimiter has even char count. **a*** - - - var text = src.slice(2, lLength + match.index + rLength - 1); - return { - type: 'strong', - raw: src.slice(0, lLength + match.index + rLength + 1), - text: text, - tokens: this.lexer.inlineTokens(text) - }; - } - } - }; - - _proto.codespan = function codespan(src) { - var cap = this.rules.inline.code.exec(src); - - if (cap) { - var text = cap[2].replace(/\n/g, ' '); - var hasNonSpaceChars = /[^ ]/.test(text); - var hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text); - - if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) { - text = text.substring(1, text.length - 1); - } - - text = escape(text, true); - return { - type: 'codespan', - raw: cap[0], - text: text - }; - } - }; - - _proto.br = function br(src) { - var cap = this.rules.inline.br.exec(src); - - if (cap) { - return { - type: 'br', - raw: cap[0] - }; - } - }; - - _proto.del = function del(src) { - var cap = this.rules.inline.del.exec(src); - - if (cap) { - return { - type: 'del', - raw: cap[0], - text: cap[2], - tokens: this.lexer.inlineTokens(cap[2]) - }; - } - }; - - _proto.autolink = function autolink(src, mangle) { - var cap = this.rules.inline.autolink.exec(src); - - if (cap) { - var text, href; - - if (cap[2] === '@') { - text = escape(this.options.mangle ? mangle(cap[1]) : cap[1]); - href = 'mailto:' + text; - } else { - text = escape(cap[1]); - href = text; - } - - return { - type: 'link', - raw: cap[0], - text: text, - href: href, - tokens: [{ - type: 'text', - raw: text, - text: text - }] - }; - } - }; - - _proto.url = function url(src, mangle) { - var cap; - - if (cap = this.rules.inline.url.exec(src)) { - var text, href; - - if (cap[2] === '@') { - text = escape(this.options.mangle ? mangle(cap[0]) : cap[0]); - href = 'mailto:' + text; - } else { - // do extended autolink path validation - var prevCapZero; - - do { - prevCapZero = cap[0]; - cap[0] = this.rules.inline._backpedal.exec(cap[0])[0]; - } while (prevCapZero !== cap[0]); - - text = escape(cap[0]); - - if (cap[1] === 'www.') { - href = 'http://' + text; - } else { - href = text; - } - } - - return { - type: 'link', - raw: cap[0], - text: text, - href: href, - tokens: [{ - type: 'text', - raw: text, - text: text - }] - }; - } - }; - - _proto.inlineText = function inlineText(src, smartypants) { - var cap = this.rules.inline.text.exec(src); - - if (cap) { - var text; - - if (this.lexer.state.inRawBlock) { - text = this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]) : cap[0]; - } else { - text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]); - } - - return { - type: 'text', - raw: cap[0], - text: text - }; - } - }; - - return Tokenizer; - }(); - - /** - * Block-Level Grammar - */ - - var block = { - newline: /^(?: *(?:\n|$))+/, - code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/, - fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/, - hr: /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/, - heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/, - blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/, - html: '^ {0,3}(?:' // optional indentation - + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) - + '|comment[^\\n]*(\\n+|$)' // (2) - + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3) - + '|\\n*|$)' // (4) - + '|\\n*|$)' // (5) - + '|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (6) - + '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) open tag - + '|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) closing tag - + ')', - def: /^ {0,3}\[(label)\]: *(?:\n *)?]+)>?(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/, - table: noopTest, - lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/, - // regex template, placeholders will be replaced according to different paragraph - // interruption rules of commonmark and the original markdown spec: - _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/, - text: /^[^\n]+/ - }; - block._label = /(?!\s*\])(?:\\.|[^\[\]\\])+/; - block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; - block.def = edit(block.def).replace('label', block._label).replace('title', block._title).getRegex(); - block.bullet = /(?:[*+-]|\d{1,9}[.)])/; - block.listItemStart = edit(/^( *)(bull) */).replace('bull', block.bullet).getRegex(); - block.list = edit(block.list).replace(/bull/g, block.bullet).replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))').replace('def', '\\n+(?=' + block.def.source + ')').getRegex(); - block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + '|track|ul'; - block._comment = /|$)/; - block.html = edit(block.html, 'i').replace('comment', block._comment).replace('tag', block._tag).replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); - block.paragraph = edit(block._paragraph).replace('hr', block.hr).replace('heading', ' {0,3}#{1,6} ').replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs - .replace('|table', '').replace('blockquote', ' {0,3}>').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|textarea|!--)').replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - block.blockquote = edit(block.blockquote).replace('paragraph', block.paragraph).getRegex(); - /** - * Normal Block Grammar - */ - - block.normal = merge({}, block); - /** - * GFM Block Grammar - */ - - block.gfm = merge({}, block.normal, { - table: '^ *([^\\n ].*\\|.*)\\n' // Header - + ' {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)(?:\\| *)?' // Align - + '(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells - - }); - block.gfm.table = edit(block.gfm.table).replace('hr', block.hr).replace('heading', ' {0,3}#{1,6} ').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|textarea|!--)').replace('tag', block._tag) // tables can be interrupted by type (6) html blocks - .getRegex(); - block.gfm.paragraph = edit(block._paragraph).replace('hr', block.hr).replace('heading', ' {0,3}#{1,6} ').replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs - .replace('table', block.gfm.table) // interrupt paragraphs with table - .replace('blockquote', ' {0,3}>').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|textarea|!--)').replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - /** - * Pedantic grammar (original John Gruber's loose markdown specification) - */ - - block.pedantic = merge({}, block.normal, { - html: edit('^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag - + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))').replace('comment', block._comment).replace(/tag/g, '(?!(?:' + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b').getRegex(), - def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, - heading: /^(#{1,6})(.*)(?:\n+|$)/, - fences: noopTest, - // fences not supported - paragraph: edit(block.normal._paragraph).replace('hr', block.hr).replace('heading', ' *#{1,6} *[^\n]').replace('lheading', block.lheading).replace('blockquote', ' {0,3}>').replace('|fences', '').replace('|list', '').replace('|html', '').getRegex() - }); - /** - * Inline-Level Grammar - */ - - var inline = { - escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, - autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, - url: noopTest, - tag: '^comment' + '|^' // self-closing tag - + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag - + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. - + '|^' // declaration, e.g. - + '|^', - // CDATA section - link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/, - reflink: /^!?\[(label)\]\[(ref)\]/, - nolink: /^!?\[(ref)\](?:\[\])?/, - reflinkSearch: 'reflink|nolink(?!\\()', - emStrong: { - lDelim: /^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/, - // (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right. - // () Skip orphan inside strong () Consume to delim (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a - rDelimAst: /^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[^*]+(?=[^*])|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/, - rDelimUnd: /^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[^_]+(?=[^_])|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _ - - }, - code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, - br: /^( {2,}|\\)\n(?!\s*$)/, - del: noopTest, - text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~'; - inline.punctuation = edit(inline.punctuation).replace(/punctuation/g, inline._punctuation).getRegex(); // sequences em should skip over [title](link), `code`, - - inline.blockSkip = /\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g; - inline.escapedEmSt = /\\\*|\\_/g; - inline._comment = edit(block._comment).replace('(?:-->|$)', '-->').getRegex(); - inline.emStrong.lDelim = edit(inline.emStrong.lDelim).replace(/punct/g, inline._punctuation).getRegex(); - inline.emStrong.rDelimAst = edit(inline.emStrong.rDelimAst, 'g').replace(/punct/g, inline._punctuation).getRegex(); - inline.emStrong.rDelimUnd = edit(inline.emStrong.rDelimUnd, 'g').replace(/punct/g, inline._punctuation).getRegex(); - inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; - inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; - inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; - inline.autolink = edit(inline.autolink).replace('scheme', inline._scheme).replace('email', inline._email).getRegex(); - inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; - inline.tag = edit(inline.tag).replace('comment', inline._comment).replace('attribute', inline._attribute).getRegex(); - inline._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; - inline._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/; - inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; - inline.link = edit(inline.link).replace('label', inline._label).replace('href', inline._href).replace('title', inline._title).getRegex(); - inline.reflink = edit(inline.reflink).replace('label', inline._label).replace('ref', block._label).getRegex(); - inline.nolink = edit(inline.nolink).replace('ref', block._label).getRegex(); - inline.reflinkSearch = edit(inline.reflinkSearch, 'g').replace('reflink', inline.reflink).replace('nolink', inline.nolink).getRegex(); - /** - * Normal Inline Grammar - */ - - inline.normal = merge({}, inline); - /** - * Pedantic Inline Grammar - */ - - inline.pedantic = merge({}, inline.normal, { - strong: { - start: /^__|\*\*/, - middle: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, - endAst: /\*\*(?!\*)/g, - endUnd: /__(?!_)/g - }, - em: { - start: /^_|\*/, - middle: /^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/, - endAst: /\*(?!\*)/g, - endUnd: /_(?!_)/g - }, - link: edit(/^!?\[(label)\]\((.*?)\)/).replace('label', inline._label).getRegex(), - reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace('label', inline._label).getRegex() - }); - /** - * GFM Inline Grammar - */ - - inline.gfm = merge({}, inline.normal, { - escape: edit(inline.escape).replace('])', '~|])').getRegex(), - _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/, - url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, - _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, - del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/, - text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\ 0.5) { - ch = 'x' + ch.toString(16); - } - - out += '&#' + ch + ';'; - } - - return out; - } - /** - * Block Lexer - */ - - - var Lexer = /*#__PURE__*/function () { - function Lexer(options) { - this.tokens = []; - this.tokens.links = Object.create(null); - this.options = options || exports.defaults; - this.options.tokenizer = this.options.tokenizer || new Tokenizer(); - this.tokenizer = this.options.tokenizer; - this.tokenizer.options = this.options; - this.tokenizer.lexer = this; - this.inlineQueue = []; - this.state = { - inLink: false, - inRawBlock: false, - top: true - }; - var rules = { - block: block.normal, - inline: inline.normal - }; - - if (this.options.pedantic) { - rules.block = block.pedantic; - rules.inline = inline.pedantic; - } else if (this.options.gfm) { - rules.block = block.gfm; - - if (this.options.breaks) { - rules.inline = inline.breaks; - } else { - rules.inline = inline.gfm; - } - } - - this.tokenizer.rules = rules; - } - /** - * Expose Rules - */ - - - /** - * Static Lex Method - */ - Lexer.lex = function lex(src, options) { - var lexer = new Lexer(options); - return lexer.lex(src); - } - /** - * Static Lex Inline Method - */ - ; - - Lexer.lexInline = function lexInline(src, options) { - var lexer = new Lexer(options); - return lexer.inlineTokens(src); - } - /** - * Preprocessing - */ - ; - - var _proto = Lexer.prototype; - - _proto.lex = function lex(src) { - src = src.replace(/\r\n|\r/g, '\n'); - this.blockTokens(src, this.tokens); - var next; - - while (next = this.inlineQueue.shift()) { - this.inlineTokens(next.src, next.tokens); - } - - return this.tokens; - } - /** - * Lexing - */ - ; - - _proto.blockTokens = function blockTokens(src, tokens) { - var _this = this; - - if (tokens === void 0) { - tokens = []; - } - - if (this.options.pedantic) { - src = src.replace(/\t/g, ' ').replace(/^ +$/gm, ''); - } else { - src = src.replace(/^( *)(\t+)/gm, function (_, leading, tabs) { - return leading + ' '.repeat(tabs.length); - }); - } - - var token, lastToken, cutSrc, lastParagraphClipped; - - while (src) { - if (this.options.extensions && this.options.extensions.block && this.options.extensions.block.some(function (extTokenizer) { - if (token = extTokenizer.call({ - lexer: _this - }, src, tokens)) { - src = src.substring(token.raw.length); - tokens.push(token); - return true; - } - - return false; - })) { - continue; - } // newline - - - if (token = this.tokenizer.space(src)) { - src = src.substring(token.raw.length); - - if (token.raw.length === 1 && tokens.length > 0) { - // if there's a single \n as a spacer, it's terminating the last line, - // so move it there so that we don't get unecessary paragraph tags - tokens[tokens.length - 1].raw += '\n'; - } else { - tokens.push(token); - } - - continue; - } // code - - - if (token = this.tokenizer.code(src)) { - src = src.substring(token.raw.length); - lastToken = tokens[tokens.length - 1]; // An indented code block cannot interrupt a paragraph. - - if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) { - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.text; - this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; - } else { - tokens.push(token); - } - - continue; - } // fences - - - if (token = this.tokenizer.fences(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // heading - - - if (token = this.tokenizer.heading(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // hr - - - if (token = this.tokenizer.hr(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // blockquote - - - if (token = this.tokenizer.blockquote(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // list - - - if (token = this.tokenizer.list(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // html - - - if (token = this.tokenizer.html(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // def - - - if (token = this.tokenizer.def(src)) { - src = src.substring(token.raw.length); - lastToken = tokens[tokens.length - 1]; - - if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) { - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.raw; - this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; - } else if (!this.tokens.links[token.tag]) { - this.tokens.links[token.tag] = { - href: token.href, - title: token.title - }; - } - - continue; - } // table (gfm) - - - if (token = this.tokenizer.table(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // lheading - - - if (token = this.tokenizer.lheading(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // top-level paragraph - // prevent paragraph consuming extensions by clipping 'src' to extension start - - - cutSrc = src; - - if (this.options.extensions && this.options.extensions.startBlock) { - (function () { - var startIndex = Infinity; - var tempSrc = src.slice(1); - var tempStart = void 0; - - _this.options.extensions.startBlock.forEach(function (getStartIndex) { - tempStart = getStartIndex.call({ - lexer: this - }, tempSrc); - - if (typeof tempStart === 'number' && tempStart >= 0) { - startIndex = Math.min(startIndex, tempStart); - } - }); - - if (startIndex < Infinity && startIndex >= 0) { - cutSrc = src.substring(0, startIndex + 1); - } - })(); - } - - if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) { - lastToken = tokens[tokens.length - 1]; - - if (lastParagraphClipped && lastToken.type === 'paragraph') { - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.text; - this.inlineQueue.pop(); - this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; - } else { - tokens.push(token); - } - - lastParagraphClipped = cutSrc.length !== src.length; - src = src.substring(token.raw.length); - continue; - } // text - - - if (token = this.tokenizer.text(src)) { - src = src.substring(token.raw.length); - lastToken = tokens[tokens.length - 1]; - - if (lastToken && lastToken.type === 'text') { - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.text; - this.inlineQueue.pop(); - this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; - } else { - tokens.push(token); - } - - continue; - } - - if (src) { - var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); - - if (this.options.silent) { - console.error(errMsg); - break; - } else { - throw new Error(errMsg); - } - } - } - - this.state.top = true; - return tokens; - }; - - _proto.inline = function inline(src, tokens) { - if (tokens === void 0) { - tokens = []; - } - - this.inlineQueue.push({ - src: src, - tokens: tokens - }); - return tokens; - } - /** - * Lexing/Compiling - */ - ; - - _proto.inlineTokens = function inlineTokens(src, tokens) { - var _this2 = this; - - if (tokens === void 0) { - tokens = []; - } - - var token, lastToken, cutSrc; // String with links masked to avoid interference with em and strong - - var maskedSrc = src; - var match; - var keepPrevChar, prevChar; // Mask out reflinks - - if (this.tokens.links) { - var links = Object.keys(this.tokens.links); - - if (links.length > 0) { - while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) { - if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) { - maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex); - } - } - } - } // Mask out other blocks - - - while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) { - maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); - } // Mask out escaped em & strong delimiters - - - while ((match = this.tokenizer.rules.inline.escapedEmSt.exec(maskedSrc)) != null) { - maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex); - } - - while (src) { - if (!keepPrevChar) { - prevChar = ''; - } - - keepPrevChar = false; // extensions - - if (this.options.extensions && this.options.extensions.inline && this.options.extensions.inline.some(function (extTokenizer) { - if (token = extTokenizer.call({ - lexer: _this2 - }, src, tokens)) { - src = src.substring(token.raw.length); - tokens.push(token); - return true; - } - - return false; - })) { - continue; - } // escape - - - if (token = this.tokenizer.escape(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // tag - - - if (token = this.tokenizer.tag(src)) { - src = src.substring(token.raw.length); - lastToken = tokens[tokens.length - 1]; - - if (lastToken && token.type === 'text' && lastToken.type === 'text') { - lastToken.raw += token.raw; - lastToken.text += token.text; - } else { - tokens.push(token); - } - - continue; - } // link - - - if (token = this.tokenizer.link(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // reflink, nolink - - - if (token = this.tokenizer.reflink(src, this.tokens.links)) { - src = src.substring(token.raw.length); - lastToken = tokens[tokens.length - 1]; - - if (lastToken && token.type === 'text' && lastToken.type === 'text') { - lastToken.raw += token.raw; - lastToken.text += token.text; - } else { - tokens.push(token); - } - - continue; - } // em & strong - - - if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // code - - - if (token = this.tokenizer.codespan(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // br - - - if (token = this.tokenizer.br(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // del (gfm) - - - if (token = this.tokenizer.del(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // autolink - - - if (token = this.tokenizer.autolink(src, mangle)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // url (gfm) - - - if (!this.state.inLink && (token = this.tokenizer.url(src, mangle))) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } // text - // prevent inlineText consuming extensions by clipping 'src' to extension start - - - cutSrc = src; - - if (this.options.extensions && this.options.extensions.startInline) { - (function () { - var startIndex = Infinity; - var tempSrc = src.slice(1); - var tempStart = void 0; - - _this2.options.extensions.startInline.forEach(function (getStartIndex) { - tempStart = getStartIndex.call({ - lexer: this - }, tempSrc); - - if (typeof tempStart === 'number' && tempStart >= 0) { - startIndex = Math.min(startIndex, tempStart); - } - }); - - if (startIndex < Infinity && startIndex >= 0) { - cutSrc = src.substring(0, startIndex + 1); - } - })(); - } - - if (token = this.tokenizer.inlineText(cutSrc, smartypants)) { - src = src.substring(token.raw.length); - - if (token.raw.slice(-1) !== '_') { - // Track prevChar before string of ____ started - prevChar = token.raw.slice(-1); - } - - keepPrevChar = true; - lastToken = tokens[tokens.length - 1]; - - if (lastToken && lastToken.type === 'text') { - lastToken.raw += token.raw; - lastToken.text += token.text; - } else { - tokens.push(token); - } - - continue; - } - - if (src) { - var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); - - if (this.options.silent) { - console.error(errMsg); - break; - } else { - throw new Error(errMsg); - } - } - } - - return tokens; - }; - - _createClass(Lexer, null, [{ - key: "rules", - get: function get() { - return { - block: block, - inline: inline - }; - } - }]); - - return Lexer; - }(); - - /** - * Renderer - */ - - var Renderer = /*#__PURE__*/function () { - function Renderer(options) { - this.options = options || exports.defaults; - } - - var _proto = Renderer.prototype; - - _proto.code = function code(_code, infostring, escaped) { - var lang = (infostring || '').match(/\S*/)[0]; - - if (this.options.highlight) { - var out = this.options.highlight(_code, lang); - - if (out != null && out !== _code) { - escaped = true; - _code = out; - } - } - - _code = _code.replace(/\n$/, '') + '\n'; - - if (!lang) { - return '
' + (escaped ? _code : escape(_code, true)) + '
\n'; - } - - return '
' + (escaped ? _code : escape(_code, true)) + '
\n'; - } - /** - * @param {string} quote - */ - ; - - _proto.blockquote = function blockquote(quote) { - return "
\n" + quote + "
\n"; - }; - - _proto.html = function html(_html) { - return _html; - } - /** - * @param {string} text - * @param {string} level - * @param {string} raw - * @param {any} slugger - */ - ; - - _proto.heading = function heading(text, level, raw, slugger) { - if (this.options.headerIds) { - var id = this.options.headerPrefix + slugger.slug(raw); - return "" + text + "\n"; - } // ignore IDs - - - return "" + text + "\n"; - }; - - _proto.hr = function hr() { - return this.options.xhtml ? '
\n' : '
\n'; - }; - - _proto.list = function list(body, ordered, start) { - var type = ordered ? 'ol' : 'ul', - startatt = ordered && start !== 1 ? ' start="' + start + '"' : ''; - return '<' + type + startatt + '>\n' + body + '\n'; - } - /** - * @param {string} text - */ - ; - - _proto.listitem = function listitem(text) { - return "
  • " + text + "
  • \n"; - }; - - _proto.checkbox = function checkbox(checked) { - return ' '; - } - /** - * @param {string} text - */ - ; - - _proto.paragraph = function paragraph(text) { - return "

    " + text + "

    \n"; - } - /** - * @param {string} header - * @param {string} body - */ - ; - - _proto.table = function table(header, body) { - if (body) body = "" + body + ""; - return '\n' + '\n' + header + '\n' + body + '
    \n'; - } - /** - * @param {string} content - */ - ; - - _proto.tablerow = function tablerow(content) { - return "\n" + content + "\n"; - }; - - _proto.tablecell = function tablecell(content, flags) { - var type = flags.header ? 'th' : 'td'; - var tag = flags.align ? "<" + type + " align=\"" + flags.align + "\">" : "<" + type + ">"; - return tag + content + ("\n"); - } - /** - * span level renderer - * @param {string} text - */ - ; - - _proto.strong = function strong(text) { - return "" + text + ""; - } - /** - * @param {string} text - */ - ; - - _proto.em = function em(text) { - return "" + text + ""; - } - /** - * @param {string} text - */ - ; - - _proto.codespan = function codespan(text) { - return "" + text + ""; - }; - - _proto.br = function br() { - return this.options.xhtml ? '
    ' : '
    '; - } - /** - * @param {string} text - */ - ; - - _proto.del = function del(text) { - return "" + text + ""; - } - /** - * @param {string} href - * @param {string} title - * @param {string} text - */ - ; - - _proto.link = function link(href, title, text) { - href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); - - if (href === null) { - return text; - } - - var out = '
    '; - return out; - } - /** - * @param {string} href - * @param {string} title - * @param {string} text - */ - ; - - _proto.image = function image(href, title, text) { - href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); - - if (href === null) { - return text; - } - - var out = "\""' : '>'; - return out; - }; - - _proto.text = function text(_text) { - return _text; - }; - - return Renderer; - }(); - - /** - * TextRenderer - * returns only the textual part of the token - */ - var TextRenderer = /*#__PURE__*/function () { - function TextRenderer() {} - - var _proto = TextRenderer.prototype; - - // no need for block level renderers - _proto.strong = function strong(text) { - return text; - }; - - _proto.em = function em(text) { - return text; - }; - - _proto.codespan = function codespan(text) { - return text; - }; - - _proto.del = function del(text) { - return text; - }; - - _proto.html = function html(text) { - return text; - }; - - _proto.text = function text(_text) { - return _text; - }; - - _proto.link = function link(href, title, text) { - return '' + text; - }; - - _proto.image = function image(href, title, text) { - return '' + text; - }; - - _proto.br = function br() { - return ''; - }; - - return TextRenderer; - }(); - - /** - * Slugger generates header id - */ - var Slugger = /*#__PURE__*/function () { - function Slugger() { - this.seen = {}; - } - /** - * @param {string} value - */ - - - var _proto = Slugger.prototype; - - _proto.serialize = function serialize(value) { - return value.toLowerCase().trim() // remove html tags - .replace(/<[!\/a-z].*?>/ig, '') // remove unwanted chars - .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '').replace(/\s/g, '-'); - } - /** - * Finds the next safe (unique) slug to use - * @param {string} originalSlug - * @param {boolean} isDryRun - */ - ; - - _proto.getNextSafeSlug = function getNextSafeSlug(originalSlug, isDryRun) { - var slug = originalSlug; - var occurenceAccumulator = 0; - - if (this.seen.hasOwnProperty(slug)) { - occurenceAccumulator = this.seen[originalSlug]; - - do { - occurenceAccumulator++; - slug = originalSlug + '-' + occurenceAccumulator; - } while (this.seen.hasOwnProperty(slug)); - } - - if (!isDryRun) { - this.seen[originalSlug] = occurenceAccumulator; - this.seen[slug] = 0; - } - - return slug; - } - /** - * Convert string to unique id - * @param {object} [options] - * @param {boolean} [options.dryrun] Generates the next unique slug without - * updating the internal accumulator. - */ - ; - - _proto.slug = function slug(value, options) { - if (options === void 0) { - options = {}; - } - - var slug = this.serialize(value); - return this.getNextSafeSlug(slug, options.dryrun); - }; - - return Slugger; - }(); - - /** - * Parsing & Compiling - */ - - var Parser = /*#__PURE__*/function () { - function Parser(options) { - this.options = options || exports.defaults; - this.options.renderer = this.options.renderer || new Renderer(); - this.renderer = this.options.renderer; - this.renderer.options = this.options; - this.textRenderer = new TextRenderer(); - this.slugger = new Slugger(); - } - /** - * Static Parse Method - */ - - - Parser.parse = function parse(tokens, options) { - var parser = new Parser(options); - return parser.parse(tokens); - } - /** - * Static Parse Inline Method - */ - ; - - Parser.parseInline = function parseInline(tokens, options) { - var parser = new Parser(options); - return parser.parseInline(tokens); - } - /** - * Parse Loop - */ - ; - - var _proto = Parser.prototype; - - _proto.parse = function parse(tokens, top) { - if (top === void 0) { - top = true; - } - - var out = '', - i, - j, - k, - l2, - l3, - row, - cell, - header, - body, - token, - ordered, - start, - loose, - itemBody, - item, - checked, - task, - checkbox, - ret; - var l = tokens.length; - - for (i = 0; i < l; i++) { - token = tokens[i]; // Run any renderer extensions - - if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) { - ret = this.options.extensions.renderers[token.type].call({ - parser: this - }, token); - - if (ret !== false || !['space', 'hr', 'heading', 'code', 'table', 'blockquote', 'list', 'html', 'paragraph', 'text'].includes(token.type)) { - out += ret || ''; - continue; - } - } - - switch (token.type) { - case 'space': - { - continue; - } - - case 'hr': - { - out += this.renderer.hr(); - continue; - } - - case 'heading': - { - out += this.renderer.heading(this.parseInline(token.tokens), token.depth, unescape(this.parseInline(token.tokens, this.textRenderer)), this.slugger); - continue; - } - - case 'code': - { - out += this.renderer.code(token.text, token.lang, token.escaped); - continue; - } - - case 'table': - { - header = ''; // header - - cell = ''; - l2 = token.header.length; - - for (j = 0; j < l2; j++) { - cell += this.renderer.tablecell(this.parseInline(token.header[j].tokens), { - header: true, - align: token.align[j] - }); - } - - header += this.renderer.tablerow(cell); - body = ''; - l2 = token.rows.length; - - for (j = 0; j < l2; j++) { - row = token.rows[j]; - cell = ''; - l3 = row.length; - - for (k = 0; k < l3; k++) { - cell += this.renderer.tablecell(this.parseInline(row[k].tokens), { - header: false, - align: token.align[k] - }); - } - - body += this.renderer.tablerow(cell); - } - - out += this.renderer.table(header, body); - continue; - } - - case 'blockquote': - { - body = this.parse(token.tokens); - out += this.renderer.blockquote(body); - continue; - } - - case 'list': - { - ordered = token.ordered; - start = token.start; - loose = token.loose; - l2 = token.items.length; - body = ''; - - for (j = 0; j < l2; j++) { - item = token.items[j]; - checked = item.checked; - task = item.task; - itemBody = ''; - - if (item.task) { - checkbox = this.renderer.checkbox(checked); - - if (loose) { - if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') { - item.tokens[0].text = checkbox + ' ' + item.tokens[0].text; - - if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') { - item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text; - } - } else { - item.tokens.unshift({ - type: 'text', - text: checkbox - }); - } - } else { - itemBody += checkbox; - } - } - - itemBody += this.parse(item.tokens, loose); - body += this.renderer.listitem(itemBody, task, checked); - } - - out += this.renderer.list(body, ordered, start); - continue; - } - - case 'html': - { - // TODO parse inline content if parameter markdown=1 - out += this.renderer.html(token.text); - continue; - } - - case 'paragraph': - { - out += this.renderer.paragraph(this.parseInline(token.tokens)); - continue; - } - - case 'text': - { - body = token.tokens ? this.parseInline(token.tokens) : token.text; - - while (i + 1 < l && tokens[i + 1].type === 'text') { - token = tokens[++i]; - body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text); - } - - out += top ? this.renderer.paragraph(body) : body; - continue; - } - - default: - { - var errMsg = 'Token with "' + token.type + '" type was not found.'; - - if (this.options.silent) { - console.error(errMsg); - return; - } else { - throw new Error(errMsg); - } - } - } - } - - return out; - } - /** - * Parse Inline Tokens - */ - ; - - _proto.parseInline = function parseInline(tokens, renderer) { - renderer = renderer || this.renderer; - var out = '', - i, - token, - ret; - var l = tokens.length; - - for (i = 0; i < l; i++) { - token = tokens[i]; // Run any renderer extensions - - if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) { - ret = this.options.extensions.renderers[token.type].call({ - parser: this - }, token); - - if (ret !== false || !['escape', 'html', 'link', 'image', 'strong', 'em', 'codespan', 'br', 'del', 'text'].includes(token.type)) { - out += ret || ''; - continue; - } - } - - switch (token.type) { - case 'escape': - { - out += renderer.text(token.text); - break; - } - - case 'html': - { - out += renderer.html(token.text); - break; - } - - case 'link': - { - out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer)); - break; - } - - case 'image': - { - out += renderer.image(token.href, token.title, token.text); - break; - } - - case 'strong': - { - out += renderer.strong(this.parseInline(token.tokens, renderer)); - break; - } - - case 'em': - { - out += renderer.em(this.parseInline(token.tokens, renderer)); - break; - } - - case 'codespan': - { - out += renderer.codespan(token.text); - break; - } - - case 'br': - { - out += renderer.br(); - break; - } - - case 'del': - { - out += renderer.del(this.parseInline(token.tokens, renderer)); - break; - } - - case 'text': - { - out += renderer.text(token.text); - break; - } - - default: - { - var errMsg = 'Token with "' + token.type + '" type was not found.'; - - if (this.options.silent) { - console.error(errMsg); - return; - } else { - throw new Error(errMsg); - } - } - } - } - - return out; - }; - - return Parser; - }(); - - /** - * Marked - */ - - function marked(src, opt, callback) { - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - throw new Error('marked(): input parameter is undefined or null'); - } - - if (typeof src !== 'string') { - throw new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected'); - } - - if (typeof opt === 'function') { - callback = opt; - opt = null; - } - - opt = merge({}, marked.defaults, opt || {}); - checkSanitizeDeprecation(opt); - - if (callback) { - var highlight = opt.highlight; - var tokens; - - try { - tokens = Lexer.lex(src, opt); - } catch (e) { - return callback(e); - } - - var done = function done(err) { - var out; - - if (!err) { - try { - if (opt.walkTokens) { - marked.walkTokens(tokens, opt.walkTokens); - } - - out = Parser.parse(tokens, opt); - } catch (e) { - err = e; - } - } - - opt.highlight = highlight; - return err ? callback(err) : callback(null, out); - }; - - if (!highlight || highlight.length < 3) { - return done(); - } - - delete opt.highlight; - if (!tokens.length) return done(); - var pending = 0; - marked.walkTokens(tokens, function (token) { - if (token.type === 'code') { - pending++; - setTimeout(function () { - highlight(token.text, token.lang, function (err, code) { - if (err) { - return done(err); - } - - if (code != null && code !== token.text) { - token.text = code; - token.escaped = true; - } - - pending--; - - if (pending === 0) { - done(); - } - }); - }, 0); - } - }); - - if (pending === 0) { - done(); - } - - return; - } - - function onError(e) { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - - if (opt.silent) { - return '

    An error occurred:

    ' + escape(e.message + '', true) + '
    '; - } - - throw e; - } - - try { - var _tokens = Lexer.lex(src, opt); - - if (opt.walkTokens) { - if (opt.async) { - return Promise.all(marked.walkTokens(_tokens, opt.walkTokens)).then(function () { - return Parser.parse(_tokens, opt); - })["catch"](onError); - } - - marked.walkTokens(_tokens, opt.walkTokens); - } - - return Parser.parse(_tokens, opt); - } catch (e) { - onError(e); - } - } - /** - * Options - */ - - marked.options = marked.setOptions = function (opt) { - merge(marked.defaults, opt); - changeDefaults(marked.defaults); - return marked; - }; - - marked.getDefaults = getDefaults; - marked.defaults = exports.defaults; - /** - * Use Extension - */ - - marked.use = function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - var opts = merge.apply(void 0, [{}].concat(args)); - var extensions = marked.defaults.extensions || { - renderers: {}, - childTokens: {} - }; - var hasExtensions; - args.forEach(function (pack) { - // ==-- Parse "addon" extensions --== // - if (pack.extensions) { - hasExtensions = true; - pack.extensions.forEach(function (ext) { - if (!ext.name) { - throw new Error('extension name required'); - } - - if (ext.renderer) { - // Renderer extensions - var prevRenderer = extensions.renderers ? extensions.renderers[ext.name] : null; - - if (prevRenderer) { - // Replace extension with func to run new extension but fall back if false - extensions.renderers[ext.name] = function () { - for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; - } - - var ret = ext.renderer.apply(this, args); - - if (ret === false) { - ret = prevRenderer.apply(this, args); - } - - return ret; - }; - } else { - extensions.renderers[ext.name] = ext.renderer; - } - } - - if (ext.tokenizer) { - // Tokenizer Extensions - if (!ext.level || ext.level !== 'block' && ext.level !== 'inline') { - throw new Error("extension level must be 'block' or 'inline'"); - } - - if (extensions[ext.level]) { - extensions[ext.level].unshift(ext.tokenizer); - } else { - extensions[ext.level] = [ext.tokenizer]; - } - - if (ext.start) { - // Function to check for start of token - if (ext.level === 'block') { - if (extensions.startBlock) { - extensions.startBlock.push(ext.start); - } else { - extensions.startBlock = [ext.start]; - } - } else if (ext.level === 'inline') { - if (extensions.startInline) { - extensions.startInline.push(ext.start); - } else { - extensions.startInline = [ext.start]; - } - } - } - } - - if (ext.childTokens) { - // Child tokens to be visited by walkTokens - extensions.childTokens[ext.name] = ext.childTokens; - } - }); - } // ==-- Parse "overwrite" extensions --== // - - - if (pack.renderer) { - (function () { - var renderer = marked.defaults.renderer || new Renderer(); - - var _loop = function _loop(prop) { - var prevRenderer = renderer[prop]; // Replace renderer with func to run extension, but fall back if false - - renderer[prop] = function () { - for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { - args[_key3] = arguments[_key3]; - } - - var ret = pack.renderer[prop].apply(renderer, args); - - if (ret === false) { - ret = prevRenderer.apply(renderer, args); - } - - return ret; - }; - }; - - for (var prop in pack.renderer) { - _loop(prop); - } - - opts.renderer = renderer; - })(); - } - - if (pack.tokenizer) { - (function () { - var tokenizer = marked.defaults.tokenizer || new Tokenizer(); - - var _loop2 = function _loop2(prop) { - var prevTokenizer = tokenizer[prop]; // Replace tokenizer with func to run extension, but fall back if false - - tokenizer[prop] = function () { - for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { - args[_key4] = arguments[_key4]; - } - - var ret = pack.tokenizer[prop].apply(tokenizer, args); - - if (ret === false) { - ret = prevTokenizer.apply(tokenizer, args); - } - - return ret; - }; - }; - - for (var prop in pack.tokenizer) { - _loop2(prop); - } - - opts.tokenizer = tokenizer; - })(); - } // ==-- Parse WalkTokens extensions --== // - - - if (pack.walkTokens) { - var _walkTokens = marked.defaults.walkTokens; - - opts.walkTokens = function (token) { - var values = []; - values.push(pack.walkTokens.call(this, token)); - - if (_walkTokens) { - values = values.concat(_walkTokens.call(this, token)); - } - - return values; - }; - } - - if (hasExtensions) { - opts.extensions = extensions; - } - - marked.setOptions(opts); - }); - }; - /** - * Run callback for every token - */ - - - marked.walkTokens = function (tokens, callback) { - var values = []; - - var _loop3 = function _loop3() { - var token = _step.value; - values = values.concat(callback.call(marked, token)); - - switch (token.type) { - case 'table': - { - for (var _iterator2 = _createForOfIteratorHelperLoose(token.header), _step2; !(_step2 = _iterator2()).done;) { - var cell = _step2.value; - values = values.concat(marked.walkTokens(cell.tokens, callback)); - } - - for (var _iterator3 = _createForOfIteratorHelperLoose(token.rows), _step3; !(_step3 = _iterator3()).done;) { - var row = _step3.value; - - for (var _iterator4 = _createForOfIteratorHelperLoose(row), _step4; !(_step4 = _iterator4()).done;) { - var _cell = _step4.value; - values = values.concat(marked.walkTokens(_cell.tokens, callback)); - } - } - - break; - } - - case 'list': - { - values = values.concat(marked.walkTokens(token.items, callback)); - break; - } - - default: - { - if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { - // Walk any extensions - marked.defaults.extensions.childTokens[token.type].forEach(function (childTokens) { - values = values.concat(marked.walkTokens(token[childTokens], callback)); - }); - } else if (token.tokens) { - values = values.concat(marked.walkTokens(token.tokens, callback)); - } - } - } - }; - - for (var _iterator = _createForOfIteratorHelperLoose(tokens), _step; !(_step = _iterator()).done;) { - _loop3(); - } - - return values; - }; - /** - * Parse Inline - * @param {string} src - */ - - - marked.parseInline = function (src, opt) { - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - throw new Error('marked.parseInline(): input parameter is undefined or null'); - } - - if (typeof src !== 'string') { - throw new Error('marked.parseInline(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected'); - } - - opt = merge({}, marked.defaults, opt || {}); - checkSanitizeDeprecation(opt); - - try { - var tokens = Lexer.lexInline(src, opt); - - if (opt.walkTokens) { - marked.walkTokens(tokens, opt.walkTokens); - } - - return Parser.parseInline(tokens, opt); - } catch (e) { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - - if (opt.silent) { - return '

    An error occurred:

    ' + escape(e.message + '', true) + '
    '; - } - - throw e; - } - }; - /** - * Expose - */ - - - marked.Parser = Parser; - marked.parser = Parser.parse; - marked.Renderer = Renderer; - marked.TextRenderer = TextRenderer; - marked.Lexer = Lexer; - marked.lexer = Lexer.lex; - marked.Tokenizer = Tokenizer; - marked.Slugger = Slugger; - marked.parse = marked; - var options = marked.options; - var setOptions = marked.setOptions; - var use = marked.use; - var walkTokens = marked.walkTokens; - var parseInline = marked.parseInline; - var parse = marked; - var parser = Parser.parse; - var lexer = Lexer.lex; - - exports.Lexer = Lexer; - exports.Parser = Parser; - exports.Renderer = Renderer; - exports.Slugger = Slugger; - exports.TextRenderer = TextRenderer; - exports.Tokenizer = Tokenizer; - exports.getDefaults = getDefaults; - exports.lexer = lexer; - exports.marked = marked; - exports.options = options; - exports.parse = parse; - exports.parseInline = parseInline; - exports.parser = parser; - exports.setOptions = setOptions; - exports.use = use; - exports.walkTokens = walkTokens; - - Object.defineProperty(exports, '__esModule', { value: true }); - +(function (global, factory) { + typeof define === 'function' && define.amd ? define(['exports'], factory) : + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.marked = {})); + })(this, (function (exports) { + 'use strict'; + + /** + * Gets the original marked default options. + */ + function _getDefaults() { + return { + async: false, + breaks: false, + extensions: null, + gfm: true, + hooks: null, + pedantic: false, + renderer: null, + silent: false, + tokenizer: null, + walkTokens: null, + }; + } + exports.defaults = _getDefaults(); + function changeDefaults(newDefaults) { + exports.defaults = newDefaults; + } + + /** + * Helpers + */ + const escapeTest = /[&<>"']/; + const escapeReplace = new RegExp(escapeTest.source, 'g'); + const escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/; + const escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, 'g'); + const escapeReplacements = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + const getEscapeReplacement = (ch) => escapeReplacements[ch]; + function escape$1(html, encode) { + if (encode) { + if (escapeTest.test(html)) { + return html.replace(escapeReplace, getEscapeReplacement); + } + } + else { + if (escapeTestNoEncode.test(html)) { + return html.replace(escapeReplaceNoEncode, getEscapeReplacement); + } + } + return html; + } + const caret = /(^|[^\[])\^/g; + function edit(regex, opt) { + let source = typeof regex === 'string' ? regex : regex.source; + opt = opt || ''; + const obj = { + replace: (name, val) => { + let valSource = typeof val === 'string' ? val : val.source; + valSource = valSource.replace(caret, '$1'); + source = source.replace(name, valSource); + return obj; + }, + getRegex: () => { + return new RegExp(source, opt); + }, + }; + return obj; + } + function cleanUrl(href) { + try { + href = encodeURI(href).replace(/%25/g, '%'); + } + catch { + return null; + } + return href; + } + const noopTest = { exec: () => null }; + function splitCells(tableRow, count) { + // ensure that every cell-delimiting pipe has a space + // before it to distinguish it from an escaped pipe + const row = tableRow.replace(/\|/g, (match, offset, str) => { + let escaped = false; + let curr = offset; + while (--curr >= 0 && str[curr] === '\\') + escaped = !escaped; + if (escaped) { + // odd number of slashes means | is escaped + // so we leave it alone + return '|'; + } + else { + // add space before unescaped | + return ' |'; + } + }), cells = row.split(/ \|/); + let i = 0; + // First/last cell in a row cannot be empty if it has no leading/trailing pipe + if (!cells[0].trim()) { + cells.shift(); + } + if (cells.length > 0 && !cells[cells.length - 1].trim()) { + cells.pop(); + } + if (count) { + if (cells.length > count) { + cells.splice(count); + } + else { + while (cells.length < count) + cells.push(''); + } + } + for (; i < cells.length; i++) { + // leading or trailing whitespace is ignored per the gfm spec + cells[i] = cells[i].trim().replace(/\\\|/g, '|'); + } + return cells; + } + /** + * Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). + * /c*$/ is vulnerable to REDOS. + * + * @param str + * @param c + * @param invert Remove suffix of non-c chars instead. Default falsey. + */ + function rtrim(str, c, invert) { + const l = str.length; + if (l === 0) { + return ''; + } + // Length of suffix matching the invert condition. + let suffLen = 0; + // Step left until we fail to match the invert condition. + while (suffLen < l) { + const currChar = str.charAt(l - suffLen - 1); + if (currChar === c && !invert) { + suffLen++; + } + else if (currChar !== c && invert) { + suffLen++; + } + else { + break; + } + } + return str.slice(0, l - suffLen); + } + function findClosingBracket(str, b) { + if (str.indexOf(b[1]) === -1) { + return -1; + } + let level = 0; + for (let i = 0; i < str.length; i++) { + if (str[i] === '\\') { + i++; + } + else if (str[i] === b[0]) { + level++; + } + else if (str[i] === b[1]) { + level--; + if (level < 0) { + return i; + } + } + } + return -1; + } + + function outputLink(cap, link, raw, lexer) { + const href = link.href; + const title = link.title ? escape$1(link.title) : null; + const text = cap[1].replace(/\\([\[\]])/g, '$1'); + if (cap[0].charAt(0) !== '!') { + lexer.state.inLink = true; + const token = { + type: 'link', + raw, + href, + title, + text, + tokens: lexer.inlineTokens(text), + }; + lexer.state.inLink = false; + return token; + } + return { + type: 'image', + raw, + href, + title, + text: escape$1(text), + }; + } + function indentCodeCompensation(raw, text) { + const matchIndentToCode = raw.match(/^(\s+)(?:```)/); + if (matchIndentToCode === null) { + return text; + } + const indentToCode = matchIndentToCode[1]; + return text + .split('\n') + .map(node => { + const matchIndentInNode = node.match(/^\s+/); + if (matchIndentInNode === null) { + return node; + } + const [indentInNode] = matchIndentInNode; + if (indentInNode.length >= indentToCode.length) { + return node.slice(indentToCode.length); + } + return node; + }) + .join('\n'); + } + /** + * Tokenizer + */ + class _Tokenizer { + options; + rules; // set by the lexer + lexer; // set by the lexer + constructor(options) { + this.options = options || exports.defaults; + } + space(src) { + const cap = this.rules.block.newline.exec(src); + if (cap && cap[0].length > 0) { + return { + type: 'space', + raw: cap[0], + }; + } + } + code(src) { + const cap = this.rules.block.code.exec(src); + if (cap) { + const text = cap[0].replace(/^ {1,4}/gm, ''); + return { + type: 'code', + raw: cap[0], + codeBlockStyle: 'indented', + text: !this.options.pedantic + ? rtrim(text, '\n') + : text, + }; + } + } + fences(src) { + const cap = this.rules.block.fences.exec(src); + if (cap) { + const raw = cap[0]; + const text = indentCodeCompensation(raw, cap[3] || ''); + return { + type: 'code', + raw, + lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, '$1') : cap[2], + text, + }; + } + } + heading(src) { + const cap = this.rules.block.heading.exec(src); + if (cap) { + let text = cap[2].trim(); + // remove trailing #s + if (/#$/.test(text)) { + const trimmed = rtrim(text, '#'); + if (this.options.pedantic) { + text = trimmed.trim(); + } + else if (!trimmed || / $/.test(trimmed)) { + // CommonMark requires space before trailing #s + text = trimmed.trim(); + } + } + return { + type: 'heading', + raw: cap[0], + depth: cap[1].length, + text, + tokens: this.lexer.inline(text), + }; + } + } + hr(src) { + const cap = this.rules.block.hr.exec(src); + if (cap) { + return { + type: 'hr', + raw: rtrim(cap[0], '\n'), + }; + } + } + blockquote(src) { + const cap = this.rules.block.blockquote.exec(src); + if (cap) { + let lines = rtrim(cap[0], '\n').split('\n'); + let raw = ''; + let text = ''; + const tokens = []; + while (lines.length > 0) { + let inBlockquote = false; + const currentLines = []; + let i; + for (i = 0; i < lines.length; i++) { + // get lines up to a continuation + if (/^ {0,3}>/.test(lines[i])) { + currentLines.push(lines[i]); + inBlockquote = true; + } + else if (!inBlockquote) { + currentLines.push(lines[i]); + } + else { + break; + } + } + lines = lines.slice(i); + const currentRaw = currentLines.join('\n'); + const currentText = currentRaw + // precede setext continuation with 4 spaces so it isn't a setext + .replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g, '\n $1') + .replace(/^ {0,3}>[ \t]?/gm, ''); + raw = raw ? `${raw}\n${currentRaw}` : currentRaw; + text = text ? `${text}\n${currentText}` : currentText; + // parse blockquote lines as top level tokens + // merge paragraphs if this is a continuation + const top = this.lexer.state.top; + this.lexer.state.top = true; + this.lexer.blockTokens(currentText, tokens, true); + this.lexer.state.top = top; + // if there is no continuation then we are done + if (lines.length === 0) { + break; + } + const lastToken = tokens[tokens.length - 1]; + if (lastToken?.type === 'code') { + // blockquote continuation cannot be preceded by a code block + break; + } + else if (lastToken?.type === 'blockquote') { + // include continuation in nested blockquote + const oldToken = lastToken; + const newText = oldToken.raw + '\n' + lines.join('\n'); + const newToken = this.blockquote(newText); + tokens[tokens.length - 1] = newToken; + raw = raw.substring(0, raw.length - oldToken.raw.length) + newToken.raw; + text = text.substring(0, text.length - oldToken.text.length) + newToken.text; + break; + } + else if (lastToken?.type === 'list') { + // include continuation in nested list + const oldToken = lastToken; + const newText = oldToken.raw + '\n' + lines.join('\n'); + const newToken = this.list(newText); + tokens[tokens.length - 1] = newToken; + raw = raw.substring(0, raw.length - lastToken.raw.length) + newToken.raw; + text = text.substring(0, text.length - oldToken.raw.length) + newToken.raw; + lines = newText.substring(tokens[tokens.length - 1].raw.length).split('\n'); + continue; + } + } + return { + type: 'blockquote', + raw, + tokens, + text, + }; + } + } + list(src) { + let cap = this.rules.block.list.exec(src); + if (cap) { + let bull = cap[1].trim(); + const isordered = bull.length > 1; + const list = { + type: 'list', + raw: '', + ordered: isordered, + start: isordered ? +bull.slice(0, -1) : '', + loose: false, + items: [], + }; + bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`; + if (this.options.pedantic) { + bull = isordered ? bull : '[*+-]'; + } + // Get next list item + const itemRegex = new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`); + let endsWithBlankLine = false; + // Check if current bullet point can start a new List Item + while (src) { + let endEarly = false; + let raw = ''; + let itemContents = ''; + if (!(cap = itemRegex.exec(src))) { + break; + } + if (this.rules.block.hr.test(src)) { // End list if bullet was actually HR (possibly move into itemRegex?) + break; + } + raw = cap[0]; + src = src.substring(raw.length); + let line = cap[2].split('\n', 1)[0].replace(/^\t+/, (t) => ' '.repeat(3 * t.length)); + let nextLine = src.split('\n', 1)[0]; + let blankLine = !line.trim(); + let indent = 0; + if (this.options.pedantic) { + indent = 2; + itemContents = line.trimStart(); + } + else if (blankLine) { + indent = cap[1].length + 1; + } + else { + indent = cap[2].search(/[^ ]/); // Find first non-space char + indent = indent > 4 ? 1 : indent; // Treat indented code blocks (> 4 spaces) as having only 1 indent + itemContents = line.slice(indent); + indent += cap[1].length; + } + if (blankLine && /^ *$/.test(nextLine)) { // Items begin with at most one blank line + raw += nextLine + '\n'; + src = src.substring(nextLine.length + 1); + endEarly = true; + } + if (!endEarly) { + const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`); + const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`); + const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`); + const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`); + // Check if following lines should be included in List Item + while (src) { + const rawLine = src.split('\n', 1)[0]; + nextLine = rawLine; + // Re-align to follow commonmark nesting rules + if (this.options.pedantic) { + nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); + } + // End list item if found code fences + if (fencesBeginRegex.test(nextLine)) { + break; + } + // End list item if found start of new heading + if (headingBeginRegex.test(nextLine)) { + break; + } + // End list item if found start of new bullet + if (nextBulletRegex.test(nextLine)) { + break; + } + // Horizontal rule found + if (hrRegex.test(src)) { + break; + } + if (nextLine.search(/[^ ]/) >= indent || !nextLine.trim()) { // Dedent if possible + itemContents += '\n' + nextLine.slice(indent); + } + else { + // not enough indentation + if (blankLine) { + break; + } + // paragraph continuation unless last line was a different block level element + if (line.search(/[^ ]/) >= 4) { // indented code block + break; + } + if (fencesBeginRegex.test(line)) { + break; + } + if (headingBeginRegex.test(line)) { + break; + } + if (hrRegex.test(line)) { + break; + } + itemContents += '\n' + nextLine; + } + if (!blankLine && !nextLine.trim()) { // Check if current line is blank + blankLine = true; + } + raw += rawLine + '\n'; + src = src.substring(rawLine.length + 1); + line = nextLine.slice(indent); + } + } + if (!list.loose) { + // If the previous item ended with a blank line, the list is loose + if (endsWithBlankLine) { + list.loose = true; + } + else if (/\n *\n *$/.test(raw)) { + endsWithBlankLine = true; + } + } + let istask = null; + let ischecked; + // Check for task list items + if (this.options.gfm) { + istask = /^\[[ xX]\] /.exec(itemContents); + if (istask) { + ischecked = istask[0] !== '[ ] '; + itemContents = itemContents.replace(/^\[[ xX]\] +/, ''); + } + } + list.items.push({ + type: 'list_item', + raw, + task: !!istask, + checked: ischecked, + loose: false, + text: itemContents, + tokens: [], + }); + list.raw += raw; + } + // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic + list.items[list.items.length - 1].raw = list.items[list.items.length - 1].raw.trimEnd(); + list.items[list.items.length - 1].text = list.items[list.items.length - 1].text.trimEnd(); + list.raw = list.raw.trimEnd(); + // Item child tokens handled here at end because we needed to have the final item to trim it first + for (let i = 0; i < list.items.length; i++) { + this.lexer.state.top = false; + list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []); + if (!list.loose) { + // Check if list should be loose + const spacers = list.items[i].tokens.filter(t => t.type === 'space'); + const hasMultipleLineBreaks = spacers.length > 0 && spacers.some(t => /\n.*\n/.test(t.raw)); + list.loose = hasMultipleLineBreaks; + } + } + // Set all items to loose if list is loose + if (list.loose) { + for (let i = 0; i < list.items.length; i++) { + list.items[i].loose = true; + } + } + return list; + } + } + html(src) { + const cap = this.rules.block.html.exec(src); + if (cap) { + const token = { + type: 'html', + block: true, + raw: cap[0], + pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style', + text: cap[0], + }; + return token; + } + } + def(src) { + const cap = this.rules.block.def.exec(src); + if (cap) { + const tag = cap[1].toLowerCase().replace(/\s+/g, ' '); + const href = cap[2] ? cap[2].replace(/^<(.*)>$/, '$1').replace(this.rules.inline.anyPunctuation, '$1') : ''; + const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, '$1') : cap[3]; + return { + type: 'def', + tag, + raw: cap[0], + href, + title, + }; + } + } + table(src) { + const cap = this.rules.block.table.exec(src); + if (!cap) { + return; + } + if (!/[:|]/.test(cap[2])) { + // delimiter row must have a pipe (|) or colon (:) otherwise it is a setext heading + return; + } + const headers = splitCells(cap[1]); + const aligns = cap[2].replace(/^\||\| *$/g, '').split('|'); + const rows = cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : []; + const item = { + type: 'table', + raw: cap[0], + header: [], + align: [], + rows: [], + }; + if (headers.length !== aligns.length) { + // header and align columns must be equal, rows can be different. + return; + } + for (const align of aligns) { + if (/^ *-+: *$/.test(align)) { + item.align.push('right'); + } + else if (/^ *:-+: *$/.test(align)) { + item.align.push('center'); + } + else if (/^ *:-+ *$/.test(align)) { + item.align.push('left'); + } + else { + item.align.push(null); + } + } + for (let i = 0; i < headers.length; i++) { + item.header.push({ + text: headers[i], + tokens: this.lexer.inline(headers[i]), + header: true, + align: item.align[i], + }); + } + for (const row of rows) { + item.rows.push(splitCells(row, item.header.length).map((cell, i) => { + return { + text: cell, + tokens: this.lexer.inline(cell), + header: false, + align: item.align[i], + }; + })); + } + return item; + } + lheading(src) { + const cap = this.rules.block.lheading.exec(src); + if (cap) { + return { + type: 'heading', + raw: cap[0], + depth: cap[2].charAt(0) === '=' ? 1 : 2, + text: cap[1], + tokens: this.lexer.inline(cap[1]), + }; + } + } + paragraph(src) { + const cap = this.rules.block.paragraph.exec(src); + if (cap) { + const text = cap[1].charAt(cap[1].length - 1) === '\n' + ? cap[1].slice(0, -1) + : cap[1]; + return { + type: 'paragraph', + raw: cap[0], + text, + tokens: this.lexer.inline(text), + }; + } + } + text(src) { + const cap = this.rules.block.text.exec(src); + if (cap) { + return { + type: 'text', + raw: cap[0], + text: cap[0], + tokens: this.lexer.inline(cap[0]), + }; + } + } + escape(src) { + const cap = this.rules.inline.escape.exec(src); + if (cap) { + return { + type: 'escape', + raw: cap[0], + text: escape$1(cap[1]), + }; + } + } + tag(src) { + const cap = this.rules.inline.tag.exec(src); + if (cap) { + if (!this.lexer.state.inLink && /^
    /i.test(cap[0])) { + this.lexer.state.inLink = false; + } + if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { + this.lexer.state.inRawBlock = true; + } + else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { + this.lexer.state.inRawBlock = false; + } + return { + type: 'html', + raw: cap[0], + inLink: this.lexer.state.inLink, + inRawBlock: this.lexer.state.inRawBlock, + block: false, + text: cap[0], + }; + } + } + link(src) { + const cap = this.rules.inline.link.exec(src); + if (cap) { + const trimmedUrl = cap[2].trim(); + if (!this.options.pedantic && /^$/.test(trimmedUrl))) { + return; + } + // ending angle bracket cannot be escaped + const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\'); + if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { + return; + } + } + else { + // find closing parenthesis + const lastParenIndex = findClosingBracket(cap[2], '()'); + if (lastParenIndex > -1) { + const start = cap[0].indexOf('!') === 0 ? 5 : 4; + const linkLen = start + cap[1].length + lastParenIndex; + cap[2] = cap[2].substring(0, lastParenIndex); + cap[0] = cap[0].substring(0, linkLen).trim(); + cap[3] = ''; + } + } + let href = cap[2]; + let title = ''; + if (this.options.pedantic) { + // split pedantic href and title + const link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); + if (link) { + href = link[1]; + title = link[3]; + } + } + else { + title = cap[3] ? cap[3].slice(1, -1) : ''; + } + href = href.trim(); + if (/^$/.test(trimmedUrl))) { + // pedantic allows starting angle bracket without ending angle bracket + href = href.slice(1); + } + else { + href = href.slice(1, -1); + } + } + return outputLink(cap, { + href: href ? href.replace(this.rules.inline.anyPunctuation, '$1') : href, + title: title ? title.replace(this.rules.inline.anyPunctuation, '$1') : title, + }, cap[0], this.lexer); + } + } + reflink(src, links) { + let cap; + if ((cap = this.rules.inline.reflink.exec(src)) + || (cap = this.rules.inline.nolink.exec(src))) { + const linkString = (cap[2] || cap[1]).replace(/\s+/g, ' '); + const link = links[linkString.toLowerCase()]; + if (!link) { + const text = cap[0].charAt(0); + return { + type: 'text', + raw: text, + text, + }; + } + return outputLink(cap, link, cap[0], this.lexer); + } + } + emStrong(src, maskedSrc, prevChar = '') { + let match = this.rules.inline.emStrongLDelim.exec(src); + if (!match) + return; + // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well + if (match[3] && prevChar.match(/[\p{L}\p{N}]/u)) + return; + const nextChar = match[1] || match[2] || ''; + if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) { + // unicode Regex counts emoji as 1 char; spread into array for proper count (used multiple times below) + const lLength = [...match[0]].length - 1; + let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0; + const endReg = match[0][0] === '*' ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd; + endReg.lastIndex = 0; + // Clip maskedSrc to same section of string as src (move to lexer?) + maskedSrc = maskedSrc.slice(-1 * src.length + lLength); + while ((match = endReg.exec(maskedSrc)) != null) { + rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6]; + if (!rDelim) + continue; // skip single * in __abc*abc__ + rLength = [...rDelim].length; + if (match[3] || match[4]) { // found another Left Delim + delimTotal += rLength; + continue; + } + else if (match[5] || match[6]) { // either Left or Right Delim + if (lLength % 3 && !((lLength + rLength) % 3)) { + midDelimTotal += rLength; + continue; // CommonMark Emphasis Rules 9-10 + } + } + delimTotal -= rLength; + if (delimTotal > 0) + continue; // Haven't found enough closing delimiters + // Remove extra characters. *a*** -> *a* + rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); + // char length can be >1 for unicode characters; + const lastCharLength = [...match[0]][0].length; + const raw = src.slice(0, lLength + match.index + lastCharLength + rLength); + // Create `em` if smallest delimiter has odd char count. *a*** + if (Math.min(lLength, rLength) % 2) { + const text = raw.slice(1, -1); + return { + type: 'em', + raw, + text, + tokens: this.lexer.inlineTokens(text), + }; + } + // Create 'strong' if smallest delimiter has even char count. **a*** + const text = raw.slice(2, -2); + return { + type: 'strong', + raw, + text, + tokens: this.lexer.inlineTokens(text), + }; + } + } + } + codespan(src) { + const cap = this.rules.inline.code.exec(src); + if (cap) { + let text = cap[2].replace(/\n/g, ' '); + const hasNonSpaceChars = /[^ ]/.test(text); + const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text); + if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) { + text = text.substring(1, text.length - 1); + } + text = escape$1(text, true); + return { + type: 'codespan', + raw: cap[0], + text, + }; + } + } + br(src) { + const cap = this.rules.inline.br.exec(src); + if (cap) { + return { + type: 'br', + raw: cap[0], + }; + } + } + del(src) { + const cap = this.rules.inline.del.exec(src); + if (cap) { + return { + type: 'del', + raw: cap[0], + text: cap[2], + tokens: this.lexer.inlineTokens(cap[2]), + }; + } + } + autolink(src) { + const cap = this.rules.inline.autolink.exec(src); + if (cap) { + let text, href; + if (cap[2] === '@') { + text = escape$1(cap[1]); + href = 'mailto:' + text; + } + else { + text = escape$1(cap[1]); + href = text; + } + return { + type: 'link', + raw: cap[0], + text, + href, + tokens: [ + { + type: 'text', + raw: text, + text, + }, + ], + }; + } + } + url(src) { + let cap; + if (cap = this.rules.inline.url.exec(src)) { + let text, href; + if (cap[2] === '@') { + text = escape$1(cap[0]); + href = 'mailto:' + text; + } + else { + // do extended autolink path validation + let prevCapZero; + do { + prevCapZero = cap[0]; + cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? ''; + } while (prevCapZero !== cap[0]); + text = escape$1(cap[0]); + if (cap[1] === 'www.') { + href = 'http://' + cap[0]; + } + else { + href = cap[0]; + } + } + return { + type: 'link', + raw: cap[0], + text, + href, + tokens: [ + { + type: 'text', + raw: text, + text, + }, + ], + }; + } + } + inlineText(src) { + const cap = this.rules.inline.text.exec(src); + if (cap) { + let text; + if (this.lexer.state.inRawBlock) { + text = cap[0]; + } + else { + text = escape$1(cap[0]); + } + return { + type: 'text', + raw: cap[0], + text, + }; + } + } + } + + /** + * Block-Level Grammar + */ + const newline = /^(?: *(?:\n|$))+/; + const blockCode = /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/; + const fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/; + const hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/; + const heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/; + const bullet = /(?:[*+-]|\d{1,9}[.)])/; + const lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/) + .replace(/bull/g, bullet) // lists can interrupt + .replace(/blockCode/g, / {4}/) // indented code blocks can interrupt + .replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt + .replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt + .replace(/heading/g, / {0,3}#{1,6}/) // ATX heading can interrupt + .replace(/html/g, / {0,3}<[^\n>]+>\n/) // block html can interrupt + .getRegex(); + const _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/; + const blockText = /^[^\n]+/; + const _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/; + const def = edit(/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/) + .replace('label', _blockLabel) + .replace('title', /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/) + .getRegex(); + const list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/) + .replace(/bull/g, bullet) + .getRegex(); + const _tag = 'address|article|aside|base|basefont|blockquote|body|caption' + + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' + + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' + + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + + '|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title' + + '|tr|track|ul'; + const _comment = /|$))/; + const html = edit('^ {0,3}(?:' // optional indentation + + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) + + '|comment[^\\n]*(\\n+|$)' // (2) + + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3) + + '|\\n*|$)' // (4) + + '|\\n*|$)' // (5) + + '|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (6) + + '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) open tag + + '|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) closing tag + + ')', 'i') + .replace('comment', _comment) + .replace('tag', _tag) + .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) + .getRegex(); + const paragraph = edit(_paragraph) + .replace('hr', hr) + .replace('heading', ' {0,3}#{1,6}(?:\\s|$)') + .replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs + .replace('|table', '') + .replace('blockquote', ' {0,3}>') + .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') + .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt + .replace('html', ')|<(?:script|pre|style|textarea|!--)') + .replace('tag', _tag) // pars can be interrupted by type (6) html blocks + .getRegex(); + const blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/) + .replace('paragraph', paragraph) + .getRegex(); + /** + * Normal Block Grammar + */ + const blockNormal = { + blockquote, + code: blockCode, + def, + fences, + heading, + hr, + html, + lheading, + list, + newline, + paragraph, + table: noopTest, + text: blockText, + }; + /** + * GFM Block Grammar + */ + const gfmTable = edit('^ *([^\\n ].*)\\n' // Header + + ' {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)' // Align + + '(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)') // Cells + .replace('hr', hr) + .replace('heading', ' {0,3}#{1,6}(?:\\s|$)') + .replace('blockquote', ' {0,3}>') + .replace('code', ' {4}[^\\n]') + .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') + .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt + .replace('html', ')|<(?:script|pre|style|textarea|!--)') + .replace('tag', _tag) // tables can be interrupted by type (6) html blocks + .getRegex(); + const blockGfm = { + ...blockNormal, + table: gfmTable, + paragraph: edit(_paragraph) + .replace('hr', hr) + .replace('heading', ' {0,3}#{1,6}(?:\\s|$)') + .replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs + .replace('table', gfmTable) // interrupt paragraphs with table + .replace('blockquote', ' {0,3}>') + .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') + .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt + .replace('html', ')|<(?:script|pre|style|textarea|!--)') + .replace('tag', _tag) // pars can be interrupted by type (6) html blocks + .getRegex(), + }; + /** + * Pedantic grammar (original John Gruber's loose markdown specification) + */ + const blockPedantic = { + ...blockNormal, + html: edit('^ *(?:comment *(?:\\n|\\s*$)' + + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag + + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') + .replace('comment', _comment) + .replace(/tag/g, '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') + .getRegex(), + def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, + heading: /^(#{1,6})(.*)(?:\n+|$)/, + fences: noopTest, // fences not supported + lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, + paragraph: edit(_paragraph) + .replace('hr', hr) + .replace('heading', ' *#{1,6} *[^\n]') + .replace('lheading', lheading) + .replace('|table', '') + .replace('blockquote', ' {0,3}>') + .replace('|fences', '') + .replace('|list', '') + .replace('|html', '') + .replace('|tag', '') + .getRegex(), + }; + /** + * Inline-Level Grammar + */ + const escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/; + const inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/; + const br = /^( {2,}|\\)\n(?!\s*$)/; + const inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\ + const blockSkip = /\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g; + const emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, 'u') + .replace(/punct/g, _punctuation) + .getRegex(); + const emStrongRDelimAst = edit('^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)' // Skip orphan inside strong + + '|[^*]+(?=[^*])' // Consume to delim + + '|(?!\\*)[punct](\\*+)(?=[\\s]|$)' // (1) #*** can only be a Right Delimiter + + '|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)' // (2) a***#, a*** can only be a Right Delimiter + + '|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])' // (3) #***a, ***a can only be Left Delimiter + + '|[\\s](\\*+)(?!\\*)(?=[punct])' // (4) ***# can only be Left Delimiter + + '|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])' // (5) #***# can be either Left or Right Delimiter + + '|[^punct\\s](\\*+)(?=[^punct\\s])', 'gu') // (6) a***a can be either Left or Right Delimiter + .replace(/punct/g, _punctuation) + .getRegex(); + // (6) Not allowed for _ + const emStrongRDelimUnd = edit('^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)' // Skip orphan inside strong + + '|[^_]+(?=[^_])' // Consume to delim + + '|(?!_)[punct](_+)(?=[\\s]|$)' // (1) #___ can only be a Right Delimiter + + '|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)' // (2) a___#, a___ can only be a Right Delimiter + + '|(?!_)[punct\\s](_+)(?=[^punct\\s])' // (3) #___a, ___a can only be Left Delimiter + + '|[\\s](_+)(?!_)(?=[punct])' // (4) ___# can only be Left Delimiter + + '|(?!_)[punct](_+)(?!_)(?=[punct])', 'gu') // (5) #___# can be either Left or Right Delimiter + .replace(/punct/g, _punctuation) + .getRegex(); + const anyPunctuation = edit(/\\([punct])/, 'gu') + .replace(/punct/g, _punctuation) + .getRegex(); + const autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/) + .replace('scheme', /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/) + .replace('email', /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/) + .getRegex(); + const _inlineComment = edit(_comment).replace('(?:-->|$)', '-->').getRegex(); + const tag = edit('^comment' + + '|^' // self-closing tag + + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag + + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. + + '|^' // declaration, e.g. + + '|^') // CDATA section + .replace('comment', _inlineComment) + .replace('attribute', /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/) + .getRegex(); + const _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; + const link = edit(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/) + .replace('label', _inlineLabel) + .replace('href', /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/) + .replace('title', /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/) + .getRegex(); + const reflink = edit(/^!?\[(label)\]\[(ref)\]/) + .replace('label', _inlineLabel) + .replace('ref', _blockLabel) + .getRegex(); + const nolink = edit(/^!?\[(ref)\](?:\[\])?/) + .replace('ref', _blockLabel) + .getRegex(); + const reflinkSearch = edit('reflink|nolink(?!\\()', 'g') + .replace('reflink', reflink) + .replace('nolink', nolink) + .getRegex(); + /** + * Normal Inline Grammar + */ + const inlineNormal = { + _backpedal: noopTest, // only used for GFM url + anyPunctuation, + autolink, + blockSkip, + br, + code: inlineCode, + del: noopTest, + emStrongLDelim, + emStrongRDelimAst, + emStrongRDelimUnd, + escape, + link, + nolink, + punctuation, + reflink, + reflinkSearch, + tag, + text: inlineText, + url: noopTest, + }; + /** + * Pedantic Inline Grammar + */ + const inlinePedantic = { + ...inlineNormal, + link: edit(/^!?\[(label)\]\((.*?)\)/) + .replace('label', _inlineLabel) + .getRegex(), + reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) + .replace('label', _inlineLabel) + .getRegex(), + }; + /** + * GFM Inline Grammar + */ + const inlineGfm = { + ...inlineNormal, + escape: edit(escape).replace('])', '~|])').getRegex(), + url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, 'i') + .replace('email', /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/) + .getRegex(), + _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, + del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/, + text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\ { + return leading + ' '.repeat(tabs.length); + }); + } + let token; + let lastToken; + let cutSrc; + while (src) { + if (this.options.extensions + && this.options.extensions.block + && this.options.extensions.block.some((extTokenizer) => { + if (token = extTokenizer.call({ lexer: this }, src, tokens)) { + src = src.substring(token.raw.length); + tokens.push(token); + return true; + } + return false; + })) { + continue; + } + // newline + if (token = this.tokenizer.space(src)) { + src = src.substring(token.raw.length); + if (token.raw.length === 1 && tokens.length > 0) { + // if there's a single \n as a spacer, it's terminating the last line, + // so move it there so that we don't get unnecessary paragraph tags + tokens[tokens.length - 1].raw += '\n'; + } + else { + tokens.push(token); + } + continue; + } + // code + if (token = this.tokenizer.code(src)) { + src = src.substring(token.raw.length); + lastToken = tokens[tokens.length - 1]; + // An indented code block cannot interrupt a paragraph. + if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) { + lastToken.raw += '\n' + token.raw; + lastToken.text += '\n' + token.text; + this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; + } + else { + tokens.push(token); + } + continue; + } + // fences + if (token = this.tokenizer.fences(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // heading + if (token = this.tokenizer.heading(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // hr + if (token = this.tokenizer.hr(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // blockquote + if (token = this.tokenizer.blockquote(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // list + if (token = this.tokenizer.list(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // html + if (token = this.tokenizer.html(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // def + if (token = this.tokenizer.def(src)) { + src = src.substring(token.raw.length); + lastToken = tokens[tokens.length - 1]; + if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) { + lastToken.raw += '\n' + token.raw; + lastToken.text += '\n' + token.raw; + this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; + } + else if (!this.tokens.links[token.tag]) { + this.tokens.links[token.tag] = { + href: token.href, + title: token.title, + }; + } + continue; + } + // table (gfm) + if (token = this.tokenizer.table(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // lheading + if (token = this.tokenizer.lheading(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // top-level paragraph + // prevent paragraph consuming extensions by clipping 'src' to extension start + cutSrc = src; + if (this.options.extensions && this.options.extensions.startBlock) { + let startIndex = Infinity; + const tempSrc = src.slice(1); + let tempStart; + this.options.extensions.startBlock.forEach((getStartIndex) => { + tempStart = getStartIndex.call({ lexer: this }, tempSrc); + if (typeof tempStart === 'number' && tempStart >= 0) { + startIndex = Math.min(startIndex, tempStart); + } + }); + if (startIndex < Infinity && startIndex >= 0) { + cutSrc = src.substring(0, startIndex + 1); + } + } + if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) { + lastToken = tokens[tokens.length - 1]; + if (lastParagraphClipped && lastToken?.type === 'paragraph') { + lastToken.raw += '\n' + token.raw; + lastToken.text += '\n' + token.text; + this.inlineQueue.pop(); + this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; + } + else { + tokens.push(token); + } + lastParagraphClipped = (cutSrc.length !== src.length); + src = src.substring(token.raw.length); + continue; + } + // text + if (token = this.tokenizer.text(src)) { + src = src.substring(token.raw.length); + lastToken = tokens[tokens.length - 1]; + if (lastToken && lastToken.type === 'text') { + lastToken.raw += '\n' + token.raw; + lastToken.text += '\n' + token.text; + this.inlineQueue.pop(); + this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; + } + else { + tokens.push(token); + } + continue; + } + if (src) { + const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); + if (this.options.silent) { + console.error(errMsg); + break; + } + else { + throw new Error(errMsg); + } + } + } + this.state.top = true; + return tokens; + } + inline(src, tokens = []) { + this.inlineQueue.push({ src, tokens }); + return tokens; + } + /** + * Lexing/Compiling + */ + inlineTokens(src, tokens = []) { + let token, lastToken, cutSrc; + // String with links masked to avoid interference with em and strong + let maskedSrc = src; + let match; + let keepPrevChar, prevChar; + // Mask out reflinks + if (this.tokens.links) { + const links = Object.keys(this.tokens.links); + if (links.length > 0) { + while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) { + if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) { + maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex); + } + } + } + } + // Mask out other blocks + while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) { + maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); + } + // Mask out escaped characters + while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) { + maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex); + } + while (src) { + if (!keepPrevChar) { + prevChar = ''; + } + keepPrevChar = false; + // extensions + if (this.options.extensions + && this.options.extensions.inline + && this.options.extensions.inline.some((extTokenizer) => { + if (token = extTokenizer.call({ lexer: this }, src, tokens)) { + src = src.substring(token.raw.length); + tokens.push(token); + return true; + } + return false; + })) { + continue; + } + // escape + if (token = this.tokenizer.escape(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // tag + if (token = this.tokenizer.tag(src)) { + src = src.substring(token.raw.length); + lastToken = tokens[tokens.length - 1]; + if (lastToken && token.type === 'text' && lastToken.type === 'text') { + lastToken.raw += token.raw; + lastToken.text += token.text; + } + else { + tokens.push(token); + } + continue; + } + // link + if (token = this.tokenizer.link(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // reflink, nolink + if (token = this.tokenizer.reflink(src, this.tokens.links)) { + src = src.substring(token.raw.length); + lastToken = tokens[tokens.length - 1]; + if (lastToken && token.type === 'text' && lastToken.type === 'text') { + lastToken.raw += token.raw; + lastToken.text += token.text; + } + else { + tokens.push(token); + } + continue; + } + // em & strong + if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // code + if (token = this.tokenizer.codespan(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // br + if (token = this.tokenizer.br(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // del (gfm) + if (token = this.tokenizer.del(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // autolink + if (token = this.tokenizer.autolink(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // url (gfm) + if (!this.state.inLink && (token = this.tokenizer.url(src))) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + // text + // prevent inlineText consuming extensions by clipping 'src' to extension start + cutSrc = src; + if (this.options.extensions && this.options.extensions.startInline) { + let startIndex = Infinity; + const tempSrc = src.slice(1); + let tempStart; + this.options.extensions.startInline.forEach((getStartIndex) => { + tempStart = getStartIndex.call({ lexer: this }, tempSrc); + if (typeof tempStart === 'number' && tempStart >= 0) { + startIndex = Math.min(startIndex, tempStart); + } + }); + if (startIndex < Infinity && startIndex >= 0) { + cutSrc = src.substring(0, startIndex + 1); + } + } + if (token = this.tokenizer.inlineText(cutSrc)) { + src = src.substring(token.raw.length); + if (token.raw.slice(-1) !== '_') { // Track prevChar before string of ____ started + prevChar = token.raw.slice(-1); + } + keepPrevChar = true; + lastToken = tokens[tokens.length - 1]; + if (lastToken && lastToken.type === 'text') { + lastToken.raw += token.raw; + lastToken.text += token.text; + } + else { + tokens.push(token); + } + continue; + } + if (src) { + const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); + if (this.options.silent) { + console.error(errMsg); + break; + } + else { + throw new Error(errMsg); + } + } + } + return tokens; + } + } + + /** + * Renderer + */ + class _Renderer { + options; + parser; // set by the parser + constructor(options) { + this.options = options || exports.defaults; + } + space(token) { + return ''; + } + code({ text, lang, escaped }) { + const langString = (lang || '').match(/^\S*/)?.[0]; + const code = text.replace(/\n$/, '') + '\n'; + if (!langString) { + return '
    '
    +					+ (escaped ? code : escape$1(code, true))
    +					+ '
    \n'; + } + return '
    '
    +				+ (escaped ? code : escape$1(code, true))
    +				+ '
    \n'; + } + blockquote({ tokens }) { + const body = this.parser.parse(tokens); + return `
    \n${body}
    \n`; + } + html({ text }) { + return text; + } + heading({ tokens, depth }) { + return `${this.parser.parseInline(tokens)}\n`; + } + hr(token) { + return '
    \n'; + } + list(token) { + const ordered = token.ordered; + const start = token.start; + let body = ''; + for (let j = 0; j < token.items.length; j++) { + const item = token.items[j]; + body += this.listitem(item); + } + const type = ordered ? 'ol' : 'ul'; + const startAttr = (ordered && start !== 1) ? (' start="' + start + '"') : ''; + return '<' + type + startAttr + '>\n' + body + '\n'; + } + listitem(item) { + let itemBody = ''; + if (item.task) { + const checkbox = this.checkbox({ checked: !!item.checked }); + if (item.loose) { + if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') { + item.tokens[0].text = checkbox + ' ' + item.tokens[0].text; + if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') { + item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text; + } + } + else { + item.tokens.unshift({ + type: 'text', + raw: checkbox + ' ', + text: checkbox + ' ', + }); + } + } + else { + itemBody += checkbox + ' '; + } + } + itemBody += this.parser.parse(item.tokens, !!item.loose); + return `
  • ${itemBody}
  • \n`; + } + checkbox({ checked }) { + return ''; + } + paragraph({ tokens }) { + return `

    ${this.parser.parseInline(tokens)}

    \n`; + } + table(token) { + let header = ''; + // header + let cell = ''; + for (let j = 0; j < token.header.length; j++) { + cell += this.tablecell(token.header[j]); + } + header += this.tablerow({ text: cell }); + let body = ''; + for (let j = 0; j < token.rows.length; j++) { + const row = token.rows[j]; + cell = ''; + for (let k = 0; k < row.length; k++) { + cell += this.tablecell(row[k]); + } + body += this.tablerow({ text: cell }); + } + if (body) + body = `${body}`; + return '\n' + + '\n' + + header + + '\n' + + body + + '
    \n'; + } + tablerow({ text }) { + return `\n${text}\n`; + } + tablecell(token) { + const content = this.parser.parseInline(token.tokens); + const type = token.header ? 'th' : 'td'; + const tag = token.align + ? `<${type} align="${token.align}">` + : `<${type}>`; + return tag + content + `\n`; + } + /** + * span level renderer + */ + strong({ tokens }) { + return `${this.parser.parseInline(tokens)}`; + } + em({ tokens }) { + return `${this.parser.parseInline(tokens)}`; + } + codespan({ text }) { + return `${text}`; + } + br(token) { + return '
    '; + } + del({ tokens }) { + return `${this.parser.parseInline(tokens)}`; + } + link({ href, title, tokens }) { + const text = this.parser.parseInline(tokens); + const cleanHref = cleanUrl(href); + if (cleanHref === null) { + return text; + } + href = cleanHref; + let out = '
    '; + return out; + } + image({ href, title, text }) { + const cleanHref = cleanUrl(href); + if (cleanHref === null) { + return text; + } + href = cleanHref; + let out = `${text} { + const tokens = genericToken[childTokens].flat(Infinity); + values = values.concat(this.walkTokens(tokens, callback)); + }); + } + else if (genericToken.tokens) { + values = values.concat(this.walkTokens(genericToken.tokens, callback)); + } + } + } + } + return values; + } + use(...args) { + const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} }; + args.forEach((pack) => { + // copy options to new object + const opts = { ...pack }; + // set async to true if it was set to true before + opts.async = this.defaults.async || opts.async || false; + // ==-- Parse "addon" extensions --== // + if (pack.extensions) { + pack.extensions.forEach((ext) => { + if (!ext.name) { + throw new Error('extension name required'); + } + if ('renderer' in ext) { // Renderer extensions + const prevRenderer = extensions.renderers[ext.name]; + if (prevRenderer) { + // Replace extension with func to run new extension but fall back if false + extensions.renderers[ext.name] = function (...args) { + let ret = ext.renderer.apply(this, args); + if (ret === false) { + ret = prevRenderer.apply(this, args); + } + return ret; + }; + } + else { + extensions.renderers[ext.name] = ext.renderer; + } + } + if ('tokenizer' in ext) { // Tokenizer Extensions + if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) { + throw new Error("extension level must be 'block' or 'inline'"); + } + const extLevel = extensions[ext.level]; + if (extLevel) { + extLevel.unshift(ext.tokenizer); + } + else { + extensions[ext.level] = [ext.tokenizer]; + } + if (ext.start) { // Function to check for start of token + if (ext.level === 'block') { + if (extensions.startBlock) { + extensions.startBlock.push(ext.start); + } + else { + extensions.startBlock = [ext.start]; + } + } + else if (ext.level === 'inline') { + if (extensions.startInline) { + extensions.startInline.push(ext.start); + } + else { + extensions.startInline = [ext.start]; + } + } + } + } + if ('childTokens' in ext && ext.childTokens) { // Child tokens to be visited by walkTokens + extensions.childTokens[ext.name] = ext.childTokens; + } + }); + opts.extensions = extensions; + } + // ==-- Parse "overwrite" extensions --== // + if (pack.renderer) { + const renderer = this.defaults.renderer || new _Renderer(this.defaults); + for (const prop in pack.renderer) { + if (!(prop in renderer)) { + throw new Error(`renderer '${prop}' does not exist`); + } + if (['options', 'parser'].includes(prop)) { + // ignore options property + continue; + } + const rendererProp = prop; + const rendererFunc = pack.renderer[rendererProp]; + const prevRenderer = renderer[rendererProp]; + // Replace renderer with func to run extension, but fall back if false + renderer[rendererProp] = (...args) => { + let ret = rendererFunc.apply(renderer, args); + if (ret === false) { + ret = prevRenderer.apply(renderer, args); + } + return ret || ''; + }; + } + opts.renderer = renderer; + } + if (pack.tokenizer) { + const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults); + for (const prop in pack.tokenizer) { + if (!(prop in tokenizer)) { + throw new Error(`tokenizer '${prop}' does not exist`); + } + if (['options', 'rules', 'lexer'].includes(prop)) { + // ignore options, rules, and lexer properties + continue; + } + const tokenizerProp = prop; + const tokenizerFunc = pack.tokenizer[tokenizerProp]; + const prevTokenizer = tokenizer[tokenizerProp]; + // Replace tokenizer with func to run extension, but fall back if false + // @ts-expect-error cannot type tokenizer function dynamically + tokenizer[tokenizerProp] = (...args) => { + let ret = tokenizerFunc.apply(tokenizer, args); + if (ret === false) { + ret = prevTokenizer.apply(tokenizer, args); + } + return ret; + }; + } + opts.tokenizer = tokenizer; + } + // ==-- Parse Hooks extensions --== // + if (pack.hooks) { + const hooks = this.defaults.hooks || new _Hooks(); + for (const prop in pack.hooks) { + if (!(prop in hooks)) { + throw new Error(`hook '${prop}' does not exist`); + } + if (prop === 'options') { + // ignore options property + continue; + } + const hooksProp = prop; + const hooksFunc = pack.hooks[hooksProp]; + const prevHook = hooks[hooksProp]; + if (_Hooks.passThroughHooks.has(prop)) { + // @ts-expect-error cannot type hook function dynamically + hooks[hooksProp] = (arg) => { + if (this.defaults.async) { + return Promise.resolve(hooksFunc.call(hooks, arg)).then(ret => { + return prevHook.call(hooks, ret); + }); + } + const ret = hooksFunc.call(hooks, arg); + return prevHook.call(hooks, ret); + }; + } + else { + // @ts-expect-error cannot type hook function dynamically + hooks[hooksProp] = (...args) => { + let ret = hooksFunc.apply(hooks, args); + if (ret === false) { + ret = prevHook.apply(hooks, args); + } + return ret; + }; + } + } + opts.hooks = hooks; + } + // ==-- Parse WalkTokens extensions --== // + if (pack.walkTokens) { + const walkTokens = this.defaults.walkTokens; + const packWalktokens = pack.walkTokens; + opts.walkTokens = function (token) { + let values = []; + values.push(packWalktokens.call(this, token)); + if (walkTokens) { + values = values.concat(walkTokens.call(this, token)); + } + return values; + }; + } + this.defaults = { ...this.defaults, ...opts }; + }); + return this; + } + setOptions(opt) { + this.defaults = { ...this.defaults, ...opt }; + return this; + } + lexer(src, options) { + return _Lexer.lex(src, options ?? this.defaults); + } + parser(tokens, options) { + return _Parser.parse(tokens, options ?? this.defaults); + } + parseMarkdown(lexer, parser) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parse = (src, options) => { + const origOpt = { ...options }; + const opt = { ...this.defaults, ...origOpt }; + const throwError = this.onError(!!opt.silent, !!opt.async); + // throw error if an extension set async to true but parse was called with async: false + if (this.defaults.async === true && origOpt.async === false) { + return throwError(new Error('marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise.')); + } + // throw error in case of non string input + if (typeof src === 'undefined' || src === null) { + return throwError(new Error('marked(): input parameter is undefined or null')); + } + if (typeof src !== 'string') { + return throwError(new Error('marked(): input parameter is of type ' + + Object.prototype.toString.call(src) + ', string expected')); + } + if (opt.hooks) { + opt.hooks.options = opt; + } + if (opt.async) { + return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src) + .then(src => lexer(src, opt)) + .then(tokens => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens) + .then(tokens => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens) + .then(tokens => parser(tokens, opt)) + .then(html => opt.hooks ? opt.hooks.postprocess(html) : html) + .catch(throwError); + } + try { + if (opt.hooks) { + src = opt.hooks.preprocess(src); + } + let tokens = lexer(src, opt); + if (opt.hooks) { + tokens = opt.hooks.processAllTokens(tokens); + } + if (opt.walkTokens) { + this.walkTokens(tokens, opt.walkTokens); + } + let html = parser(tokens, opt); + if (opt.hooks) { + html = opt.hooks.postprocess(html); + } + return html; + } + catch (e) { + return throwError(e); + } + }; + return parse; + } + onError(silent, async) { + return (e) => { + e.message += '\nPlease report this to https://github.com/markedjs/marked.'; + if (silent) { + const msg = '

    An error occurred:

    '
    +						+ escape$1(e.message + '', true)
    +						+ '
    '; + if (async) { + return Promise.resolve(msg); + } + return msg; + } + if (async) { + return Promise.reject(e); + } + throw e; + }; + } + } + + const markedInstance = new Marked(); + function marked(src, opt) { + return markedInstance.parse(src, opt); + } + /** + * Sets the default options. + * + * @param options Hash of options + */ + marked.options = + marked.setOptions = function (options) { + markedInstance.setOptions(options); + marked.defaults = markedInstance.defaults; + changeDefaults(marked.defaults); + return marked; + }; + /** + * Gets the original marked default options. + */ + marked.getDefaults = _getDefaults; + marked.defaults = exports.defaults; + /** + * Use Extension + */ + marked.use = function (...args) { + markedInstance.use(...args); + marked.defaults = markedInstance.defaults; + changeDefaults(marked.defaults); + return marked; + }; + /** + * Run callback for every token + */ + marked.walkTokens = function (tokens, callback) { + return markedInstance.walkTokens(tokens, callback); + }; + /** + * Compiles markdown to HTML without enclosing `p` tag. + * + * @param src String of markdown source to be compiled + * @param options Hash of options + * @return String of compiled HTML + */ + marked.parseInline = markedInstance.parseInline; + /** + * Expose + */ + marked.Parser = _Parser; + marked.parser = _Parser.parse; + marked.Renderer = _Renderer; + marked.TextRenderer = _TextRenderer; + marked.Lexer = _Lexer; + marked.lexer = _Lexer.lex; + marked.Tokenizer = _Tokenizer; + marked.Hooks = _Hooks; + marked.parse = marked; + const options = marked.options; + const setOptions = marked.setOptions; + const use = marked.use; + const walkTokens = marked.walkTokens; + const parseInline = marked.parseInline; + const parse = marked; + const parser = _Parser.parse; + const lexer = _Lexer.lex; + + exports.Hooks = _Hooks; + exports.Lexer = _Lexer; + exports.Marked = Marked; + exports.Parser = _Parser; + exports.Renderer = _Renderer; + exports.TextRenderer = _TextRenderer; + exports.Tokenizer = _Tokenizer; + exports.getDefaults = _getDefaults; + exports.lexer = lexer; + exports.marked = marked; + exports.options = options; + exports.parse = parse; + exports.parseInline = parseInline; + exports.parser = parser; + exports.setOptions = setOptions; + exports.use = use; + exports.walkTokens = walkTokens; })); // ESM-uncomment-begin // })(); +// export var Hooks = (__marked_exports.Hooks || exports.Hooks); // export var Lexer = (__marked_exports.Lexer || exports.Lexer); +// export var Marked = (__marked_exports.Marked || exports.Marked); // export var Parser = (__marked_exports.Parser || exports.Parser); // export var Renderer = (__marked_exports.Renderer || exports.Renderer); -// export var Slugger = (__marked_exports.Slugger || exports.Slugger); // export var TextRenderer = (__marked_exports.TextRenderer || exports.TextRenderer); // export var Tokenizer = (__marked_exports.Tokenizer || exports.Tokenizer); +// export var defaults = (__marked_exports.defaults || exports.defaults); // export var getDefaults = (__marked_exports.getDefaults || exports.getDefaults); // export var lexer = (__marked_exports.lexer || exports.lexer); // export var marked = (__marked_exports.marked || exports.marked); @@ -3098,3 +2534,5 @@ // export var use = (__marked_exports.use || exports.use); // export var walkTokens = (__marked_exports.walkTokens || exports.walkTokens); // ESM-uncomment-end + +//# sourceMappingURL=marked.umd.js.map diff --git a/patched-vscode/src/vs/base/common/network.ts b/patched-vscode/src/vs/base/common/network.ts index bfa2189e..99fabfb3 100644 --- a/patched-vscode/src/vs/base/common/network.ts +++ b/patched-vscode/src/vs/base/common/network.ts @@ -63,7 +63,10 @@ export namespace Schemas { export const vscodeNotebookCell = 'vscode-notebook-cell'; export const vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata'; + export const vscodeNotebookCellMetadataDiff = 'vscode-notebook-cell-metadata-diff'; export const vscodeNotebookCellOutput = 'vscode-notebook-cell-output'; + export const vscodeNotebookCellOutputDiff = 'vscode-notebook-cell-output-diff'; + export const vscodeNotebookMetadata = 'vscode-notebook-metadata'; export const vscodeInteractiveInput = 'vscode-interactive-input'; export const vscodeSettings = 'vscode-settings'; @@ -75,11 +78,6 @@ export namespace Schemas { /** Scheme used for code blocks in chat. */ export const vscodeChatCodeBlock = 'vscode-chat-code-block'; - /** - * Scheme used for backing documents created by copilot for chat. - */ - export const vscodeCopilotBackingChatCodeBlock = 'vscode-copilot-chat-code-block'; - /** Scheme used for LHS of code compare (aka diff) blocks in chat. */ export const vscodeChatCodeCompareBlock = 'vscode-chat-code-compare-block'; @@ -131,6 +129,11 @@ export namespace Schemas { * Scheme used for special rendering of settings in the release notes */ export const codeSetting = 'code-setting'; + + /** + * Scheme used for output panel resources + */ + export const outputChannel = 'output'; } export function matchesScheme(target: URI | string, scheme: string): boolean { @@ -212,9 +215,7 @@ class RemoteAuthoritiesImpl { return URI.from({ scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, authority: `${host}:${port}`, - path: platform.isWeb - ? (window.location.pathname + "/" + this._remoteResourcesPath).replace(/\/\/+/g, "/") - : this._remoteResourcesPath, + path: this._remoteResourcesPath, query }); } @@ -255,7 +256,12 @@ class FileAccessImpl { * **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context. */ asBrowserUri(resourcePath: AppResourcePath | ''): URI { + // ESM-comment-begin const uri = this.toUri(resourcePath, require); + // ESM-comment-end + // ESM-uncomment-begin + // const uri = this.toUri(resourcePath); + // ESM-uncomment-end return this.uriToBrowserUri(uri); } @@ -302,7 +308,12 @@ class FileAccessImpl { * is responsible for loading. */ asFileUri(resourcePath: AppResourcePath | ''): URI { + // ESM-comment-begin const uri = this.toUri(resourcePath, require); + // ESM-comment-end + // ESM-uncomment-begin + // const uri = this.toUri(resourcePath); + // ESM-uncomment-end return this.uriToFileUri(uri); } @@ -327,12 +338,25 @@ class FileAccessImpl { return uri; } - private toUri(uriOrModule: URI | string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI { + private toUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { if (URI.isUri(uriOrModule)) { return uriOrModule; } - return URI.parse(moduleIdToUrl.toUrl(uriOrModule)); + if (globalThis._VSCODE_FILE_ROOT) { + const rootUriOrPath = globalThis._VSCODE_FILE_ROOT; + + // File URL (with scheme) + if (/^\w[\w\d+.-]*:\/\//.test(rootUriOrPath)) { + return URI.joinPath(URI.parse(rootUriOrPath, true), uriOrModule); + } + + // File Path (no scheme) + const modulePath = paths.join(rootUriOrPath, uriOrModule); + return URI.file(modulePath); + } + + return URI.parse(moduleIdToUrl!.toUrl(uriOrModule)); } } diff --git a/patched-vscode/src/vs/base/common/numbers.ts b/patched-vscode/src/vs/base/common/numbers.ts index 29e65b86..ab4c9f92 100644 --- a/patched-vscode/src/vs/base/common/numbers.ts +++ b/patched-vscode/src/vs/base/common/numbers.ts @@ -69,3 +69,30 @@ export class SlidingWindowAverage { return this._val; } } + +/** Returns whether the point is within the triangle formed by the following 6 x/y point pairs */ +export function isPointWithinTriangle( + x: number, y: number, + ax: number, ay: number, + bx: number, by: number, + cx: number, cy: number +) { + const v0x = cx - ax; + const v0y = cy - ay; + const v1x = bx - ax; + const v1y = by - ay; + const v2x = x - ax; + const v2y = y - ay; + + const dot00 = v0x * v0x + v0y * v0y; + const dot01 = v0x * v1x + v0y * v1y; + const dot02 = v0x * v2x + v0y * v2y; + const dot11 = v1x * v1x + v1y * v1y; + const dot12 = v1x * v2x + v1y * v2y; + + const invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + const u = (dot11 * dot02 - dot01 * dot12) * invDenom; + const v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + return u >= 0 && v >= 0 && u + v < 1; +} diff --git a/patched-vscode/src/vs/base/common/observable.ts b/patched-vscode/src/vs/base/common/observable.ts index a4b21404..c090a272 100644 --- a/patched-vscode/src/vs/base/common/observable.ts +++ b/patched-vscode/src/vs/base/common/observable.ts @@ -60,6 +60,9 @@ export { waitForState, derivedWithCancellationToken, } from 'vs/base/common/observableInternal/promise'; +export { + observableValueOpts +} from 'vs/base/common/observableInternal/api'; import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableInternal/logging'; diff --git a/patched-vscode/src/vs/base/common/observableInternal/api.ts b/patched-vscode/src/vs/base/common/observableInternal/api.ts new file mode 100644 index 00000000..6e56671b --- /dev/null +++ b/patched-vscode/src/vs/base/common/observableInternal/api.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EqualityComparer, strictEquals } from 'vs/base/common/equals'; +import { ISettableObservable } from 'vs/base/common/observable'; +import { ObservableValue } from 'vs/base/common/observableInternal/base'; +import { IDebugNameData, DebugNameData } from 'vs/base/common/observableInternal/debugName'; +import { LazyObservableValue } from 'vs/base/common/observableInternal/lazyObservableValue'; + +export function observableValueOpts( + options: IDebugNameData & { + equalsFn?: EqualityComparer; + lazy?: boolean; + }, + initialValue: T +): ISettableObservable { + if (options.lazy) { + return new LazyObservableValue( + new DebugNameData(options.owner, options.debugName, undefined), + initialValue, + options.equalsFn ?? strictEquals, + ); + } + return new ObservableValue( + new DebugNameData(options.owner, options.debugName, undefined), + initialValue, + options.equalsFn ?? strictEquals, + ); +} diff --git a/patched-vscode/src/vs/base/common/observableInternal/autorun.ts b/patched-vscode/src/vs/base/common/observableInternal/autorun.ts index a2f169ee..845e870d 100644 --- a/patched-vscode/src/vs/base/common/observableInternal/autorun.ts +++ b/patched-vscode/src/vs/base/common/observableInternal/autorun.ts @@ -76,7 +76,7 @@ export function autorunWithStoreHandleChanges( { owner: options.owner, debugName: options.debugName, - debugReferenceFn: options.debugReferenceFn, + debugReferenceFn: options.debugReferenceFn ?? fn, createEmptyChangeSummary: options.createEmptyChangeSummary, handleChange: options.handleChange, }, @@ -154,7 +154,7 @@ export class AutorunObserver implements IObserver, IReader } constructor( - private readonly _debugNameData: DebugNameData, + public readonly _debugNameData: DebugNameData, public readonly _runFn: (reader: IReader, changeSummary: TChangeSummary) => void, private readonly createChangeSummary: (() => TChangeSummary) | undefined, private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, diff --git a/patched-vscode/src/vs/base/common/observableInternal/base.ts b/patched-vscode/src/vs/base/common/observableInternal/base.ts index 7f76c8cc..f499281e 100644 --- a/patched-vscode/src/vs/base/common/observableInternal/base.ts +++ b/patched-vscode/src/vs/base/common/observableInternal/base.ts @@ -6,7 +6,7 @@ import { strictEquals, EqualityComparer } from 'vs/base/common/equals'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { keepObserved, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; -import { DebugNameData, IDebugNameData, Owner, getFunctionName } from 'vs/base/common/observableInternal/debugName'; +import { DebugNameData, DebugOwner, getFunctionName } from 'vs/base/common/observableInternal/debugName'; import type { derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -66,6 +66,8 @@ export interface IObservable { map(fn: (value: T, reader: IReader) => TNew): IObservable; map(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable; + flatten(this: IObservable>): IObservable; + /** * Makes sure this value is computed eagerly. */ @@ -201,9 +203,9 @@ export abstract class ConvenientObservable implements IObservable(fn: (value: T, reader: IReader) => TNew): IObservable; - public map(owner: Owner, fn: (value: T, reader: IReader) => TNew): IObservable; - public map(fnOrOwner: Owner | ((value: T, reader: IReader) => TNew), fnOrUndefined?: (value: T, reader: IReader) => TNew): IObservable { - const owner = fnOrUndefined === undefined ? undefined : fnOrOwner as Owner; + public map(owner: DebugOwner, fn: (value: T, reader: IReader) => TNew): IObservable; + public map(fnOrOwner: DebugOwner | ((value: T, reader: IReader) => TNew), fnOrUndefined?: (value: T, reader: IReader) => TNew): IObservable { + const owner = fnOrUndefined === undefined ? undefined : fnOrOwner as DebugOwner; const fn = fnOrUndefined === undefined ? fnOrOwner as (value: T, reader: IReader) => TNew : fnOrUndefined; return _derived( @@ -232,6 +234,20 @@ export abstract class ConvenientObservable implements IObservable(this: IObservable>): IObservable { + return _derived( + { + owner: undefined, + debugName: () => `${this.debugName} (flattened)`, + }, + (reader) => this.read(reader).read(reader), + ); + } + public recomputeInitiallyAndOnChange(store: DisposableStore, handleValue?: (value: T) => void): IObservable { store.add(_recomputeInitiallyAndOnChange!(this, handleValue)); return this; @@ -385,19 +401,6 @@ export function observableValue(nameOrOwner: string | object, return new ObservableValue(debugNameData, initialValue, strictEquals); } -export function observableValueOpts( - options: IDebugNameData & { - equalsFn?: EqualityComparer; - }, - initialValue: T -): ISettableObservable { - return new ObservableValue( - new DebugNameData(options.owner, options.debugName, undefined), - initialValue, - options.equalsFn ?? strictEquals, - ); -} - export class ObservableValue extends BaseObservable implements ISettableObservable { diff --git a/patched-vscode/src/vs/base/common/observableInternal/debugName.ts b/patched-vscode/src/vs/base/common/observableInternal/debugName.ts index 481d24f0..1ff1f244 100644 --- a/patched-vscode/src/vs/base/common/observableInternal/debugName.ts +++ b/patched-vscode/src/vs/base/common/observableInternal/debugName.ts @@ -8,7 +8,7 @@ export interface IDebugNameData { * The owner object of an observable. * Used for debugging only, such as computing a name for the observable by iterating over the fields of the owner. */ - readonly owner?: Owner | undefined; + readonly owner?: DebugOwner | undefined; /** * A string or function that returns a string that represents the name of the observable. @@ -25,7 +25,7 @@ export interface IDebugNameData { export class DebugNameData { constructor( - public readonly owner: Owner | undefined, + public readonly owner: DebugOwner | undefined, public readonly debugNameSource: DebugNameSource | undefined, public readonly referenceFn: Function | undefined, ) { } @@ -36,10 +36,10 @@ export class DebugNameData { } /** - * The owner object of an observable. + * The owning object of an observable. * Is only used for debugging purposes, such as computing a name for the observable by iterating over the fields of the owner. */ -export type Owner = object | undefined; +export type DebugOwner = object | undefined; export type DebugNameSource = string | (() => string | undefined); const countPerName = new Map(); diff --git a/patched-vscode/src/vs/base/common/observableInternal/derived.ts b/patched-vscode/src/vs/base/common/observableInternal/derived.ts index 9e95bf9d..8de22247 100644 --- a/patched-vscode/src/vs/base/common/observableInternal/derived.ts +++ b/patched-vscode/src/vs/base/common/observableInternal/derived.ts @@ -7,7 +7,7 @@ import { assertFn } from 'vs/base/common/assert'; import { EqualityComparer, strictEquals } from 'vs/base/common/equals'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { BaseObservable, IChangeContext, IObservable, IObserver, IReader, ISettableObservable, ITransaction, _setDerivedOpts, } from 'vs/base/common/observableInternal/base'; -import { DebugNameData, IDebugNameData, Owner } from 'vs/base/common/observableInternal/debugName'; +import { DebugNameData, IDebugNameData, DebugOwner } from 'vs/base/common/observableInternal/debugName'; import { getLogger } from 'vs/base/common/observableInternal/logging'; /** @@ -17,8 +17,8 @@ import { getLogger } from 'vs/base/common/observableInternal/logging'; * {@link computeFn} should start with a JS Doc using `@description` to name the derived. */ export function derived(computeFn: (reader: IReader) => T): IObservable; -export function derived(owner: Owner, computeFn: (reader: IReader) => T): IObservable; -export function derived(computeFnOrOwner: ((reader: IReader) => T) | Owner, computeFn?: ((reader: IReader) => T) | undefined): IObservable { +export function derived(owner: DebugOwner, computeFn: (reader: IReader) => T): IObservable; +export function derived(computeFnOrOwner: ((reader: IReader) => T) | DebugOwner, computeFn?: ((reader: IReader) => T) | undefined): IObservable { if (computeFn !== undefined) { return new Derived( new DebugNameData(computeFnOrOwner, undefined, computeFn), @@ -39,7 +39,7 @@ export function derived(computeFnOrOwner: ((reader: IReader) => T) | Owner, c ); } -export function derivedWithSetter(owner: Owner | undefined, computeFn: (reader: IReader) => T, setter: (value: T, transaction: ITransaction | undefined) => void): ISettableObservable { +export function derivedWithSetter(owner: DebugOwner | undefined, computeFn: (reader: IReader) => T, setter: (value: T, transaction: ITransaction | undefined) => void): ISettableObservable { return new DerivedWithSetter( new DebugNameData(owner, undefined, computeFn), computeFn, @@ -105,7 +105,7 @@ export function derivedWithStore(computeFn: (reader: IReader, store: Disposab export function derivedWithStore(owner: object, computeFn: (reader: IReader, store: DisposableStore) => T): IObservable; export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: DisposableStore) => T) | object, computeFnOrUndefined?: ((reader: IReader, store: DisposableStore) => T)): IObservable { let computeFn: (reader: IReader, store: DisposableStore) => T; - let owner: Owner; + let owner: DebugOwner; if (computeFnOrUndefined === undefined) { computeFn = computeFnOrOwner as any; owner = undefined; @@ -128,10 +128,10 @@ export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: } export function derivedDisposable(computeFn: (reader: IReader) => T): IObservable; -export function derivedDisposable(owner: Owner, computeFn: (reader: IReader) => T): IObservable; -export function derivedDisposable(computeFnOrOwner: ((reader: IReader) => T) | Owner, computeFnOrUndefined?: ((reader: IReader) => T)): IObservable { +export function derivedDisposable(owner: DebugOwner, computeFn: (reader: IReader) => T): IObservable; +export function derivedDisposable(computeFnOrOwner: ((reader: IReader) => T) | DebugOwner, computeFnOrUndefined?: ((reader: IReader) => T)): IObservable { let computeFn: (reader: IReader) => T; - let owner: Owner; + let owner: DebugOwner; if (computeFnOrUndefined === undefined) { computeFn = computeFnOrOwner as any; owner = undefined; @@ -140,11 +140,15 @@ export function derivedDisposable(computeFnOr computeFn = computeFnOrUndefined as any; } - const store = new DisposableStore(); + let store: DisposableStore | undefined = undefined; return new Derived( new DebugNameData(owner, undefined, computeFn), r => { - store.clear(); + if (!store) { + store = new DisposableStore(); + } else { + store.clear(); + } const result = computeFn(r); if (result) { store.add(result); @@ -152,7 +156,12 @@ export function derivedDisposable(computeFnOr return result; }, undefined, undefined, - () => store.dispose(), + () => { + if (store) { + store.dispose(); + store = undefined; + } + }, strictEquals ); } @@ -192,7 +201,7 @@ export class Derived extends BaseObservable im } constructor( - private readonly _debugNameData: DebugNameData, + public readonly _debugNameData: DebugNameData, public readonly _computeFn: (reader: IReader, changeSummary: TChangeSummary) => T, private readonly createChangeSummary: (() => TChangeSummary) | undefined, private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, diff --git a/patched-vscode/src/vs/base/common/observableInternal/lazyObservableValue.ts b/patched-vscode/src/vs/base/common/observableInternal/lazyObservableValue.ts new file mode 100644 index 00000000..1c35f458 --- /dev/null +++ b/patched-vscode/src/vs/base/common/observableInternal/lazyObservableValue.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EqualityComparer } from 'vs/base/common/equals'; +import { ISettableObservable, ITransaction } from 'vs/base/common/observable'; +import { BaseObservable, IObserver, TransactionImpl } from 'vs/base/common/observableInternal/base'; +import { DebugNameData } from 'vs/base/common/observableInternal/debugName'; + +/** + * Holds off updating observers until the value is actually read. +*/ +export class LazyObservableValue + extends BaseObservable + implements ISettableObservable { + protected _value: T; + private _isUpToDate = true; + private readonly _deltas: TChange[] = []; + + get debugName() { + return this._debugNameData.getDebugName(this) ?? 'LazyObservableValue'; + } + + constructor( + private readonly _debugNameData: DebugNameData, + initialValue: T, + private readonly _equalityComparator: EqualityComparer, + ) { + super(); + this._value = initialValue; + } + + public override get(): T { + this._update(); + return this._value; + } + + private _update(): void { + if (this._isUpToDate) { + return; + } + this._isUpToDate = true; + + if (this._deltas.length > 0) { + for (const observer of this.observers) { + for (const change of this._deltas) { + observer.handleChange(this, change); + } + } + this._deltas.length = 0; + } else { + for (const observer of this.observers) { + observer.handleChange(this, undefined); + } + } + } + + private _updateCounter = 0; + + private _beginUpdate(): void { + this._updateCounter++; + if (this._updateCounter === 1) { + for (const observer of this.observers) { + observer.beginUpdate(this); + } + } + } + + private _endUpdate(): void { + this._updateCounter--; + if (this._updateCounter === 0) { + this._update(); + + // End update could change the observer list. + const observers = [...this.observers]; + for (const r of observers) { + r.endUpdate(this); + } + } + } + + public override addObserver(observer: IObserver): void { + const shouldCallBeginUpdate = !this.observers.has(observer) && this._updateCounter > 0; + super.addObserver(observer); + + if (shouldCallBeginUpdate) { + observer.beginUpdate(this); + } + } + + public override removeObserver(observer: IObserver): void { + const shouldCallEndUpdate = this.observers.has(observer) && this._updateCounter > 0; + super.removeObserver(observer); + + if (shouldCallEndUpdate) { + // Calling end update after removing the observer makes sure endUpdate cannot be called twice here. + observer.endUpdate(this); + } + } + + public set(value: T, tx: ITransaction | undefined, change: TChange): void { + if (change === undefined && this._equalityComparator(this._value, value)) { + return; + } + + let _tx: TransactionImpl | undefined; + if (!tx) { + tx = _tx = new TransactionImpl(() => { }, () => `Setting ${this.debugName}`); + } + try { + this._isUpToDate = false; + this._setValue(value); + if (change !== undefined) { + this._deltas.push(change); + } + + tx.updateObserver({ + beginUpdate: () => this._beginUpdate(), + endUpdate: () => this._endUpdate(), + handleChange: (observable, change) => { }, + handlePossibleChange: (observable) => { }, + }, this); + + if (this._updateCounter > 1) { + // We already started begin/end update, so we need to manually call handlePossibleChange + for (const observer of this.observers) { + observer.handlePossibleChange(this); + } + } + + } finally { + if (_tx) { + _tx.finish(); + } + } + } + + override toString(): string { + return `${this.debugName}: ${this._value}`; + } + + protected _setValue(newValue: T): void { + this._value = newValue; + } +} diff --git a/patched-vscode/src/vs/base/common/observableInternal/logging.ts b/patched-vscode/src/vs/base/common/observableInternal/logging.ts index 01fcc3cd..5e4712e6 100644 --- a/patched-vscode/src/vs/base/common/observableInternal/logging.ts +++ b/patched-vscode/src/vs/base/common/observableInternal/logging.ts @@ -114,7 +114,7 @@ export class ConsoleObservableLogger implements IObservableLogger { styled(derived.debugName, { color: 'BlueViolet' }), ...this.formatInfo(info), this.formatChanges(changedObservables), - { data: [{ fn: derived._computeFn }] } + { data: [{ fn: derived._debugNameData.referenceFn ?? derived._computeFn }] } ])); changedObservables.clear(); } @@ -143,7 +143,7 @@ export class ConsoleObservableLogger implements IObservableLogger { formatKind('autorun'), styled(autorun.debugName, { color: 'BlueViolet' }), this.formatChanges(changedObservables), - { data: [{ fn: autorun._runFn }] } + { data: [{ fn: autorun._debugNameData.referenceFn ?? autorun._runFn }] } ])); changedObservables.clear(); this.indentation++; diff --git a/patched-vscode/src/vs/base/common/observableInternal/promise.ts b/patched-vscode/src/vs/base/common/observableInternal/promise.ts index e0109a39..80d269c1 100644 --- a/patched-vscode/src/vs/base/common/observableInternal/promise.ts +++ b/patched-vscode/src/vs/base/common/observableInternal/promise.ts @@ -6,7 +6,7 @@ import { autorun } from 'vs/base/common/observableInternal/autorun'; import { IObservable, IReader, observableValue, transaction } from './base'; import { Derived, derived } from 'vs/base/common/observableInternal/derived'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { DebugNameData, Owner } from 'vs/base/common/observableInternal/debugName'; +import { DebugNameData, DebugOwner } from 'vs/base/common/observableInternal/debugName'; import { strictEquals } from 'vs/base/common/equals'; import { CancellationError } from 'vs/base/common/errors'; @@ -40,6 +40,10 @@ export class ObservableLazy { * A promise whose state is observable. */ export class ObservablePromise { + public static fromFn(fn: () => Promise): ObservablePromise { + return new ObservablePromise(fn()); + } + private readonly _value = observableValue | undefined>(this, undefined); /** @@ -179,7 +183,7 @@ export function derivedWithCancellationToken(computeFn: (reader: IReader, can export function derivedWithCancellationToken(owner: object, computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable; export function derivedWithCancellationToken(computeFnOrOwner: ((reader: IReader, cancellationToken: CancellationToken) => T) | object, computeFnOrUndefined?: ((reader: IReader, cancellationToken: CancellationToken) => T)): IObservable { let computeFn: (reader: IReader, store: CancellationToken) => T; - let owner: Owner; + let owner: DebugOwner; if (computeFnOrUndefined === undefined) { computeFn = computeFnOrOwner as any; owner = undefined; diff --git a/patched-vscode/src/vs/base/common/observableInternal/utils.ts b/patched-vscode/src/vs/base/common/observableInternal/utils.ts index 409fe4a1..0b19a4a3 100644 --- a/patched-vscode/src/vs/base/common/observableInternal/utils.ts +++ b/patched-vscode/src/vs/base/common/observableInternal/utils.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; +import { Event, IValueWithChangeEvent } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { autorun } from 'vs/base/common/observableInternal/autorun'; +import { autorun, autorunOpts } from 'vs/base/common/observableInternal/autorun'; import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setKeepObserved, _setRecomputeInitiallyAndOnChange, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; -import { DebugNameData, Owner, getFunctionName } from 'vs/base/common/observableInternal/debugName'; +import { DebugNameData, IDebugNameData, DebugOwner, getDebugName, } from 'vs/base/common/observableInternal/debugName'; import { derived, derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; -import { IValueWithChangeEvent } from '../event'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { EqualityComparer, strictEquals } from 'vs/base/common/equals'; /** * Represents an efficient observable whose value never changes. @@ -52,11 +53,49 @@ export function observableFromPromise(promise: Promise): IObservable<{ val return observable; } + +export function observableFromEvent( + owner: DebugOwner, + event: Event, + getValue: (args: TArgs | undefined) => T, +): IObservable; export function observableFromEvent( event: Event, - getValue: (args: TArgs | undefined) => T + getValue: (args: TArgs | undefined) => T, +): IObservable; +export function observableFromEvent(...args: + [owner: DebugOwner, event: Event, getValue: (args: any | undefined) => any] + | [event: Event, getValue: (args: any | undefined) => any] +): IObservable { + let owner; + let event; + let getValue; + if (args.length === 3) { + [owner, event, getValue] = args; + } else { + [event, getValue] = args; + } + return new FromEventObservable( + new DebugNameData(owner, undefined, getValue), + event, + getValue, + () => FromEventObservable.globalTransaction, + strictEquals + ); +} + +export function observableFromEventOpts( + options: IDebugNameData & { + equalsFn?: EqualityComparer; + }, + event: Event, + getValue: (args: TArgs | undefined) => T, ): IObservable { - return new FromEventObservable(event, getValue); + return new FromEventObservable( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? getValue), + event, + getValue, () => FromEventObservable.globalTransaction, options.equalsFn ?? strictEquals + ); } export class FromEventObservable extends BaseObservable { @@ -67,14 +106,17 @@ export class FromEventObservable extends BaseObservable { private subscription: IDisposable | undefined; constructor( + private readonly _debugNameData: DebugNameData, private readonly event: Event, - public readonly _getValue: (args: TArgs | undefined) => T + public readonly _getValue: (args: TArgs | undefined) => T, + private readonly _getTransaction: () => ITransaction | undefined, + private readonly _equalityComparator: EqualityComparer ) { super(); } private getDebugName(): string | undefined { - return getFunctionName(this._getValue); + return this._debugNameData.getDebugName(this); } public get debugName(): string { @@ -90,7 +132,7 @@ export class FromEventObservable extends BaseObservable { const newValue = this._getValue(args); const oldValue = this.value; - const didChange = !this.hasValue || oldValue !== newValue; + const didChange = !this.hasValue || !(this._equalityComparator(oldValue!, newValue)); let didRunTransaction = false; if (didChange) { @@ -99,7 +141,7 @@ export class FromEventObservable extends BaseObservable { if (this.hasValue) { didRunTransaction = true; subtransaction( - FromEventObservable.globalTransaction, + this._getTransaction(), (tx) => { getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); @@ -229,6 +271,10 @@ class ObservableSignal extends BaseObservable implements return new DebugNameData(this._owner, this._debugName, undefined).getDebugName(this) ?? 'Observable Signal'; } + public override toString(): string { + return this.debugName; + } + constructor( private readonly _debugName: string | undefined, private readonly _owner?: object, @@ -406,9 +452,9 @@ export class KeepAliveObserver implements IObserver { } } -export function derivedObservableWithCache(owner: Owner, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { +export function derivedObservableWithCache(owner: DebugOwner, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { let lastValue: T | undefined = undefined; - const observable = derived(owner, reader => { + const observable = derivedOpts({ owner, debugReferenceFn: computeFn }, reader => { lastValue = computeFn(reader, lastValue); return lastValue; }); @@ -439,7 +485,7 @@ export function derivedObservableWithWritableCache(owner: object, computeFn: /** * When the items array changes, referential equal items are not mapped again. */ -export function mapObservableArrayCached(owner: Owner, items: IObservable, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable { +export function mapObservableArrayCached(owner: DebugOwner, items: IObservable, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable { let m = new ArrayMap(map, keySelector); const self = derivedOpts({ debugReferenceFn: map, @@ -515,9 +561,58 @@ export class ValueWithChangeEventFromObservable implements IValueWithChangeEv } } -export function observableFromValueWithChangeEvent(_owner: Owner, value: IValueWithChangeEvent): IObservable { +export function observableFromValueWithChangeEvent(owner: DebugOwner, value: IValueWithChangeEvent): IObservable { if (value instanceof ValueWithChangeEventFromObservable) { return value.observable; } - return observableFromEvent(value.onDidChange, () => value.value); + return observableFromEvent(owner, value.onDidChange, () => value.value); +} + +/** + * Creates an observable that has the latest changed value of the given observables. + * Initially (and when not observed), it has the value of the last observable. + * When observed and any of the observables change, it has the value of the last changed observable. + * If multiple observables change in the same transaction, the last observable wins. +*/ +export function latestChangedValue[]>(owner: DebugOwner, observables: T): IObservable> { + if (observables.length === 0) { + throw new BugIndicatingError(); + } + + let hasLastChangedValue = false; + let lastChangedValue: any = undefined; + + const result = observableFromEvent(owner, cb => { + const store = new DisposableStore(); + for (const o of observables) { + store.add(autorunOpts({ debugName: () => getDebugName(result, new DebugNameData(owner, undefined, undefined)) + '.updateLastChangedValue' }, reader => { + hasLastChangedValue = true; + lastChangedValue = o.read(reader); + cb(); + })); + } + store.add({ + dispose() { + hasLastChangedValue = false; + lastChangedValue = undefined; + }, + }); + return store; + }, () => { + if (hasLastChangedValue) { + return lastChangedValue; + } else { + return observables[observables.length - 1].get(); + } + }); + return result; +} + +/** + * Works like a derived. + * However, if the value is not undefined, it is cached and will not be recomputed anymore. + * In that case, the derived will unsubscribe from its dependencies. +*/ +export function derivedConstOnceDefined(owner: DebugOwner, fn: (reader: IReader) => T): IObservable { + return derivedObservableWithCache(owner, (reader, lastValue) => lastValue ?? fn(reader)); } diff --git a/patched-vscode/src/vs/base/common/path.ts b/patched-vscode/src/vs/base/common/path.ts index 83140948..6f40f7d0 100644 --- a/patched-vscode/src/vs/base/common/path.ts +++ b/patched-vscode/src/vs/base/common/path.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ // NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace -// Copied from: https://github.com/nodejs/node/blob/v16.14.2/lib/path.js +// Copied from: https://github.com/nodejs/node/commits/v20.9.0/lib/path.js +// Excluding: the change that adds primordials +// (https://github.com/nodejs/node/commit/187a862d221dec42fa9a5c4214e7034d9092792f and others) /** * Copyright Joyent, Inc. and other Node contributors. @@ -159,11 +161,15 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin return res; } +function formatExt(ext: string): string { + return ext ? `${ext[0] === '.' ? '' : '.'}${ext}` : ''; +} + function _format(sep: string, pathObject: ParsedPath) { validateObject(pathObject, 'pathObject'); const dir = pathObject.dir || pathObject.root; const base = pathObject.base || - `${pathObject.name || ''}${pathObject.ext || ''}`; + `${pathObject.name || ''}${formatExt(pathObject.ext)}`; if (!dir) { return base; } @@ -185,7 +191,7 @@ export interface IPath { resolve(...pathSegments: string[]): string; relative(from: string, to: string): string; dirname(path: string): string; - basename(path: string, ext?: string): string; + basename(path: string, suffix?: string): string; extname(path: string): string; format(pathObject: ParsedPath): string; parse(path: string): ParsedPath; @@ -207,7 +213,7 @@ export const win32: IPath = { let path; if (i >= 0) { path = pathSegments[i]; - validateString(path, 'path'); + validateString(path, `paths[${i}]`); // Skip empty entries if (path.length === 0) { @@ -757,9 +763,9 @@ export const win32: IPath = { return path.slice(0, end); }, - basename(path: string, ext?: string): string { - if (ext !== undefined) { - validateString(ext, 'ext'); + basename(path: string, suffix?: string): string { + if (suffix !== undefined) { + validateString(suffix, 'suffix'); } validateString(path, 'path'); let start = 0; @@ -776,11 +782,11 @@ export const win32: IPath = { start = 2; } - if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { - if (ext === path) { + if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) { + if (suffix === path) { return ''; } - let extIdx = ext.length - 1; + let extIdx = suffix.length - 1; let firstNonSlashEnd = -1; for (i = path.length - 1; i >= start; --i) { const code = path.charCodeAt(i); @@ -800,7 +806,7 @@ export const win32: IPath = { } if (extIdx >= 0) { // Try to match the explicit extension - if (code === ext.charCodeAt(extIdx)) { + if (code === suffix.charCodeAt(extIdx)) { if (--extIdx === -1) { // We matched the extension, so mark this as the end of our path // component @@ -1095,7 +1101,7 @@ export const posix: IPath = { for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { const path = i >= 0 ? pathSegments[i] : posixCwd(); - validateString(path, 'path'); + validateString(path, `paths[${i}]`); // Skip empty entries if (path.length === 0) { @@ -1280,9 +1286,9 @@ export const posix: IPath = { return path.slice(0, end); }, - basename(path: string, ext?: string): string { - if (ext !== undefined) { - validateString(ext, 'ext'); + basename(path: string, suffix?: string): string { + if (suffix !== undefined) { + validateString(suffix, 'ext'); } validateString(path, 'path'); @@ -1291,11 +1297,11 @@ export const posix: IPath = { let matchedSlash = true; let i; - if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { - if (ext === path) { + if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) { + if (suffix === path) { return ''; } - let extIdx = ext.length - 1; + let extIdx = suffix.length - 1; let firstNonSlashEnd = -1; for (i = path.length - 1; i >= 0; --i) { const code = path.charCodeAt(i); @@ -1315,7 +1321,7 @@ export const posix: IPath = { } if (extIdx >= 0) { // Try to match the explicit extension - if (code === ext.charCodeAt(extIdx)) { + if (code === suffix.charCodeAt(extIdx)) { if (--extIdx === -1) { // We matched the extension, so mark this as the end of our path // component diff --git a/patched-vscode/src/vs/base/common/performance.js b/patched-vscode/src/vs/base/common/performance.js index aff4d073..93167c84 100644 --- a/patched-vscode/src/vs/base/common/performance.js +++ b/patched-vscode/src/vs/base/common/performance.js @@ -3,9 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +//@ts-check 'use strict'; -//@ts-check +// ESM-uncomment-begin +// /** @type any */ +// const module = { exports: {} }; +// ESM-uncomment-end (function () { @@ -42,6 +46,7 @@ // Identify browser environment when following property is not present // https://nodejs.org/dist/latest-v16.x/docs/api/perf_hooks.html#performancenodetiming + // @ts-ignore if (typeof performance === 'object' && typeof performance.mark === 'function' && !performance.nodeTiming) { // in a browser context, reuse performance-util @@ -78,7 +83,7 @@ } else if (typeof process === 'object') { // node.js: use the normal polyfill but add the timeOrigin // from the node perf_hooks API as very first mark - const timeOrigin = performance?.timeOrigin ?? Math.round((require.__$__nodeRequire || require)('perf_hooks').performance.timeOrigin); + const timeOrigin = performance?.timeOrigin;// ?? Math.round((require.__$__nodeRequire ?? require /* TODO@esm this is fishy */)('perf_hooks').performance.timeOrigin); return _definePolyfillMarks(timeOrigin); } else { @@ -119,7 +124,13 @@ module.exports = _factory(sharedObj); } else { console.trace('perf-util defined in UNKNOWN context (neither requirejs or commonjs)'); + // @ts-ignore sharedObj.perf = _factory(sharedObj); } })(); + +// ESM-uncomment-begin +// export const mark = module.exports.mark; +// export const getMarks = module.exports.getMarks; +// ESM-uncomment-end diff --git a/patched-vscode/src/vs/base/common/platform.ts b/patched-vscode/src/vs/base/common/platform.ts index 2251c7db..d4ec4909 100644 --- a/patched-vscode/src/vs/base/common/platform.ts +++ b/patched-vscode/src/vs/base/common/platform.ts @@ -2,6 +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 * as nls from 'vs/nls'; export const LANGUAGE_DEFAULT = 'en'; @@ -22,13 +23,6 @@ let _platformLocale: string = LANGUAGE_DEFAULT; let _translationsConfigFile: string | undefined = undefined; let _userAgent: string | undefined = undefined; -interface NLSConfig { - locale: string; - osLocale: string; - availableLanguages: { [key: string]: string }; - _translationsConfigFile: string; -} - export interface IProcessEnvironment { [key: string]: string | undefined; } @@ -89,13 +83,11 @@ if (typeof nodeProcess === 'object') { const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG']; if (rawNlsConfig) { try { - const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); - const resolved = nlsConfig.availableLanguages['*']; - _locale = nlsConfig.locale; + const nlsConfig: nls.INLSConfiguration = JSON.parse(rawNlsConfig); + _locale = nlsConfig.userLocale; _platformLocale = nlsConfig.osLocale; - // VSCode's default language is 'en' - _language = resolved ? resolved : LANGUAGE_DEFAULT; - _translationsConfigFile = nlsConfig._translationsConfigFile; + _language = nlsConfig.resolvedLanguage || LANGUAGE_DEFAULT; + _translationsConfigFile = nlsConfig.languagePack?.translationsConfigFile; } catch (e) { } } @@ -111,18 +103,9 @@ else if (typeof navigator === 'object' && !isElectronRenderer) { _isLinux = _userAgent.indexOf('Linux') >= 0; _isMobile = _userAgent?.indexOf('Mobi') >= 0; _isWeb = true; - - const configuredLocale = nls.getConfiguredDefaultLocale( - // This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale` - // to ensure that the NLS AMD Loader plugin has been loaded and configured. - // This is because the loader plugin decides what the default locale is based on - // how it's able to resolve the strings. - nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_') - ); - - _locale = configuredLocale || LANGUAGE_DEFAULT; - _language = _locale; - _platformLocale = navigator.language; + _language = nls.getNLSLanguage() || LANGUAGE_DEFAULT; + _locale = navigator.language.toLowerCase(); + _platformLocale = _locale; } // Unknown environment @@ -178,7 +161,7 @@ export const userAgent = _userAgent; /** * The language used for the user interface. The format of * the string is all lower case (e.g. zh-tw for Traditional - * Chinese) + * Chinese or de for German) */ export const language = _language; @@ -204,15 +187,16 @@ export namespace Language { } /** - * The OS locale or the locale specified by --locale. The format of - * the string is all lower case (e.g. zh-tw for Traditional - * Chinese). The UI is not necessarily shown in the provided locale. + * Desktop: The OS locale or the locale specified by --locale or `argv.json`. + * Web: matches `platformLocale`. + * + * The UI is not necessarily shown in the provided locale. */ export const locale = _locale; /** * This will always be set to the OS/browser's locale regardless of - * what was specified by --locale. The format of the string is all + * what was specified otherwise. The format of the string is all * lower case (e.g. zh-tw for Traditional Chinese). The UI is not * necessarily shown in the provided locale. */ diff --git a/patched-vscode/src/vs/base/common/prefixTree.ts b/patched-vscode/src/vs/base/common/prefixTree.ts index 8e839e2b..53f02964 100644 --- a/patched-vscode/src/vs/base/common/prefixTree.ts +++ b/patched-vscode/src/vs/base/common/prefixTree.ts @@ -32,6 +32,11 @@ export class WellDefinedPrefixTree { return this.root.children?.values() || Iterable.empty(); } + /** Gets the top-level nodes of the tree */ + public get entries(): Iterable<[string, IPrefixTreeNode]> { + return this.root.children?.entries() || Iterable.empty(); + } + /** * Inserts a new value in the prefix tree. * @param onNode - called for each node as we descend to the insertion point, diff --git a/patched-vscode/src/vs/base/common/process.ts b/patched-vscode/src/vs/base/common/process.ts index 48fcd8ac..981e5924 100644 --- a/patched-vscode/src/vs/base/common/process.ts +++ b/patched-vscode/src/vs/base/common/process.ts @@ -21,7 +21,7 @@ if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'unde } // Native node.js environment -else if (typeof process !== 'undefined') { +else if (typeof process !== 'undefined' && typeof process?.versions?.node === 'string') { safeProcess = { get platform() { return process.platform; }, get arch() { return process.arch; }, diff --git a/patched-vscode/src/vs/base/common/processes.ts b/patched-vscode/src/vs/base/common/processes.ts index 417c4ba1..ef29387b 100644 --- a/patched-vscode/src/vs/base/common/processes.ts +++ b/patched-vscode/src/vs/base/common/processes.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IProcessEnvironment, isLinux, isMacintosh } from 'vs/base/common/platform'; +import { IProcessEnvironment, isLinux } from 'vs/base/common/platform'; /** * Options to be passed to the external program or shell. @@ -140,13 +140,6 @@ export function removeDangerousEnvVariables(env: IProcessEnvironment | undefined // See https://github.com/microsoft/vscode/issues/130072 delete env['DEBUG']; - if (isMacintosh) { - // Unset `DYLD_LIBRARY_PATH`, as it leads to process crashes - // See https://github.com/microsoft/vscode/issues/104525 - // See https://github.com/microsoft/vscode/issues/105848 - delete env['DYLD_LIBRARY_PATH']; - } - if (isLinux) { // Unset `LD_PRELOAD`, as it might lead to process crashes // See https://github.com/microsoft/vscode/issues/134177 diff --git a/patched-vscode/src/vs/base/common/product.ts b/patched-vscode/src/vs/base/common/product.ts index 2cca4770..022d2e65 100644 --- a/patched-vscode/src/vs/base/common/product.ts +++ b/patched-vscode/src/vs/base/common/product.ts @@ -55,7 +55,6 @@ export type ExtensionVirtualWorkspaceSupport = { }; export interface IProductConfiguration { - readonly rootEndpoint?: string readonly version: string; readonly date?: string; readonly quality?: string; @@ -83,6 +82,7 @@ export interface IProductConfiguration { readonly webEndpointUrlTemplate?: string; readonly webviewContentExternalBaseUrlTemplate?: string; readonly target?: string; + readonly nlsCoreBaseUrl?: string; readonly settingsSearchBuildId?: number; readonly settingsSearchUrl?: string; @@ -160,7 +160,6 @@ export interface IProductConfiguration { readonly tunnelApplicationConfig?: ITunnelApplicationConfig; readonly npsSurveyUrl?: string; - readonly cesSurveyUrl?: string; readonly surveys?: readonly ISurveyData[]; readonly checksums?: { [path: string]: string }; @@ -174,6 +173,7 @@ export interface IProductConfiguration { readonly extensionPointExtensionKind?: { readonly [extensionPointId: string]: ('ui' | 'workspace' | 'web')[] }; readonly extensionSyncedKeys?: { readonly [extensionId: string]: string[] }; + readonly extensionsEnabledWithApiProposalVersion?: string[]; readonly extensionEnabledApiProposals?: { readonly [extensionId: string]: string[] }; readonly extensionUntrustedWorkspaceSupport?: { readonly [extensionId: string]: ExtensionUntrustedWorkspaceSupport }; readonly extensionVirtualWorkspacesSupport?: { readonly [extensionId: string]: ExtensionVirtualWorkspaceSupport }; @@ -190,7 +190,6 @@ export interface IProductConfiguration { readonly commonlyUsedSettings?: string[]; readonly aiGeneratedWorkspaceTrust?: IAiGeneratedWorkspaceTrust; readonly gitHubEntitlement?: IGitHubEntitlement; - readonly chatWelcomeView?: IChatWelcomeView; readonly chatParticipantRegistry?: string; } @@ -207,6 +206,7 @@ export interface IExtensionRecommendations { export interface ISettingsEditorOpenCondition { readonly prerelease?: boolean | string; + readonly descriptionOverride?: string; } export interface IExtensionRecommendationCondition { @@ -305,9 +305,3 @@ export interface IGitHubEntitlement { confirmationMessage: string; confirmationAction: string; } - -export interface IChatWelcomeView { - welcomeViewId: string; - welcomeViewTitle: string; - welcomeViewContent: string; -} diff --git a/patched-vscode/src/vs/base/common/semver/semver.d.ts b/patched-vscode/src/vs/base/common/semver/semver.d.ts index 61a55bac..1c2157f1 100644 --- a/patched-vscode/src/vs/base/common/semver/semver.d.ts +++ b/patched-vscode/src/vs/base/common/semver/semver.d.ts @@ -3,9 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// ESM-comment-begin export as namespace semver; - export = semver; +// ESM-comment-end + +// ESM-uncomment-begin +// export * from 'semver' +// ESM-uncomment-end declare namespace semver { diff --git a/patched-vscode/src/vs/base/common/semver/semver.js b/patched-vscode/src/vs/base/common/semver/semver.js index 3eacfb19..ce02b4c9 100644 --- a/patched-vscode/src/vs/base/common/semver/semver.js +++ b/patched-vscode/src/vs/base/common/semver/semver.js @@ -8,4 +8,49 @@ * DO NOT EDIT THIS FILE */ +// ESM-uncomment-begin +// const exports = {}; +// const module = { exports }; +// ESM-uncomment-end + !function(e,r){if("object"==typeof exports&&"object"==typeof module)module.exports=r();else if("function"==typeof define&&define.amd)define([],r);else{var t=r();for(var n in t)("object"==typeof exports?exports:e)[n]=t[n]}}("undefined"!=typeof self?self:this,(function(){return function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}return t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:n})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(n,o,function(r){return e[r]}.bind(null,o));return n},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=0)}([function(e,r,t){(function(t){var n;r=e.exports=H,n="object"==typeof t&&t.env&&t.env.NODE_DEBUG&&/\bsemver\b/i.test(t.env.NODE_DEBUG)?function(){var e=Array.prototype.slice.call(arguments,0);e.unshift("SEMVER"),console.log.apply(console,e)}:function(){},r.SEMVER_SPEC_VERSION="2.0.0";var o=256,i=Number.MAX_SAFE_INTEGER||9007199254740991,s=r.re=[],a=r.src=[],u=0,c=u++;a[c]="0|[1-9]\\d*";var p=u++;a[p]="[0-9]+";var f=u++;a[f]="\\d*[a-zA-Z-][a-zA-Z0-9-]*";var l=u++;a[l]="("+a[c]+")\\.("+a[c]+")\\.("+a[c]+")";var h=u++;a[h]="("+a[p]+")\\.("+a[p]+")\\.("+a[p]+")";var v=u++;a[v]="(?:"+a[c]+"|"+a[f]+")";var m=u++;a[m]="(?:"+a[p]+"|"+a[f]+")";var w=u++;a[w]="(?:-("+a[v]+"(?:\\."+a[v]+")*))";var g=u++;a[g]="(?:-?("+a[m]+"(?:\\."+a[m]+")*))";var y=u++;a[y]="[0-9A-Za-z-]+";var d=u++;a[d]="(?:\\+("+a[y]+"(?:\\."+a[y]+")*))";var b=u++,j="v?"+a[l]+a[w]+"?"+a[d]+"?";a[b]="^"+j+"$";var E="[v=\\s]*"+a[h]+a[g]+"?"+a[d]+"?",T=u++;a[T]="^"+E+"$";var x=u++;a[x]="((?:<|>)?=?)";var $=u++;a[$]=a[p]+"|x|X|\\*";var k=u++;a[k]=a[c]+"|x|X|\\*";var S=u++;a[S]="[v=\\s]*("+a[k]+")(?:\\.("+a[k]+")(?:\\.("+a[k]+")(?:"+a[w]+")?"+a[d]+"?)?)?";var R=u++;a[R]="[v=\\s]*("+a[$]+")(?:\\.("+a[$]+")(?:\\.("+a[$]+")(?:"+a[g]+")?"+a[d]+"?)?)?";var I=u++;a[I]="^"+a[x]+"\\s*"+a[S]+"$";var _=u++;a[_]="^"+a[x]+"\\s*"+a[R]+"$";var O=u++;a[O]="(?:^|[^\\d])(\\d{1,16})(?:\\.(\\d{1,16}))?(?:\\.(\\d{1,16}))?(?:$|[^\\d])";var A=u++;a[A]="(?:~>?)";var M=u++;a[M]="(\\s*)"+a[A]+"\\s+",s[M]=new RegExp(a[M],"g");var V=u++;a[V]="^"+a[A]+a[S]+"$";var P=u++;a[P]="^"+a[A]+a[R]+"$";var C=u++;a[C]="(?:\\^)";var L=u++;a[L]="(\\s*)"+a[C]+"\\s+",s[L]=new RegExp(a[L],"g");var N=u++;a[N]="^"+a[C]+a[S]+"$";var q=u++;a[q]="^"+a[C]+a[R]+"$";var D=u++;a[D]="^"+a[x]+"\\s*("+E+")$|^$";var X=u++;a[X]="^"+a[x]+"\\s*("+j+")$|^$";var z=u++;a[z]="(\\s*)"+a[x]+"\\s*("+E+"|"+a[S]+")",s[z]=new RegExp(a[z],"g");var G=u++;a[G]="^\\s*("+a[S]+")\\s+-\\s+("+a[S]+")\\s*$";var Z=u++;a[Z]="^\\s*("+a[R]+")\\s+-\\s+("+a[R]+")\\s*$";var B=u++;a[B]="(<|>)?=?\\s*\\*";for(var U=0;U<35;U++)n(U,a[U]),s[U]||(s[U]=new RegExp(a[U]));function F(e,r){if(e instanceof H)return e;if("string"!=typeof e)return null;if(e.length>o)return null;if(!(r?s[T]:s[b]).test(e))return null;try{return new H(e,r)}catch(e){return null}}function H(e,r){if(e instanceof H){if(e.loose===r)return e;e=e.version}else if("string"!=typeof e)throw new TypeError("Invalid Version: "+e);if(e.length>o)throw new TypeError("version is longer than "+o+" characters");if(!(this instanceof H))return new H(e,r);n("SemVer",e,r),this.loose=r;var t=e.trim().match(r?s[T]:s[b]);if(!t)throw new TypeError("Invalid Version: "+e);if(this.raw=e,this.major=+t[1],this.minor=+t[2],this.patch=+t[3],this.major>i||this.major<0)throw new TypeError("Invalid major version");if(this.minor>i||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>i||this.patch<0)throw new TypeError("Invalid patch version");t[4]?this.prerelease=t[4].split(".").map((function(e){if(/^[0-9]+$/.test(e)){var r=+e;if(r>=0&&r=0;)"number"==typeof this.prerelease[t]&&(this.prerelease[t]++,t=-2);-1===t&&this.prerelease.push(0)}r&&(this.prerelease[0]===r?isNaN(this.prerelease[1])&&(this.prerelease=[r,0]):this.prerelease=[r,0]);break;default:throw new Error("invalid increment argument: "+e)}return this.format(),this.raw=this.version,this},r.inc=function(e,r,t,n){"string"==typeof t&&(n=t,t=void 0);try{return new H(e,t).inc(r,n).version}catch(e){return null}},r.diff=function(e,r){if(ee(e,r))return null;var t=F(e),n=F(r);if(t.prerelease.length||n.prerelease.length){for(var o in t)if(("major"===o||"minor"===o||"patch"===o)&&t[o]!==n[o])return"pre"+o;return"prerelease"}for(var o in t)if(("major"===o||"minor"===o||"patch"===o)&&t[o]!==n[o])return o},r.compareIdentifiers=K;var J=/^[0-9]+$/;function K(e,r){var t=J.test(e),n=J.test(r);return t&&n&&(e=+e,r=+r),t&&!n?-1:n&&!t?1:er?1:0}function Q(e,r,t){return new H(e,t).compare(new H(r,t))}function W(e,r,t){return Q(e,r,t)>0}function Y(e,r,t){return Q(e,r,t)<0}function ee(e,r,t){return 0===Q(e,r,t)}function re(e,r,t){return 0!==Q(e,r,t)}function te(e,r,t){return Q(e,r,t)>=0}function ne(e,r,t){return Q(e,r,t)<=0}function oe(e,r,t,n){var o;switch(r){case"===":"object"==typeof e&&(e=e.version),"object"==typeof t&&(t=t.version),o=e===t;break;case"!==":"object"==typeof e&&(e=e.version),"object"==typeof t&&(t=t.version),o=e!==t;break;case"":case"=":case"==":o=ee(e,t,n);break;case"!=":o=re(e,t,n);break;case">":o=W(e,t,n);break;case">=":o=te(e,t,n);break;case"<":o=Y(e,t,n);break;case"<=":o=ne(e,t,n);break;default:throw new TypeError("Invalid operator: "+r)}return o}function ie(e,r){if(e instanceof ie){if(e.loose===r)return e;e=e.value}if(!(this instanceof ie))return new ie(e,r);n("comparator",e,r),this.loose=r,this.parse(e),this.semver===se?this.value="":this.value=this.operator+this.semver.version,n("comp",this)}r.rcompareIdentifiers=function(e,r){return K(r,e)},r.major=function(e,r){return new H(e,r).major},r.minor=function(e,r){return new H(e,r).minor},r.patch=function(e,r){return new H(e,r).patch},r.compare=Q,r.compareLoose=function(e,r){return Q(e,r,!0)},r.rcompare=function(e,r,t){return Q(r,e,t)},r.sort=function(e,t){return e.sort((function(e,n){return r.compare(e,n,t)}))},r.rsort=function(e,t){return e.sort((function(e,n){return r.rcompare(e,n,t)}))},r.gt=W,r.lt=Y,r.eq=ee,r.neq=re,r.gte=te,r.lte=ne,r.cmp=oe,r.Comparator=ie;var se={};function ae(e,r){if(e instanceof ae)return e.loose===r?e:new ae(e.raw,r);if(e instanceof ie)return new ae(e.value,r);if(!(this instanceof ae))return new ae(e,r);if(this.loose=r,this.raw=e,this.set=e.split(/\s*\|\|\s*/).map((function(e){return this.parseRange(e.trim())}),this).filter((function(e){return e.length})),!this.set.length)throw new TypeError("Invalid SemVer Range: "+e);this.format()}function ue(e){return!e||"x"===e.toLowerCase()||"*"===e}function ce(e,r,t,n,o,i,s,a,u,c,p,f,l){return((r=ue(t)?"":ue(n)?">="+t+".0.0":ue(o)?">="+t+"."+n+".0":">="+r)+" "+(a=ue(u)?"":ue(c)?"<"+(+u+1)+".0.0":ue(p)?"<"+u+"."+(+c+1)+".0":f?"<="+u+"."+c+"."+p+"-"+f:"<="+a)).trim()}function pe(e,r){for(var t=0;t0){var o=e[t].semver;if(o.major===r.major&&o.minor===r.minor&&o.patch===r.patch)return!0}return!1}return!0}function fe(e,r,t){try{r=new ae(r,t)}catch(e){return!1}return r.test(e)}function le(e,r,t,n){var o,i,s,a,u;switch(e=new H(e,n),r=new ae(r,n),t){case">":o=W,i=ne,s=Y,a=">",u=">=";break;case"<":o=Y,i=te,s=W,a="<",u="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(fe(e,r,n))return!1;for(var c=0;c=0.0.0")),f=f||e,l=l||e,o(e.semver,f.semver,n)?f=e:s(e.semver,l.semver,n)&&(l=e)})),f.operator===a||f.operator===u)return!1;if((!l.operator||l.operator===a)&&i(e,l.semver))return!1;if(l.operator===u&&s(e,l.semver))return!1}return!0}ie.prototype.parse=function(e){var r=this.loose?s[D]:s[X],t=e.match(r);if(!t)throw new TypeError("Invalid comparator: "+e);this.operator=t[1],"="===this.operator&&(this.operator=""),t[2]?this.semver=new H(t[2],this.loose):this.semver=se},ie.prototype.toString=function(){return this.value},ie.prototype.test=function(e){return n("Comparator.test",e,this.loose),this.semver===se||("string"==typeof e&&(e=new H(e,this.loose)),oe(e,this.operator,this.semver,this.loose))},ie.prototype.intersects=function(e,r){if(!(e instanceof ie))throw new TypeError("a Comparator is required");var t;if(""===this.operator)return t=new ae(e.value,r),fe(this.value,t,r);if(""===e.operator)return t=new ae(this.value,r),fe(e.semver,t,r);var n=!(">="!==this.operator&&">"!==this.operator||">="!==e.operator&&">"!==e.operator),o=!("<="!==this.operator&&"<"!==this.operator||"<="!==e.operator&&"<"!==e.operator),i=this.semver.version===e.semver.version,s=!(">="!==this.operator&&"<="!==this.operator||">="!==e.operator&&"<="!==e.operator),a=oe(this.semver,"<",e.semver,r)&&(">="===this.operator||">"===this.operator)&&("<="===e.operator||"<"===e.operator),u=oe(this.semver,">",e.semver,r)&&("<="===this.operator||"<"===this.operator)&&(">="===e.operator||">"===e.operator);return n||o||i&&s||a||u},r.Range=ae,ae.prototype.format=function(){return this.range=this.set.map((function(e){return e.join(" ").trim()})).join("||").trim(),this.range},ae.prototype.toString=function(){return this.range},ae.prototype.parseRange=function(e){var r=this.loose;e=e.trim(),n("range",e,r);var t=r?s[Z]:s[G];e=e.replace(t,ce),n("hyphen replace",e),e=e.replace(s[z],"$1$2$3"),n("comparator trim",e,s[z]),e=(e=(e=e.replace(s[M],"$1~")).replace(s[L],"$1^")).split(/\s+/).join(" ");var o=r?s[D]:s[X],i=e.split(" ").map((function(e){return function(e,r){return n("comp",e),e=function(e,r){return e.trim().split(/\s+/).map((function(e){return function(e,r){n("caret",e,r);var t=r?s[q]:s[N];return e.replace(t,(function(r,t,o,i,s){var a;return n("caret",e,r,t,o,i,s),ue(t)?a="":ue(o)?a=">="+t+".0.0 <"+(+t+1)+".0.0":ue(i)?a="0"===t?">="+t+"."+o+".0 <"+t+"."+(+o+1)+".0":">="+t+"."+o+".0 <"+(+t+1)+".0.0":s?(n("replaceCaret pr",s),"-"!==s.charAt(0)&&(s="-"+s),a="0"===t?"0"===o?">="+t+"."+o+"."+i+s+" <"+t+"."+o+"."+(+i+1):">="+t+"."+o+"."+i+s+" <"+t+"."+(+o+1)+".0":">="+t+"."+o+"."+i+s+" <"+(+t+1)+".0.0"):(n("no pr"),a="0"===t?"0"===o?">="+t+"."+o+"."+i+" <"+t+"."+o+"."+(+i+1):">="+t+"."+o+"."+i+" <"+t+"."+(+o+1)+".0":">="+t+"."+o+"."+i+" <"+(+t+1)+".0.0"),n("caret return",a),a}))}(e,r)})).join(" ")}(e,r),n("caret",e),e=function(e,r){return e.trim().split(/\s+/).map((function(e){return function(e,r){var t=r?s[P]:s[V];return e.replace(t,(function(r,t,o,i,s){var a;return n("tilde",e,r,t,o,i,s),ue(t)?a="":ue(o)?a=">="+t+".0.0 <"+(+t+1)+".0.0":ue(i)?a=">="+t+"."+o+".0 <"+t+"."+(+o+1)+".0":s?(n("replaceTilde pr",s),"-"!==s.charAt(0)&&(s="-"+s),a=">="+t+"."+o+"."+i+s+" <"+t+"."+(+o+1)+".0"):a=">="+t+"."+o+"."+i+" <"+t+"."+(+o+1)+".0",n("tilde return",a),a}))}(e,r)})).join(" ")}(e,r),n("tildes",e),e=function(e,r){return n("replaceXRanges",e,r),e.split(/\s+/).map((function(e){return function(e,r){e=e.trim();var t=r?s[_]:s[I];return e.replace(t,(function(r,t,o,i,s,a){n("xRange",e,r,t,o,i,s,a);var u=ue(o),c=u||ue(i),p=c||ue(s);return"="===t&&p&&(t=""),u?r=">"===t||"<"===t?"<0.0.0":"*":t&&p?(c&&(i=0),p&&(s=0),">"===t?(t=">=",c?(o=+o+1,i=0,s=0):p&&(i=+i+1,s=0)):"<="===t&&(t="<",c?o=+o+1:i=+i+1),r=t+o+"."+i+"."+s):c?r=">="+o+".0.0 <"+(+o+1)+".0.0":p&&(r=">="+o+"."+i+".0 <"+o+"."+(+i+1)+".0"),n("xRange return",r),r}))}(e,r)})).join(" ")}(e,r),n("xrange",e),e=function(e,r){return n("replaceStars",e,r),e.trim().replace(s[B],"")}(e,r),n("stars",e),e}(e,r)})).join(" ").split(/\s+/);return this.loose&&(i=i.filter((function(e){return!!e.match(o)}))),i=i.map((function(e){return new ie(e,r)}))},ae.prototype.intersects=function(e,r){if(!(e instanceof ae))throw new TypeError("a Range is required");return this.set.some((function(t){return t.every((function(t){return e.set.some((function(e){return e.every((function(e){return t.intersects(e,r)}))}))}))}))},r.toComparators=function(e,r){return new ae(e,r).set.map((function(e){return e.map((function(e){return e.value})).join(" ").trim().split(" ")}))},ae.prototype.test=function(e){if(!e)return!1;"string"==typeof e&&(e=new H(e,this.loose));for(var r=0;r",t)},r.outside=le,r.prerelease=function(e,r){var t=F(e,r);return t&&t.prerelease.length?t.prerelease:null},r.intersects=function(e,r,t){return e=new ae(e,t),r=new ae(r,t),e.intersects(r)},r.coerce=function(e){if(e instanceof H)return e;if("string"!=typeof e)return null;var r=e.match(s[O]);return null==r?null:F((r[1]||"0")+"."+(r[2]||"0")+"."+(r[3]||"0"))}}).call(this,t(1))},function(e,r){var t,n,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function a(e){if(t===setTimeout)return setTimeout(e,0);if((t===i||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(r){try{return t.call(null,e,0)}catch(r){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:i}catch(e){t=i}try{n="function"==typeof clearTimeout?clearTimeout:s}catch(e){n=s}}();var u,c=[],p=!1,f=-1;function l(){p&&u&&(p=!1,u.length?c=u.concat(c):f=-1,c.length&&h())}function h(){if(!p){var e=a(l);p=!0;for(var r=c.length;r;){for(u=c,c=[];++f1)for(var t=1;t(x: Sub): Base { + return x; +} + type AddFirstParameterToFunction = T extends (...args: any[]) => TargetFunctionsReturnType ? // Function: add param to function (firstArg: FirstParameter, ...args: Parameters) => ReturnType : diff --git a/patched-vscode/src/vs/base/common/uri.ts b/patched-vscode/src/vs/base/common/uri.ts index 4d7e5143..f12b04da 100644 --- a/patched-vscode/src/vs/base/common/uri.ts +++ b/patched-vscode/src/vs/base/common/uri.ts @@ -413,6 +413,10 @@ export class URI implements UriComponents { return result; } } + + [Symbol.for('debug.description')]() { + return `URI(${this.toString()})`; + } } export interface UriComponents { diff --git a/patched-vscode/src/vs/base/common/worker/simpleWorker.ts b/patched-vscode/src/vs/base/common/worker/simpleWorker.ts index 57cc59ee..cf19e691 100644 --- a/patched-vscode/src/vs/base/common/worker/simpleWorker.ts +++ b/patched-vscode/src/vs/base/common/worker/simpleWorker.ts @@ -3,13 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { transformErrorForSerialization } from 'vs/base/common/errors'; +import { CharCode } from 'vs/base/common/charCode'; +import { onUnexpectedError, transformErrorForSerialization } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { getAllMethodNames } from 'vs/base/common/objects'; +import { AppResourcePath, FileAccess } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; +// ESM-comment-begin +const isESM = false; +// ESM-comment-end +// ESM-uncomment-begin +// const isESM = true; +// ESM-uncomment-end + +const DEFAULT_CHANNEL = 'default'; const INITIALIZE = '$initialize'; export interface IWorker extends IDisposable { @@ -22,7 +32,13 @@ export interface IWorkerCallback { } export interface IWorkerFactory { - create(moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker; + create(modules: IWorkerDescriptor, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker; +} + +export interface IWorkerDescriptor { + readonly amdModuleId: string; + readonly esmModuleLocation: URI | undefined; + readonly label: string | undefined; } let webWorkerWarningLogged = false; @@ -50,6 +66,7 @@ class RequestMessage { constructor( public readonly vsWorker: number, public readonly req: string, + public readonly channel: string, public readonly method: string, public readonly args: any[] ) { } @@ -68,6 +85,7 @@ class SubscribeEventMessage { constructor( public readonly vsWorker: number, public readonly req: string, + public readonly channel: string, public readonly eventName: string, public readonly arg: any ) { } @@ -96,8 +114,8 @@ interface IMessageReply { interface IMessageHandler { sendMessage(msg: any, transfer?: ArrayBuffer[]): void; - handleMessage(method: string, args: any[]): Promise; - handleEvent(eventName: string, arg: any): Event; + handleMessage(channel: string, method: string, args: any[]): Promise; + handleEvent(channel: string, eventName: string, arg: any): Event; } class SimpleWorkerProtocol { @@ -122,24 +140,24 @@ class SimpleWorkerProtocol { this._workerId = workerId; } - public sendMessage(method: string, args: any[]): Promise { + public sendMessage(channel: string, method: string, args: any[]): Promise { const req = String(++this._lastSentReq); return new Promise((resolve, reject) => { this._pendingReplies[req] = { resolve: resolve, reject: reject }; - this._send(new RequestMessage(this._workerId, req, method, args)); + this._send(new RequestMessage(this._workerId, req, channel, method, args)); }); } - public listen(eventName: string, arg: any): Event { + public listen(channel: string, eventName: string, arg: any): Event { let req: string | null = null; const emitter = new Emitter({ onWillAddFirstListener: () => { req = String(++this._lastSentReq); this._pendingEmitters.set(req, emitter); - this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg)); + this._send(new SubscribeEventMessage(this._workerId, req, channel, eventName, arg)); }, onDidRemoveLastListener: () => { this._pendingEmitters.delete(req!); @@ -160,6 +178,29 @@ class SimpleWorkerProtocol { this._handleMessage(message); } + public createProxyToRemoteChannel(channel: string, sendMessageBarrier?: () => Promise): T { + const handler = { + get: (target: any, name: PropertyKey) => { + if (typeof name === 'string' && !target[name]) { + if (propertyIsDynamicEvent(name)) { // onDynamic... + target[name] = (arg: any): Event => { + return this.listen(channel, name, arg); + }; + } else if (propertyIsEvent(name)) { // on... + target[name] = this.listen(channel, name, undefined); + } else if (name.charCodeAt(0) === CharCode.DollarSign) { // $... + target[name] = async (...myArgs: any[]) => { + await sendMessageBarrier?.(); + return this.sendMessage(channel, name, myArgs); + }; + } + } + return target[name]; + } + }; + return new Proxy(Object.create(null), handler); + } + private _handleMessage(msg: Message): void { switch (msg.type) { case MessageType.Reply: @@ -201,7 +242,7 @@ class SimpleWorkerProtocol { private _handleRequestMessage(requestMessage: RequestMessage): void { const req = requestMessage.req; - const result = this._handler.handleMessage(requestMessage.method, requestMessage.args); + const result = this._handler.handleMessage(requestMessage.channel, requestMessage.method, requestMessage.args); result.then((r) => { this._send(new ReplyMessage(this._workerId, req, r, undefined)); }, (e) => { @@ -215,7 +256,7 @@ class SimpleWorkerProtocol { private _handleSubscribeEventMessage(msg: SubscribeEventMessage): void { const req = msg.req; - const disposable = this._handler.handleEvent(msg.eventName, msg.arg)((event) => { + const disposable = this._handler.handleEvent(msg.channel, msg.eventName, msg.arg)((event) => { this._send(new EventMessage(this._workerId, req, event)); }); this._pendingEvents.set(req, disposable); @@ -255,35 +296,60 @@ class SimpleWorkerProtocol { } } +type ProxiedMethodName = (`$${string}` | `on${string}`); + +export type Proxied = { [K in keyof T]: T[K] extends (...args: infer A) => infer R + ? ( + K extends ProxiedMethodName + ? (...args: A) => Promise> + : never + ) + : never +}; + export interface IWorkerClient { - getProxyObject(): Promise; + proxy: Proxied; dispose(): void; + setChannel(channel: string, handler: T): void; + getChannel(channel: string): Proxied; +} + +export interface IWorkerServer { + setChannel(channel: string, handler: T): void; + getChannel(channel: string): Proxied; } /** * Main thread side */ -export class SimpleWorkerClient extends Disposable implements IWorkerClient { +export class SimpleWorkerClient extends Disposable implements IWorkerClient { private readonly _worker: IWorker; - private readonly _onModuleLoaded: Promise; + private readonly _onModuleLoaded: Promise; private readonly _protocol: SimpleWorkerProtocol; - private readonly _lazyProxy: Promise; + public readonly proxy: Proxied; + private readonly _localChannels: Map = new Map(); + private readonly _remoteChannels: Map = new Map(); - constructor(workerFactory: IWorkerFactory, moduleId: string, host: H) { + constructor( + workerFactory: IWorkerFactory, + workerDescriptor: IWorkerDescriptor, + ) { super(); - let lazyProxyReject: ((err: any) => void) | null = null; - this._worker = this._register(workerFactory.create( - 'vs/base/common/worker/simpleWorker', + { + amdModuleId: 'vs/base/common/worker/simpleWorker', + esmModuleLocation: workerDescriptor.esmModuleLocation, + label: workerDescriptor.label + }, (msg: Message) => { this._protocol.handleMessage(msg); }, (err: any) => { // in Firefox, web workers fail lazily :( // we will reject the proxy - lazyProxyReject?.(err); + onUnexpectedError(err); } )); @@ -291,33 +357,11 @@ export class SimpleWorkerClient extends Disp sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { this._worker.postMessage(msg, transfer); }, - handleMessage: (method: string, args: any[]): Promise => { - if (typeof (host as any)[method] !== 'function') { - return Promise.reject(new Error('Missing method ' + method + ' on main thread host.')); - } - - try { - return Promise.resolve((host as any)[method].apply(host, args)); - } catch (e) { - return Promise.reject(e); - } + handleMessage: (channel: string, method: string, args: any[]): Promise => { + return this._handleMessage(channel, method, args); }, - handleEvent: (eventName: string, arg: any): Event => { - if (propertyIsDynamicEvent(eventName)) { - const event = (host as any)[eventName].call(host, arg); - if (typeof event !== 'function') { - throw new Error(`Missing dynamic event ${eventName} on main thread host.`); - } - return event; - } - if (propertyIsEvent(eventName)) { - const event = (host as any)[eventName]; - if (typeof event !== 'function') { - throw new Error(`Missing event ${eventName} on main thread host.`); - } - return event; - } - throw new Error(`Malformed event name ${eventName}`); + handleEvent: (channel: string, eventName: string, arg: any): Event => { + return this._handleEvent(channel, eventName, arg); } }); this._protocol.setWorkerId(this._worker.getId()); @@ -334,45 +378,67 @@ export class SimpleWorkerClient extends Disp loaderConfiguration = (globalThis as any).requirejs.s.contexts._.config; } - const hostMethods = getAllMethodNames(host); - // Send initialize message - this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [ + this._onModuleLoaded = this._protocol.sendMessage(DEFAULT_CHANNEL, INITIALIZE, [ this._worker.getId(), JSON.parse(JSON.stringify(loaderConfiguration)), - moduleId, - hostMethods, + workerDescriptor.amdModuleId, ]); - // Create proxy to loaded code - const proxyMethodRequest = (method: string, args: any[]): Promise => { - return this._request(method, args); - }; - const proxyListen = (eventName: string, arg: any): Event => { - return this._protocol.listen(eventName, arg); - }; - - this._lazyProxy = new Promise((resolve, reject) => { - lazyProxyReject = reject; - this._onModuleLoaded.then((availableMethods: string[]) => { - resolve(createProxyObject(availableMethods, proxyMethodRequest, proxyListen)); - }, (e) => { - reject(e); - this._onError('Worker failed to load ' + moduleId, e); - }); + this.proxy = this._protocol.createProxyToRemoteChannel(DEFAULT_CHANNEL, async () => { await this._onModuleLoaded; }); + this._onModuleLoaded.catch((e) => { + this._onError('Worker failed to load ' + workerDescriptor.amdModuleId, e); }); } - public getProxyObject(): Promise { - return this._lazyProxy; + private _handleMessage(channelName: string, method: string, args: any[]): Promise { + const channel: object | undefined = this._localChannels.get(channelName); + if (!channel) { + return Promise.reject(new Error(`Missing channel ${channelName} on main thread`)); + } + if (typeof (channel as any)[method] !== 'function') { + return Promise.reject(new Error(`Missing method ${method} on main thread channel ${channelName}`)); + } + + try { + return Promise.resolve((channel as any)[method].apply(channel, args)); + } catch (e) { + return Promise.reject(e); + } } - private _request(method: string, args: any[]): Promise { - return new Promise((resolve, reject) => { - this._onModuleLoaded.then(() => { - this._protocol.sendMessage(method, args).then(resolve, reject); - }, reject); - }); + private _handleEvent(channelName: string, eventName: string, arg: any): Event { + const channel: object | undefined = this._localChannels.get(channelName); + if (!channel) { + throw new Error(`Missing channel ${channelName} on main thread`); + } + if (propertyIsDynamicEvent(eventName)) { + const event = (channel as any)[eventName].call(channel, arg); + if (typeof event !== 'function') { + throw new Error(`Missing dynamic event ${eventName} on main thread channel ${channelName}.`); + } + return event; + } + if (propertyIsEvent(eventName)) { + const event = (channel as any)[eventName]; + if (typeof event !== 'function') { + throw new Error(`Missing event ${eventName} on main thread channel ${channelName}.`); + } + return event; + } + throw new Error(`Malformed event name ${eventName}`); + } + + public setChannel(channel: string, handler: T): void { + this._localChannels.set(channel, handler); + } + + public getChannel(channel: string): Proxied { + if (!this._remoteChannels.has(channel)) { + const inst = this._protocol.createProxyToRemoteChannel(channel, async () => { await this._onModuleLoaded; }); + this._remoteChannels.set(channel, inst); + } + return this._remoteChannels.get(channel) as Proxied; } private _onError(message: string, error?: any): void { @@ -391,65 +457,35 @@ function propertyIsDynamicEvent(name: string): boolean { return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9)); } -function createProxyObject( - methodNames: string[], - invoke: (method: string, args: unknown[]) => unknown, - proxyListen: (eventName: string, arg: any) => Event -): T { - const createProxyMethod = (method: string): () => unknown => { - return function () { - const args = Array.prototype.slice.call(arguments, 0); - return invoke(method, args); - }; - }; - const createProxyDynamicEvent = (eventName: string): (arg: any) => Event => { - return function (arg) { - return proxyListen(eventName, arg); - }; - }; - - const result = {} as T; - for (const methodName of methodNames) { - if (propertyIsDynamicEvent(methodName)) { - (result)[methodName] = createProxyDynamicEvent(methodName); - continue; - } - if (propertyIsEvent(methodName)) { - (result)[methodName] = proxyListen(methodName, undefined); - continue; - } - (result)[methodName] = createProxyMethod(methodName); - } - return result; -} - export interface IRequestHandler { _requestHandlerBrand: any; [prop: string]: any; } -export interface IRequestHandlerFactory { - (host: H): IRequestHandler; +export interface IRequestHandlerFactory { + (workerServer: IWorkerServer): IRequestHandler; } /** * Worker side */ -export class SimpleWorkerServer { +export class SimpleWorkerServer implements IWorkerServer { - private _requestHandlerFactory: IRequestHandlerFactory | null; + private _requestHandlerFactory: IRequestHandlerFactory | null; private _requestHandler: IRequestHandler | null; private _protocol: SimpleWorkerProtocol; + private readonly _localChannels: Map = new Map(); + private readonly _remoteChannels: Map = new Map(); - constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory | null) { + constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory | null) { this._requestHandlerFactory = requestHandlerFactory; this._requestHandler = null; this._protocol = new SimpleWorkerProtocol({ sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { postMessage(msg, transfer); }, - handleMessage: (method: string, args: any[]): Promise => this._handleMessage(method, args), - handleEvent: (eventName: string, arg: any): Event => this._handleEvent(eventName, arg) + handleMessage: (channel: string, method: string, args: any[]): Promise => this._handleMessage(channel, method, args), + handleEvent: (channel: string, eventName: string, arg: any): Event => this._handleEvent(channel, eventName, arg) }); } @@ -457,35 +493,40 @@ export class SimpleWorkerServer { this._protocol.handleMessage(msg); } - private _handleMessage(method: string, args: any[]): Promise { - if (method === INITIALIZE) { - return this.initialize(args[0], args[1], args[2], args[3]); + private _handleMessage(channel: string, method: string, args: any[]): Promise { + if (channel === DEFAULT_CHANNEL && method === INITIALIZE) { + return this.initialize(args[0], args[1], args[2]); } - if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') { - return Promise.reject(new Error('Missing requestHandler or method: ' + method)); + const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this._requestHandler : this._localChannels.get(channel)); + if (!requestHandler) { + return Promise.reject(new Error(`Missing channel ${channel} on worker thread`)); + } + if (typeof (requestHandler as any)[method] !== 'function') { + return Promise.reject(new Error(`Missing method ${method} on worker thread channel ${channel}`)); } try { - return Promise.resolve(this._requestHandler[method].apply(this._requestHandler, args)); + return Promise.resolve((requestHandler as any)[method].apply(requestHandler, args)); } catch (e) { return Promise.reject(e); } } - private _handleEvent(eventName: string, arg: any): Event { - if (!this._requestHandler) { - throw new Error(`Missing requestHandler`); + private _handleEvent(channel: string, eventName: string, arg: any): Event { + const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this._requestHandler : this._localChannels.get(channel)); + if (!requestHandler) { + throw new Error(`Missing channel ${channel} on worker thread`); } if (propertyIsDynamicEvent(eventName)) { - const event = (this._requestHandler as any)[eventName].call(this._requestHandler, arg); + const event = (requestHandler as any)[eventName].call(requestHandler, arg); if (typeof event !== 'function') { throw new Error(`Missing dynamic event ${eventName} on request handler.`); } return event; } if (propertyIsEvent(eventName)) { - const event = (this._requestHandler as any)[eventName]; + const event = (requestHandler as any)[eventName]; if (typeof event !== 'function') { throw new Error(`Missing event ${eventName} on request handler.`); } @@ -494,22 +535,25 @@ export class SimpleWorkerServer { throw new Error(`Malformed event name ${eventName}`); } - private initialize(workerId: number, loaderConfig: any, moduleId: string, hostMethods: string[]): Promise { - this._protocol.setWorkerId(workerId); + public setChannel(channel: string, handler: T): void { + this._localChannels.set(channel, handler); + } - const proxyMethodRequest = (method: string, args: any[]): Promise => { - return this._protocol.sendMessage(method, args); - }; - const proxyListen = (eventName: string, arg: any): Event => { - return this._protocol.listen(eventName, arg); - }; + public getChannel(channel: string): Proxied { + if (!this._remoteChannels.has(channel)) { + const inst = this._protocol.createProxyToRemoteChannel(channel); + this._remoteChannels.set(channel, inst); + } + return this._remoteChannels.get(channel) as Proxied; + } - const hostProxy = createProxyObject(hostMethods, proxyMethodRequest, proxyListen); + private async initialize(workerId: number, loaderConfig: any, moduleId: string): Promise { + this._protocol.setWorkerId(workerId); if (this._requestHandlerFactory) { // static request handler - this._requestHandler = this._requestHandlerFactory(hostProxy); - return Promise.resolve(getAllMethodNames(this._requestHandler)); + this._requestHandler = this._requestHandlerFactory(this); + return; } if (loaderConfig) { @@ -532,7 +576,18 @@ export class SimpleWorkerServer { globalThis.require.config(loaderConfig); } - return new Promise((resolve, reject) => { + if (isESM) { + const url = FileAccess.asBrowserUri(`${moduleId}.js` as AppResourcePath).toString(true); + return import(`${url}`).then((module: { create: IRequestHandlerFactory }) => { + this._requestHandler = module.create(this); + + if (!this._requestHandler) { + throw new Error(`No RequestHandler!`); + } + }); + } + + return new Promise((resolve, reject) => { // Use the global require to be sure to get the global config // ESM-comment-begin @@ -542,24 +597,24 @@ export class SimpleWorkerServer { // const req = globalThis.require; // ESM-uncomment-end - req([moduleId], (module: { create: IRequestHandlerFactory }) => { - this._requestHandler = module.create(hostProxy); + req([moduleId], (module: { create: IRequestHandlerFactory }) => { + this._requestHandler = module.create(this); if (!this._requestHandler) { reject(new Error(`No RequestHandler!`)); return; } - resolve(getAllMethodNames(this._requestHandler)); + resolve(); }, reject); }); } } /** - * Called on the worker side + * Defines the worker entry point. Must be exported and named `create`. * @skipMangle */ -export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer { +export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer { return new SimpleWorkerServer(postMessage, null); } diff --git a/patched-vscode/src/vs/base/common/worker/simpleWorkerBootstrap.ts b/patched-vscode/src/vs/base/common/worker/simpleWorkerBootstrap.ts new file mode 100644 index 00000000..c776f733 --- /dev/null +++ b/patched-vscode/src/vs/base/common/worker/simpleWorkerBootstrap.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRequestHandlerFactory, SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker'; + +type MessageEvent = { + data: any; +}; + +declare const globalThis: { + postMessage: (message: any) => void; + onmessage: (event: MessageEvent) => void; +}; + +let initialized = false; + +function initialize(factory: IRequestHandlerFactory) { + if (initialized) { + return; + } + initialized = true; + + const simpleWorker = new SimpleWorkerServer( + msg => globalThis.postMessage(msg), + (workerServer) => factory(workerServer) + ); + + globalThis.onmessage = (e: MessageEvent) => { + simpleWorker.onmessage(e.data); + }; +} + +export function bootstrapSimpleWorker(factory: IRequestHandlerFactory) { + globalThis.onmessage = (_e: MessageEvent) => { + // Ignore first message in this case and initialize if not yet initialized + if (!initialized) { + initialize(factory); + } + }; +} diff --git a/patched-vscode/src/vs/base/node/extpath.ts b/patched-vscode/src/vs/base/node/extpath.ts index a7ec9cf6..eb91392e 100644 --- a/patched-vscode/src/vs/base/node/extpath.ts +++ b/patched-vscode/src/vs/base/node/extpath.ts @@ -119,7 +119,7 @@ export async function realpath(path: string): Promise { // to not resolve links but to simply see if the path is read accessible or not. const normalizedPath = normalizePath(path); - await Promises.access(normalizedPath, fs.constants.R_OK); + await fs.promises.access(normalizedPath, fs.constants.R_OK); return normalizedPath; } diff --git a/patched-vscode/src/vs/base/node/languagePacks.d.ts b/patched-vscode/src/vs/base/node/languagePacks.d.ts deleted file mode 100644 index 5dd1b1f1..00000000 --- a/patched-vscode/src/vs/base/node/languagePacks.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export interface NLSConfiguration { - locale: string; - osLocale: string; - availableLanguages: { - [key: string]: string; - }; - pseudo?: boolean; - _languagePackSupport?: boolean; -} - -export interface InternalNLSConfiguration extends NLSConfiguration { - _languagePackId: string; - _translationsConfigFile: string; - _cacheRoot: string; - _resolvedLanguagePackCoreLocation: string; - _corruptedFile: string; - _languagePackSupport?: boolean; -} - -export function getNLSConfiguration(commit: string | undefined, userDataPath: string, metaDataFile: string, locale: string, osLocale: string): Promise; diff --git a/patched-vscode/src/vs/base/node/languagePacks.js b/patched-vscode/src/vs/base/node/languagePacks.js deleted file mode 100644 index 0d3d1acf..00000000 --- a/patched-vscode/src/vs/base/node/languagePacks.js +++ /dev/null @@ -1,264 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/// - -//@ts-check -(function () { - 'use strict'; - - /** - * @param {typeof import('path')} path - * @param {typeof import('fs')} fs - * @param {typeof import('../common/performance')} perf - */ - function factory(path, fs, perf) { - - /** - * @param {string} file - * @returns {Promise} - */ - function exists(file) { - return new Promise(c => fs.exists(file, c)); - } - - /** - * @param {string} file - * @returns {Promise} - */ - function touch(file) { - return new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); }); - } - - /** - * @param {string} dir - * @returns {Promise} - */ - function mkdirp(dir) { - return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir))); - } - - /** - * @param {string} location - * @returns {Promise} - */ - function rimraf(location) { - return new Promise((c, e) => fs.rm(location, { recursive: true, force: true, maxRetries: 3 }, err => err ? e(err) : c())); - } - - /** - * @param {string} file - * @returns {Promise} - */ - function readFile(file) { - return new Promise((c, e) => fs.readFile(file, 'utf8', (err, data) => err ? e(err) : c(data))); - } - - /** - * @param {string} file - * @param {string} content - * @returns {Promise} - */ - function writeFile(file, content) { - return new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c())); - } - - /** - * @param {string} userDataPath - * @returns {Promise} - */ - async function getLanguagePackConfigurations(userDataPath) { - const configFile = path.join(userDataPath, 'languagepacks.json'); - try { - return JSON.parse(await readFile(configFile)); - } catch (err) { - // Do nothing. If we can't read the file we have no - // language pack config. - } - return undefined; - } - - /** - * @param {object} config - * @param {string | undefined} locale - */ - function resolveLanguagePackLocale(config, locale) { - try { - while (locale) { - if (config[locale]) { - return locale; - } else { - const index = locale.lastIndexOf('-'); - if (index > 0) { - locale = locale.substring(0, index); - } else { - return undefined; - } - } - } - } catch (err) { - console.error('Resolving language pack configuration failed.', err); - } - return undefined; - } - - /** - * @param {string | undefined} commit - * @param {string} userDataPath - * @param {string} metaDataFile - * @param {string} locale - * @param {string} osLocale - * @returns {Promise} - */ - function getNLSConfiguration(commit, userDataPath, metaDataFile, locale, osLocale) { - const defaultResult = function (locale) { - perf.mark('code/didGenerateNls'); - return Promise.resolve({ locale, osLocale, availableLanguages: {} }); - }; - - perf.mark('code/willGenerateNls'); - - if (locale === 'pseudo') { - return Promise.resolve({ locale, osLocale, availableLanguages: {}, pseudo: true }); - } - - if (process.env['VSCODE_DEV']) { - return Promise.resolve({ locale, osLocale, availableLanguages: {} }); - } - - // We have a built version so we have extracted nls file. Try to find - // the right file to use. - - // Check if we have an English or English US locale. If so fall to default since that is our - // English translation (we don't ship *.nls.en.json files) - if (locale && (locale === 'en' || locale === 'en-us')) { - return Promise.resolve({ locale, osLocale, availableLanguages: {} }); - } - - const initialLocale = locale; - - try { - if (!commit) { - return defaultResult(initialLocale); - } - return getLanguagePackConfigurations(userDataPath).then(configs => { - if (!configs) { - return defaultResult(initialLocale); - } - const resolvedLocale = resolveLanguagePackLocale(configs, locale); - if (!resolvedLocale) { - return defaultResult(initialLocale); - } - locale = resolvedLocale; - const packConfig = configs[locale]; - let mainPack; - if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') { - return defaultResult(initialLocale); - } - return exists(mainPack).then(fileExists => { - if (!fileExists) { - return defaultResult(initialLocale); - } - const packId = packConfig.hash + '.' + locale; - const cacheRoot = path.join(userDataPath, 'clp', packId); - const coreLocation = path.join(cacheRoot, commit); - const translationsConfigFile = path.join(cacheRoot, 'tcf.json'); - const corruptedFile = path.join(cacheRoot, 'corrupted.info'); - const result = { - locale: initialLocale, - osLocale, - availableLanguages: { '*': locale }, - _languagePackId: packId, - _translationsConfigFile: translationsConfigFile, - _cacheRoot: cacheRoot, - _resolvedLanguagePackCoreLocation: coreLocation, - _corruptedFile: corruptedFile - }; - return exists(corruptedFile).then(corrupted => { - // The nls cache directory is corrupted. - let toDelete; - if (corrupted) { - toDelete = rimraf(cacheRoot); - } else { - toDelete = Promise.resolve(undefined); - } - return toDelete.then(() => { - return exists(coreLocation).then(fileExists => { - if (fileExists) { - // We don't wait for this. No big harm if we can't touch - touch(coreLocation).catch(() => { }); - perf.mark('code/didGenerateNls'); - return result; - } - return mkdirp(coreLocation).then(() => { - return Promise.all([readFile(metaDataFile), readFile(mainPack)]); - }).then(values => { - const metadata = JSON.parse(values[0]); - const packData = JSON.parse(values[1]).contents; - const bundles = Object.keys(metadata.bundles); - const writes = []; - for (const bundle of bundles) { - const modules = metadata.bundles[bundle]; - const target = Object.create(null); - for (const module of modules) { - const keys = metadata.keys[module]; - const defaultMessages = metadata.messages[module]; - const translations = packData[module]; - let targetStrings; - if (translations) { - targetStrings = []; - for (let i = 0; i < keys.length; i++) { - const elem = keys[i]; - const key = typeof elem === 'string' ? elem : elem.key; - let translatedMessage = translations[key]; - if (translatedMessage === undefined) { - translatedMessage = defaultMessages[i]; - } - targetStrings.push(translatedMessage); - } - } else { - targetStrings = defaultMessages; - } - target[module] = targetStrings; - } - writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target))); - } - writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations))); - return Promise.all(writes); - }).then(() => { - perf.mark('code/didGenerateNls'); - return result; - }).catch(err => { - console.error('Generating translation files failed.', err); - return defaultResult(locale); - }); - }); - }); - }); - }); - }); - } catch (err) { - console.error('Generating translation files failed.', err); - return defaultResult(locale); - } - } - - return { - getNLSConfiguration - }; - } - - if (typeof define === 'function') { - // amd - define(['path', 'fs', 'vs/base/common/performance'], function (/** @type {typeof import('path')} */ path, /** @type {typeof import('fs')} */ fs, /** @type {typeof import('../common/performance')} */ perf) { return factory(path, fs, perf); }); - } else if (typeof module === 'object' && typeof module.exports === 'object') { - const path = require('path'); - const fs = require('fs'); - const perf = require('../common/performance'); - module.exports = factory(path, fs, perf); - } else { - throw new Error('Unknown context'); - } -}()); diff --git a/patched-vscode/src/vs/base/node/nls.d.ts b/patched-vscode/src/vs/base/node/nls.d.ts new file mode 100644 index 00000000..94ef503e --- /dev/null +++ b/patched-vscode/src/vs/base/node/nls.d.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { INLSConfiguration } from 'vs/nls'; + +export interface IResolveNLSConfigurationContext { + + /** + * Location where `nls.messages.json` and `nls.keys.json` are stored. + */ + readonly nlsMetadataPath: string; + + /** + * Path to the user data directory. Used as a cache for + * language packs converted to the format we need. + */ + readonly userDataPath: string; + + /** + * Commit of the running application. Can be `undefined` + * when not built. + */ + readonly commit: string | undefined; + + /** + * Locale as defined in `argv.json` or `app.getLocale()`. + */ + readonly userLocale: string; + + /** + * Locale as defined by the OS (e.g. `app.getPreferredSystemLanguages()`). + */ + readonly osLocale: string; +} + +export function resolveNLSConfiguration(context: IResolveNLSConfigurationContext): Promise; diff --git a/patched-vscode/src/vs/base/node/nls.js b/patched-vscode/src/vs/base/node/nls.js new file mode 100644 index 00000000..3752b1ff --- /dev/null +++ b/patched-vscode/src/vs/base/node/nls.js @@ -0,0 +1,274 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// + +//@ts-check +'use strict'; + +/** + * @import { INLSConfiguration, ILanguagePacks } from '../../nls' + * @import { IResolveNLSConfigurationContext } from './nls' + */ + +// ESM-uncomment-begin +// import * as path from 'path'; +// import * as fs from 'fs'; +// import * as perf from '../common/performance.js'; +// +// /** @type any */ +// const module = { exports: {} }; +// ESM-uncomment-end + +(function () { + + /** + * @param {typeof import('path')} path + * @param {typeof import('fs')} fs + * @param {typeof import('../common/performance')} perf + */ + function factory(path, fs, perf) { + + //#region fs helpers + + /** + * @param {string} path + */ + async function exists(path) { + try { + await fs.promises.access(path); + + return true; + } catch { + return false; + } + } + + /** + * @param {string} path + */ + function touch(path) { + const date = new Date(); + return fs.promises.utimes(path, date, date); + } + + //#endregion + + /** + * The `languagepacks.json` file is a JSON file that contains all metadata + * about installed language extensions per language. Specifically, for + * core (`vscode`) and all extensions it supports, it points to the related + * translation files. + * + * The file is updated whenever a new language pack is installed or removed. + * + * @param {string} userDataPath + * @returns {Promise} + */ + async function getLanguagePackConfigurations(userDataPath) { + const configFile = path.join(userDataPath, 'languagepacks.json'); + try { + return JSON.parse(await fs.promises.readFile(configFile, 'utf-8')); + } catch (err) { + return undefined; // Do nothing. If we can't read the file we have no language pack config. + } + } + + /** + * @param {ILanguagePacks} languagePacks + * @param {string | undefined} locale + */ + function resolveLanguagePackLanguage(languagePacks, locale) { + try { + while (locale) { + if (languagePacks[locale]) { + return locale; + } + + const index = locale.lastIndexOf('-'); + if (index > 0) { + locale = locale.substring(0, index); + } else { + return undefined; + } + } + } catch (error) { + console.error('Resolving language pack configuration failed.', error); + } + + return undefined; + } + + /** + * @param {string} userLocale + * @param {string} osLocale + * @param {string} nlsMetadataPath + * @returns {INLSConfiguration} + */ + function defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath) { + perf.mark('code/didGenerateNls'); + + return { + userLocale, + osLocale, + resolvedLanguage: 'en', + defaultMessagesFile: path.join(nlsMetadataPath, 'nls.messages.json'), + + // NLS: below 2 are a relic from old times only used by vscode-nls and deprecated + locale: userLocale, + availableLanguages: {} + }; + } + + /** + * @param {IResolveNLSConfigurationContext} context + * @returns {Promise} + */ + async function resolveNLSConfiguration({ userLocale, osLocale, userDataPath, commit, nlsMetadataPath }) { + perf.mark('code/willGenerateNls'); + + if ( + process.env['VSCODE_DEV'] || + userLocale === 'pseudo' || + userLocale.startsWith('en') || + !commit || + !userDataPath + ) { + return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath); + } + + try { + const languagePacks = await getLanguagePackConfigurations(userDataPath); + if (!languagePacks) { + return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath); + } + + const resolvedLanguage = resolveLanguagePackLanguage(languagePacks, userLocale); + if (!resolvedLanguage) { + return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath); + } + + const languagePack = languagePacks[resolvedLanguage]; + const mainLanguagePackPath = languagePack?.translations?.['vscode']; + if ( + !languagePack || + typeof languagePack.hash !== 'string' || + !languagePack.translations || + typeof mainLanguagePackPath !== 'string' || + !(await exists(mainLanguagePackPath)) + ) { + return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath); + } + + const languagePackId = `${languagePack.hash}.${resolvedLanguage}`; + const globalLanguagePackCachePath = path.join(userDataPath, 'clp', languagePackId); + const commitLanguagePackCachePath = path.join(globalLanguagePackCachePath, commit); + const languagePackMessagesFile = path.join(commitLanguagePackCachePath, 'nls.messages.json'); + const translationsConfigFile = path.join(globalLanguagePackCachePath, 'tcf.json'); + const languagePackCorruptMarkerFile = path.join(globalLanguagePackCachePath, 'corrupted.info'); + + if (await exists(languagePackCorruptMarkerFile)) { + await fs.promises.rm(globalLanguagePackCachePath, { recursive: true, force: true, maxRetries: 3 }); // delete corrupted cache folder + } + + /** @type {INLSConfiguration} */ + const result = { + userLocale, + osLocale, + resolvedLanguage, + defaultMessagesFile: path.join(nlsMetadataPath, 'nls.messages.json'), + languagePack: { + translationsConfigFile, + messagesFile: languagePackMessagesFile, + corruptMarkerFile: languagePackCorruptMarkerFile + }, + + // NLS: below properties are a relic from old times only used by vscode-nls and deprecated + locale: userLocale, + availableLanguages: { '*': resolvedLanguage }, + _languagePackId: languagePackId, + _languagePackSupport: true, + _translationsConfigFile: translationsConfigFile, + _cacheRoot: globalLanguagePackCachePath, + _resolvedLanguagePackCoreLocation: commitLanguagePackCachePath, + _corruptedFile: languagePackCorruptMarkerFile + }; + + if (await exists(commitLanguagePackCachePath)) { + touch(commitLanguagePackCachePath).catch(() => { }); // We don't wait for this. No big harm if we can't touch + perf.mark('code/didGenerateNls'); + return result; + } + + /** @type {[unknown, Array<[string, string[]]>, string[], { contents: Record> }]} */ + // ^moduleId ^nlsKeys ^moduleId ^nlsKey ^nlsValue + const [ + , + nlsDefaultKeys, + nlsDefaultMessages, + nlsPackdata + ] = await Promise.all([ + fs.promises.mkdir(commitLanguagePackCachePath, { recursive: true }), + JSON.parse(await fs.promises.readFile(path.join(nlsMetadataPath, 'nls.keys.json'), 'utf-8')), + JSON.parse(await fs.promises.readFile(path.join(nlsMetadataPath, 'nls.messages.json'), 'utf-8')), + JSON.parse(await fs.promises.readFile(mainLanguagePackPath, 'utf-8')) + ]); + + /** @type {string[]} */ + const nlsResult = []; + + // We expect NLS messages to be in a flat array in sorted order as they + // where produced during build time. We use `nls.keys.json` to know the + // right order and then lookup the related message from the translation. + // If a translation does not exist, we fallback to the default message. + + let nlsIndex = 0; + for (const [moduleId, nlsKeys] of nlsDefaultKeys) { + const moduleTranslations = nlsPackdata.contents[moduleId]; + for (const nlsKey of nlsKeys) { + nlsResult.push(moduleTranslations?.[nlsKey] || nlsDefaultMessages[nlsIndex]); + nlsIndex++; + } + } + + await Promise.all([ + fs.promises.writeFile(languagePackMessagesFile, JSON.stringify(nlsResult), 'utf-8'), + fs.promises.writeFile(translationsConfigFile, JSON.stringify(languagePack.translations), 'utf-8') + ]); + + perf.mark('code/didGenerateNls'); + + return result; + } catch (error) { + console.error('Generating translation files failed.', error); + } + + return defaultNLSConfiguration(userLocale, osLocale, nlsMetadataPath); + } + + return { + resolveNLSConfiguration + }; + } + + if (typeof define === 'function') { + // amd + define(['path', 'fs', 'vs/base/common/performance'], function (/** @type {typeof import('path')} */ path, /** @type {typeof import('fs')} */ fs, /** @type {typeof import('../common/performance')} */ perf) { return factory(path, fs, perf); }); + } else if (typeof module === 'object' && typeof module.exports === 'object') { + // commonjs + // ESM-comment-begin + const path = require('path'); + const fs = require('fs'); + const perf = require('../common/performance'); + // ESM-comment-end + module.exports = factory(path, fs, perf); + } else { + throw new Error('vs/base/node/nls defined in UNKNOWN context (neither requirejs or commonjs)'); + } +})(); + +// ESM-uncomment-begin +// export const resolveNLSConfiguration = module.exports.resolveNLSConfiguration; +// ESM-uncomment-end diff --git a/patched-vscode/src/vs/base/node/osDisplayProtocolInfo.ts b/patched-vscode/src/vs/base/node/osDisplayProtocolInfo.ts index c028dc88..90c825bd 100644 --- a/patched-vscode/src/vs/base/node/osDisplayProtocolInfo.ts +++ b/patched-vscode/src/vs/base/node/osDisplayProtocolInfo.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { constants as FSConstants } from 'fs'; -import { access } from 'fs/promises'; +import { constants as FSConstants, promises as FSPromises } from 'fs'; import { join } from 'vs/base/common/path'; import { env } from 'vs/base/common/process'; @@ -44,7 +43,7 @@ export async function getDisplayProtocol(errorLogger: (error: any) => void): Pro const waylandServerPipe = join(xdgRuntimeDir, 'wayland-0'); try { - await access(waylandServerPipe, FSConstants.R_OK); + await FSPromises.access(waylandServerPipe, FSConstants.R_OK); // If the file exists, then the session is wayland. return DisplayProtocolType.Wayland; diff --git a/patched-vscode/src/vs/base/node/osReleaseInfo.ts b/patched-vscode/src/vs/base/node/osReleaseInfo.ts index f72b0fe8..60101f54 100644 --- a/patched-vscode/src/vs/base/node/osReleaseInfo.ts +++ b/patched-vscode/src/vs/base/node/osReleaseInfo.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { constants as FSConstants } from 'fs'; -import { open, FileHandle } from 'fs/promises'; +import { constants as FSConstants, promises as FSPromises } from 'fs'; import { createInterface as readLines } from 'readline'; import * as Platform from 'vs/base/common/platform'; @@ -22,10 +21,10 @@ export async function getOSReleaseInfo(errorLogger: (error: any) => void): Promi // Extract release information on linux based systems // using the identifiers specified in // https://www.freedesktop.org/software/systemd/man/os-release.html - let handle: FileHandle | undefined; + let handle: FSPromises.FileHandle | undefined; for (const filePath of ['/etc/os-release', '/usr/lib/os-release', '/etc/lsb-release']) { try { - handle = await open(filePath, FSConstants.R_OK); + handle = await FSPromises.open(filePath, FSConstants.R_OK); break; } catch (err) { } } diff --git a/patched-vscode/src/vs/base/node/pfs.ts b/patched-vscode/src/vs/base/node/pfs.ts index 025df98b..52e8c9e8 100644 --- a/patched-vscode/src/vs/base/node/pfs.ts +++ b/patched-vscode/src/vs/base/node/pfs.ts @@ -60,14 +60,6 @@ async function rimraf(path: string, mode = RimRafMode.UNLINK, moveToPath?: strin async function rimrafMove(path: string, moveToPath = randomPath(tmpdir())): Promise { try { try { - // Intentionally using `fs.promises` here to skip - // the patched graceful-fs method that can result - // in very long running `rename` calls when the - // folder is locked by a file watcher. We do not - // really want to slow down this operation more - // than necessary and we have a fallback to delete - // via unlink. - // https://github.com/microsoft/vscode/issues/139908 await fs.promises.rename(path, moveToPath); } catch (error) { if (error.code === 'ENOENT') { @@ -87,7 +79,7 @@ async function rimrafMove(path: string, moveToPath = randomPath(tmpdir())): Prom } async function rimrafUnlink(path: string): Promise { - return promisify(fs.rm)(path, { recursive: true, force: true, maxRetries: 3 }); + return fs.promises.rm(path, { recursive: true, force: true, maxRetries: 3 }); } export function rimrafSync(path: string): void { @@ -118,12 +110,12 @@ export interface IDirent { async function readdir(path: string): Promise; async function readdir(path: string, options: { withFileTypes: true }): Promise; async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> { - return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : promisify(fs.readdir)(path))); + return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : fs.promises.readdir(path))); } async function safeReaddirWithFileTypes(path: string): Promise { try { - return await promisify(fs.readdir)(path, { withFileTypes: true }); + return await fs.promises.readdir(path, { withFileTypes: true }); } catch (error) { console.warn('[node.js fs] readdir with filetypes failed with error: ', error); } @@ -142,7 +134,7 @@ async function safeReaddirWithFileTypes(path: string): Promise { let isSymbolicLink = false; try { - const lstat = await Promises.lstat(join(path, child)); + const lstat = await fs.promises.lstat(join(path, child)); isFile = lstat.isFile(); isDirectory = lstat.isDirectory(); @@ -267,7 +259,7 @@ export namespace SymlinkSupport { // First stat the link let lstats: fs.Stats | undefined; try { - lstats = await Promises.lstat(path); + lstats = await fs.promises.lstat(path); // Return early if the stat is not a symbolic link at all if (!lstats.isSymbolicLink()) { @@ -280,7 +272,7 @@ export namespace SymlinkSupport { // If the stat is a symbolic link or failed to stat, use fs.stat() // which for symbolic links will stat the target they point to try { - const stats = await Promises.stat(path); + const stats = await fs.promises.stat(path); return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined }; } catch (error) { @@ -295,7 +287,7 @@ export namespace SymlinkSupport { // are not supported (https://github.com/nodejs/node/issues/36790) if (isWindows && error.code === 'EACCES') { try { - const stats = await Promises.stat(await Promises.readlink(path)); + const stats = await fs.promises.stat(await fs.promises.readlink(path)); return { stat: stats, symbolicLink: { dangling: false } }; } catch (error) { @@ -493,7 +485,7 @@ function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptio * - allows to move across multiple disks * - attempts to retry the operation for certain error codes on Windows */ -async function rename(source: string, target: string, windowsRetryTimeout: number | false = 60000 /* matches graceful-fs */): Promise { +async function rename(source: string, target: string, windowsRetryTimeout: number | false = 60000): Promise { if (source === target) { return; // simulate node.js behaviour here and do a no-op if paths match } @@ -501,12 +493,10 @@ async function rename(source: string, target: string, windowsRetryTimeout: numbe try { if (isWindows && typeof windowsRetryTimeout === 'number') { // On Windows, a rename can fail when either source or target - // is locked by AV software. We do leverage graceful-fs to iron - // out these issues, however in case the target file exists, - // graceful-fs will immediately return without retry for fs.rename(). + // is locked by AV software. await renameWithRetry(source, target, Date.now(), windowsRetryTimeout); } else { - await promisify(fs.rename)(source, target); + await fs.promises.rename(source, target); } } catch (error) { // In two cases we fallback to classic copy and delete: @@ -528,7 +518,7 @@ async function rename(source: string, target: string, windowsRetryTimeout: numbe async function renameWithRetry(source: string, target: string, startTime: number, retryTimeout: number, attempt = 0): Promise { try { - return await promisify(fs.rename)(source, target); + return await fs.promises.rename(source, target); } catch (error) { if (error.code !== 'EACCES' && error.code !== 'EPERM' && error.code !== 'EBUSY') { throw error; // only for errors we think are temporary @@ -630,7 +620,7 @@ async function doCopy(source: string, target: string, payload: ICopyPayload): Pr async function doCopyDirectory(source: string, target: string, mode: number, payload: ICopyPayload): Promise { // Create folder - await Promises.mkdir(target, { recursive: true, mode }); + await fs.promises.mkdir(target, { recursive: true, mode }); // Copy each file recursively const files = await readdir(source); @@ -642,16 +632,16 @@ async function doCopyDirectory(source: string, target: string, mode: number, pay async function doCopyFile(source: string, target: string, mode: number): Promise { // Copy file - await Promises.copyFile(source, target); + await fs.promises.copyFile(source, target); // restore mode (https://github.com/nodejs/node/issues/1104) - await Promises.chmod(target, mode); + await fs.promises.chmod(target, mode); } async function doCopySymlink(source: string, target: string, payload: ICopyPayload): Promise { // Figure out link target - let linkTarget = await Promises.readlink(source); + let linkTarget = await fs.promises.readlink(source); // Special case: the symlink points to a target that is // actually within the path that is being copied. In that @@ -662,7 +652,7 @@ async function doCopySymlink(source: string, target: string, payload: ICopyPaylo } // Create symlink - await Promises.symlink(linkTarget, target); + await fs.promises.symlink(linkTarget, target); } //#endregion @@ -670,31 +660,19 @@ async function doCopySymlink(source: string, target: string, payload: ICopyPaylo //#region Promise based fs methods /** - * Prefer this helper class over the `fs.promises` API to - * enable `graceful-fs` to function properly. Given issue - * https://github.com/isaacs/node-graceful-fs/issues/160 it - * is evident that the module only takes care of the non-promise - * based fs methods. + * Some low level `fs` methods provided as `Promises` similar to + * `fs.promises` but with notable differences, either implemented + * by us or by restoring the original callback based behavior. * - * Another reason is `realpath` being entirely different in - * the promise based implementation compared to the other - * one (https://github.com/microsoft/vscode/issues/118562) - * - * Note: using getters for a reason, since `graceful-fs` - * patching might kick in later after modules have been - * loaded we need to defer access to fs methods. - * (https://github.com/microsoft/vscode/issues/124176) + * At least `realpath` is implemented differently in the promise + * based implementation compared to the callback based one. The + * promise based implementation actually calls `fs.realpath.native`. + * (https://github.com/microsoft/vscode/issues/118562) */ export const Promises = new class { //#region Implemented by node.js - get access() { return promisify(fs.access); } - - get stat() { return promisify(fs.stat); } - get lstat() { return promisify(fs.lstat); } - get utimes() { return promisify(fs.utimes); } - get read() { // Not using `promisify` here for a reason: the return @@ -713,7 +691,6 @@ export const Promises = new class { }); }; } - get readFile() { return promisify(fs.readFile); } get write() { @@ -734,27 +711,12 @@ export const Promises = new class { }; } - get appendFile() { return promisify(fs.appendFile); } - - get fdatasync() { return promisify(fs.fdatasync); } - get truncate() { return promisify(fs.truncate); } - - get copyFile() { return promisify(fs.copyFile); } - - get open() { return promisify(fs.open); } - get close() { return promisify(fs.close); } - - get symlink() { return promisify(fs.symlink); } - get readlink() { return promisify(fs.readlink); } - - get chmod() { return promisify(fs.chmod); } - - get mkdir() { return promisify(fs.mkdir); } + get fdatasync() { return promisify(fs.fdatasync); } // not exposed as API in 20.x yet - get unlink() { return promisify(fs.unlink); } - get rmdir() { return promisify(fs.rmdir); } + get open() { return promisify(fs.open); } // changed to return `FileHandle` in promise API + get close() { return promisify(fs.close); } // not exposed as API due to the `FileHandle` return type of `open` - get realpath() { return promisify(fs.realpath); } + get realpath() { return promisify(fs.realpath); } // `fs.promises.realpath` will use `fs.realpath.native` which we do not want //#endregion @@ -762,7 +724,7 @@ export const Promises = new class { async exists(path: string): Promise { try { - await Promises.access(path); + await fs.promises.access(path); return true; } catch { diff --git a/patched-vscode/src/vs/base/node/processes.ts b/patched-vscode/src/vs/base/node/processes.ts index 9c07f106..7bd20f80 100644 --- a/patched-vscode/src/vs/base/node/processes.ts +++ b/patched-vscode/src/vs/base/node/processes.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import { Stats } from 'fs'; +import { Stats, promises } from 'fs'; import * as path from 'vs/base/common/path'; import * as Platform from 'vs/base/common/platform'; import * as process from 'vs/base/common/process'; @@ -91,11 +91,11 @@ export namespace win32 { if (await pfs.Promises.exists(path)) { let statValue: Stats | undefined; try { - statValue = await pfs.Promises.stat(path); + statValue = await promises.stat(path); } catch (e) { if (e.message.startsWith('EACCES')) { // it might be symlink - statValue = await pfs.Promises.lstat(path); + statValue = await promises.lstat(path); } } return statValue ? !statValue.isDirectory() : false; diff --git a/patched-vscode/src/vs/base/node/unc.js b/patched-vscode/src/vs/base/node/unc.js index b0af4d38..3284ee4d 100644 --- a/patched-vscode/src/vs/base/node/unc.js +++ b/patched-vscode/src/vs/base/node/unc.js @@ -3,9 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +//@ts-check 'use strict'; -//@ts-check +// ESM-uncomment-begin +// /** @type any */ +// const module = { exports: {} }; +// ESM-uncomment-end (function () { function factory() { @@ -18,6 +22,7 @@ // The property `process.uncHostAllowlist` is not available in official node.js // releases, only in our own builds, so we have to probe for availability + // @ts-ignore return process.uncHostAllowlist; } @@ -114,6 +119,7 @@ return; } + // @ts-ignore process.restrictUNCAccess = false; } @@ -122,6 +128,7 @@ return true; } + // @ts-ignore return process.restrictUNCAccess === false; } @@ -144,3 +151,11 @@ console.trace('vs/base/node/unc defined in UNKNOWN context (neither requirejs or commonjs)'); } })(); + +// ESM-uncomment-begin +// export const getUNCHost = module.exports.getUNCHost; +// export const getUNCHostAllowlist = module.exports.getUNCHostAllowlist; +// export const addUNCHostToAllowlist = module.exports.addUNCHostToAllowlist; +// export const disableUNCAccessRestrictions = module.exports.disableUNCAccessRestrictions; +// export const isUNCAccessRestrictionsDisabled = module.exports.isUNCAccessRestrictionsDisabled; +// ESM-uncomment-end diff --git a/patched-vscode/src/vs/base/node/zip.ts b/patched-vscode/src/vs/base/node/zip.ts index c0d9b4b8..1295944e 100644 --- a/patched-vscode/src/vs/base/node/zip.ts +++ b/patched-vscode/src/vs/base/node/zip.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createWriteStream, WriteStream } from 'fs'; +import { createWriteStream, WriteStream, promises } from 'fs'; import { Readable } from 'stream'; import { createCancelablePromise, Sequencer } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -85,7 +85,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa istream?.destroy(); }); - return Promise.resolve(Promises.mkdir(targetDirName, { recursive: true })).then(() => new Promise((c, e) => { + return Promise.resolve(promises.mkdir(targetDirName, { recursive: true })).then(() => new Promise((c, e) => { if (token.isCancellationRequested) { return; } @@ -148,7 +148,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok // directory file names end with '/' if (/\/$/.test(fileName)) { const targetFileName = path.join(targetPath, fileName); - last = createCancelablePromise(token => Promises.mkdir(targetFileName, { recursive: true }).then(() => readNextEntry(token)).then(undefined, e)); + last = createCancelablePromise(token => promises.mkdir(targetFileName, { recursive: true }).then(() => readNextEntry(token)).then(undefined, e)); return; } diff --git a/patched-vscode/src/vs/base/parts/ipc/common/ipc.net.ts b/patched-vscode/src/vs/base/parts/ipc/common/ipc.net.ts index 1fe8ee07..39ba4f16 100644 --- a/patched-vscode/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/patched-vscode/src/vs/base/parts/ipc/common/ipc.net.ts @@ -597,6 +597,8 @@ export class Client extends IPCClient { override dispose(): void { super.dispose(); const socket = this.protocol.getSocket(); + // should be sent gracefully with a .flush(), but try to send it out as a + // last resort here if nothing else: this.protocol.sendDisconnect(); this.protocol.dispose(); socket.end(); @@ -808,6 +810,7 @@ export interface PersistentProtocolOptions { export class PersistentProtocol implements IMessagePassingProtocol { private _isReconnecting: boolean; + private _didSendDisconnect?: boolean; private _outgoingUnackMsg: Queue; private _outgoingMsgId: number; @@ -910,9 +913,12 @@ export class PersistentProtocol implements IMessagePassingProtocol { } sendDisconnect(): void { - const msg = new ProtocolMessage(ProtocolMessageType.Disconnect, 0, 0, getEmptyBuffer()); - this._socketWriter.write(msg); - this._socketWriter.flush(); + if (!this._didSendDisconnect) { + this._didSendDisconnect = true; + const msg = new ProtocolMessage(ProtocolMessageType.Disconnect, 0, 0, getEmptyBuffer()); + this._socketWriter.write(msg); + this._socketWriter.flush(); + } } sendPause(): void { diff --git a/patched-vscode/src/vs/base/parts/ipc/common/ipc.ts b/patched-vscode/src/vs/base/parts/ipc/common/ipc.ts index 6530fac0..f1bed382 100644 --- a/patched-vscode/src/vs/base/parts/ipc/common/ipc.ts +++ b/patched-vscode/src/vs/base/parts/ipc/common/ipc.ts @@ -426,18 +426,18 @@ export class ChannelServer implements IChannelServer { - this.sendResponse({ id, data, type: ResponseType.PromiseSuccess }); + this.sendResponse({ id, data, type: ResponseType.PromiseSuccess }); }, err => { if (err instanceof Error) { - this.sendResponse({ + this.sendResponse({ id, data: { message: err.message, name: err.name, - stack: err.stack ? (err.stack.split ? err.stack.split('\n') : err.stack) : undefined + stack: err.stack ? err.stack.split('\n') : undefined }, type: ResponseType.PromiseError }); } else { - this.sendResponse({ id, data: err, type: ResponseType.PromiseErrorObj }); + this.sendResponse({ id, data: err, type: ResponseType.PromiseErrorObj }); } }).finally(() => { disposable.dispose(); @@ -458,7 +458,7 @@ export class ChannelServer implements IChannelServer this.sendResponse({ id, data, type: ResponseType.EventFire })); + const disposable = event(data => this.sendResponse({ id, data, type: ResponseType.EventFire })); this.activeRequests.set(request.id, disposable); } @@ -484,7 +484,7 @@ export class ChannelServer implements IChannelServer{ + this.sendResponse({ id: request.id, data: { name: 'Unknown channel', message: `Channel name '${request.channelName}' timed out after ${this.timeoutDelay}ms`, stack: undefined }, type: ResponseType.PromiseError diff --git a/patched-vscode/src/vs/base/parts/ipc/electron-main/ipcMain.ts b/patched-vscode/src/vs/base/parts/ipc/electron-main/ipcMain.ts index c72b6e8e..a0bf9a16 100644 --- a/patched-vscode/src/vs/base/parts/ipc/electron-main/ipcMain.ts +++ b/patched-vscode/src/vs/base/parts/ipc/electron-main/ipcMain.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ipcMain as unsafeIpcMain, IpcMainEvent, IpcMainInvokeEvent } from 'electron'; +import electron from 'electron'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { VSCODE_AUTHORITY } from 'vs/base/common/network'; -type ipcMainListener = (event: IpcMainEvent, ...args: any[]) => void; +type ipcMainListener = (event: electron.IpcMainEvent, ...args: any[]) => void; class ValidatedIpcMain implements Event.NodeEventEmitter { @@ -25,7 +25,7 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { // Remember the wrapped listener so that later we can // properly implement `removeListener`. - const wrappedListener = (event: IpcMainEvent, ...args: any[]) => { + const wrappedListener = (event: electron.IpcMainEvent, ...args: any[]) => { if (this.validateEvent(channel, event)) { listener(event, ...args); } @@ -33,7 +33,7 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { this.mapListenerToWrapper.set(listener, wrappedListener); - unsafeIpcMain.on(channel, wrappedListener); + electron.ipcMain.on(channel, wrappedListener); return this; } @@ -43,7 +43,7 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { * only the next time a message is sent to `channel`, after which it is removed. */ once(channel: string, listener: ipcMainListener): this { - unsafeIpcMain.once(channel, (event: IpcMainEvent, ...args: any[]) => { + electron.ipcMain.once(channel, (event: electron.IpcMainEvent, ...args: any[]) => { if (this.validateEvent(channel, event)) { listener(event, ...args); } @@ -68,8 +68,8 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { * are serialized and only the `message` property from the original error is * provided to the renderer process. Please refer to #24427 for details. */ - handle(channel: string, listener: (event: IpcMainInvokeEvent, ...args: any[]) => Promise): this { - unsafeIpcMain.handle(channel, (event: IpcMainInvokeEvent, ...args: any[]) => { + handle(channel: string, listener: (event: electron.IpcMainInvokeEvent, ...args: any[]) => Promise): this { + electron.ipcMain.handle(channel, (event: electron.IpcMainInvokeEvent, ...args: any[]) => { if (this.validateEvent(channel, event)) { return listener(event, ...args); } @@ -84,7 +84,7 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { * Removes any handler for `channel`, if present. */ removeHandler(channel: string): this { - unsafeIpcMain.removeHandler(channel); + electron.ipcMain.removeHandler(channel); return this; } @@ -96,14 +96,14 @@ class ValidatedIpcMain implements Event.NodeEventEmitter { removeListener(channel: string, listener: ipcMainListener): this { const wrappedListener = this.mapListenerToWrapper.get(listener); if (wrappedListener) { - unsafeIpcMain.removeListener(channel, wrappedListener); + electron.ipcMain.removeListener(channel, wrappedListener); this.mapListenerToWrapper.delete(listener); } return this; } - private validateEvent(channel: string, event: IpcMainEvent | IpcMainInvokeEvent): boolean { + private validateEvent(channel: string, event: electron.IpcMainEvent | electron.IpcMainInvokeEvent): boolean { if (!channel || !channel.startsWith('vscode:')) { onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because the channel is unknown.`); return false; // unexpected channel diff --git a/patched-vscode/src/vs/base/parts/ipc/node/ipc.net.ts b/patched-vscode/src/vs/base/parts/ipc/node/ipc.net.ts index 7d57ca6c..0f3dd812 100644 --- a/patched-vscode/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/patched-vscode/src/vs/base/parts/ipc/node/ipc.net.ts @@ -17,6 +17,15 @@ import { generateUuid } from 'vs/base/common/uuid'; import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc'; import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; +/** + * Maximum time to wait for a 'close' event to fire after the socket stream + * ends. For unix domain sockets, the close event may not fire consistently + * due to what appears to be a Node.js bug. + * + * @see https://github.com/microsoft/vscode/issues/211462#issuecomment-2155471996 + */ +const socketEndTimeoutMs = 30_000; + export class NodeSocket implements ISocket { public readonly debugLabel: string; @@ -51,15 +60,20 @@ export class NodeSocket implements ISocket { }; this.socket.on('error', this._errorListener); + let endTimeoutHandle: NodeJS.Timeout | undefined; this._closeListener = (hadError: boolean) => { this.traceSocketEvent(SocketDiagnosticsEventType.Close, { hadError }); this._canWrite = false; + if (endTimeoutHandle) { + clearTimeout(endTimeoutHandle); + } }; this.socket.on('close', this._closeListener); this._endListener = () => { this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndReceived); this._canWrite = false; + endTimeoutHandle = setTimeout(() => socket.destroy(), socketEndTimeoutMs); }; this.socket.on('end', this._endListener); } diff --git a/patched-vscode/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts b/patched-vscode/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts index 00813be6..c49d9bf8 100644 --- a/patched-vscode/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts +++ b/patched-vscode/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp'; diff --git a/patched-vscode/src/vs/base/parts/ipc/test/common/ipc.test.ts b/patched-vscode/src/vs/base/parts/ipc/test/common/ipc.test.ts index 0515d24a..b9fc881a 100644 --- a/patched-vscode/src/vs/base/parts/ipc/test/common/ipc.test.ts +++ b/patched-vscode/src/vs/base/parts/ipc/test/common/ipc.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; diff --git a/patched-vscode/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts b/patched-vscode/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts index 44559562..4bb851eb 100644 --- a/patched-vscode/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts +++ b/patched-vscode/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts b/patched-vscode/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts index d761f720..9caacd5b 100644 --- a/patched-vscode/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts +++ b/patched-vscode/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; diff --git a/patched-vscode/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/patched-vscode/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 0e018300..bac8dc99 100644 --- a/patched-vscode/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/patched-vscode/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; +import sinon from 'sinon'; import { EventEmitter } from 'events'; import { AddressInfo, connect, createServer, Server, Socket } from 'net'; import { tmpdir } from 'os'; import { Barrier, timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { flakySuite } from 'vs/base/test/common/testUtils'; @@ -134,7 +135,7 @@ class Ether { suite('IPC, Socket Protocol', () => { - ensureNoDisposablesAreLeakedInTestSuite(); + const ds = ensureNoDisposablesAreLeakedInTestSuite(); let ether: Ether; @@ -186,6 +187,26 @@ suite('IPC, Socket Protocol', () => { b.dispose(); }); + + + test('issue #211462: destroy socket after end timeout', async () => { + const socket = new EventEmitter(); + Object.assign(socket, { destroy: () => socket.emit('close') }); + const protocol = ds.add(new Protocol(new NodeSocket(socket as Socket))); + + const disposed = sinon.stub(); + const timers = sinon.useFakeTimers(); + + ds.add(toDisposable(() => timers.restore())); + ds.add(protocol.onDidDispose(disposed)); + + socket.emit('end'); + assert.ok(!disposed.called); + timers.tick(29_999); + assert.ok(!disposed.called); + timers.tick(1); + assert.ok(disposed.called); + }); }); suite('PersistentProtocol reconnection', () => { diff --git a/patched-vscode/src/vs/base/parts/request/browser/request.ts b/patched-vscode/src/vs/base/parts/request/browser/request.ts index 5802f6aa..7e40f2d8 100644 --- a/patched-vscode/src/vs/base/parts/request/browser/request.ts +++ b/patched-vscode/src/vs/base/parts/request/browser/request.ts @@ -6,73 +6,93 @@ import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; -import { IRequestContext, IRequestOptions, OfflineError } from 'vs/base/parts/request/common/request'; +import { IHeaders, IRequestContext, IRequestOptions, OfflineError } from 'vs/base/parts/request/common/request'; -export function request(options: IRequestOptions, token: CancellationToken): Promise { - if (options.proxyAuthorization) { - options.headers = { - ...(options.headers || {}), - 'Proxy-Authorization': options.proxyAuthorization - }; +export async function request(options: IRequestOptions, token: CancellationToken): Promise { + if (token.isCancellationRequested) { + throw canceled(); } - const xhr = new XMLHttpRequest(); - return new Promise((resolve, reject) => { - - xhr.open(options.type || 'GET', options.url || '', true, options.user, options.password); - setRequestHeaders(xhr, options); + const cancellation = new AbortController(); + const disposable = token.onCancellationRequested(() => cancellation.abort()); + const signal = options.timeout ? AbortSignal.any([ + cancellation.signal, + AbortSignal.timeout(options.timeout), + ]) : cancellation.signal; - xhr.responseType = 'arraybuffer'; - xhr.onerror = e => reject( - navigator.onLine ? new Error(xhr.statusText && ('XHR failed: ' + xhr.statusText) || 'XHR failed') : new OfflineError() - ); - xhr.onload = (e) => { - resolve({ - res: { - statusCode: xhr.status, - headers: getResponseHeaders(xhr) - }, - stream: bufferToStream(VSBuffer.wrap(new Uint8Array(xhr.response))) - }); + try { + const res = await fetch(options.url || '', { + method: options.type || 'GET', + headers: getRequestHeaders(options), + body: options.data, + signal, + }); + return { + res: { + statusCode: res.status, + headers: getResponseHeaders(res), + }, + stream: bufferToStream(VSBuffer.wrap(new Uint8Array(await res.arrayBuffer()))), }; - xhr.ontimeout = e => reject(new Error(`XHR timeout: ${options.timeout}ms`)); - - if (options.timeout) { - xhr.timeout = options.timeout; + } catch (err) { + if (!navigator.onLine) { + throw new OfflineError(); } - - xhr.send(options.data); - - // cancel - token.onCancellationRequested(() => { - xhr.abort(); - reject(canceled()); - }); - }); + if (err?.name === 'AbortError') { + throw canceled(); + } + if (err?.name === 'TimeoutError') { + throw new Error(`Fetch timeout: ${options.timeout}ms`); + } + throw err; + } finally { + disposable.dispose(); + } } -function setRequestHeaders(xhr: XMLHttpRequest, options: IRequestOptions): void { - if (options.headers) { +function getRequestHeaders(options: IRequestOptions) { + if (options.headers || options.user || options.password || options.proxyAuthorization) { + const headers: HeadersInit = new Headers(); outer: for (const k in options.headers) { - switch (k) { - case 'User-Agent': - case 'Accept-Encoding': - case 'Content-Length': + switch (k.toLowerCase()) { + case 'user-agent': + case 'accept-encoding': + case 'content-length': // unsafe headers continue outer; } - xhr.setRequestHeader(k, options.headers[k]); + const header = options.headers[k]; + if (typeof header === 'string') { + headers.set(k, header); + } else if (Array.isArray(header)) { + for (const h of header) { + headers.append(k, h); + } + } + } + if (options.user || options.password) { + headers.set('Authorization', 'Basic ' + btoa(`${options.user || ''}:${options.password || ''}`)); } + if (options.proxyAuthorization) { + headers.set('Proxy-Authorization', options.proxyAuthorization); + } + return headers; } + return undefined; } -function getResponseHeaders(xhr: XMLHttpRequest): { [name: string]: string } { - const headers: { [name: string]: string } = Object.create(null); - for (const line of xhr.getAllResponseHeaders().split(/\r\n|\n|\r/g)) { - if (line) { - const idx = line.indexOf(':'); - headers[line.substr(0, idx).trim().toLowerCase()] = line.substr(idx + 1).trim(); +function getResponseHeaders(res: Response): IHeaders { + const headers: IHeaders = Object.create(null); + res.headers.forEach((value, key) => { + if (headers[key]) { + if (Array.isArray(headers[key])) { + headers[key].push(value); + } else { + headers[key] = [headers[key], value]; + } + } else { + headers[key] = value; } - } + }); return headers; } diff --git a/patched-vscode/src/vs/base/parts/request/common/request.ts b/patched-vscode/src/vs/base/parts/request/common/request.ts index 5fb56f8f..18fa68c3 100644 --- a/patched-vscode/src/vs/base/parts/request/common/request.ts +++ b/patched-vscode/src/vs/base/parts/request/common/request.ts @@ -25,7 +25,14 @@ export class OfflineError extends Error { } export interface IHeaders { - [header: string]: string; + 'Proxy-Authorization'?: string; + 'x-operation-id'?: string; + 'retry-after'?: string; + etag?: string; + 'Content-Length'?: string; + 'activityid'?: string; + 'X-Market-User-Id'?: string; + [header: string]: string | string[] | undefined; } export interface IRequestOptions { diff --git a/patched-vscode/src/vs/base/parts/request/test/browser/request.test.ts b/patched-vscode/src/vs/base/parts/request/test/browser/request.test.ts new file mode 100644 index 00000000..4bac2bc8 --- /dev/null +++ b/patched-vscode/src/vs/base/parts/request/test/browser/request.test.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// eslint-disable-next-line local/code-import-patterns +import * as http from 'http'; +// eslint-disable-next-line local/code-import-patterns +import { AddressInfo } from 'net'; +import assert from 'assert'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { request } from 'vs/base/parts/request/browser/request'; +import { streamToBuffer } from 'vs/base/common/buffer'; +import { flakySuite } from 'vs/base/test/common/testUtils'; + +flakySuite('Request', () => { + + let port: number; + let server: http.Server; + + setup(async () => { + port = await new Promise((resolvePort, rejectPort) => { + server = http.createServer((req, res) => { + if (req.url === '/noreply') { + return; // never respond + } + res.setHeader('Content-Type', 'application/json'); + if (req.headers['echo-header']) { + res.setHeader('echo-header', req.headers['echo-header']); + } + const data: Buffer[] = []; + req.on('data', chunk => data.push(chunk)); + req.on('end', () => { + res.end(JSON.stringify({ + method: req.method, + url: req.url, + data: Buffer.concat(data).toString() + })); + }); + }).listen(0, '127.0.0.1', () => { + const address = server.address(); + resolvePort((address as AddressInfo).port); + }).on('error', err => { + rejectPort(err); + }); + }); + }); + + teardown(async () => { + await new Promise((resolve, reject) => { + server.close(err => err ? reject(err) : resolve()); + }); + }); + + test('GET', async () => { + const context = await request({ + url: `http://127.0.0.1:${port}`, + headers: { + 'echo-header': 'echo-value' + } + }, CancellationToken.None); + assert.strictEqual(context.res.statusCode, 200); + assert.strictEqual(context.res.headers['content-type'], 'application/json'); + assert.strictEqual(context.res.headers['echo-header'], 'echo-value'); + const buffer = await streamToBuffer(context.stream); + const body = JSON.parse(buffer.toString()); + assert.strictEqual(body.method, 'GET'); + assert.strictEqual(body.url, '/'); + }); + + test('POST', async () => { + const context = await request({ + type: 'POST', + url: `http://127.0.0.1:${port}/postpath`, + data: 'Some data', + }, CancellationToken.None); + assert.strictEqual(context.res.statusCode, 200); + assert.strictEqual(context.res.headers['content-type'], 'application/json'); + const buffer = await streamToBuffer(context.stream); + const body = JSON.parse(buffer.toString()); + assert.strictEqual(body.method, 'POST'); + assert.strictEqual(body.url, '/postpath'); + assert.strictEqual(body.data, 'Some data'); + }); + + test('timeout', async () => { + try { + await request({ + type: 'GET', + url: `http://127.0.0.1:${port}/noreply`, + timeout: 123, + }, CancellationToken.None); + assert.fail('Should fail with timeout'); + } catch (err) { + assert.strictEqual(err.message, 'Fetch timeout: 123ms'); + } + }); + + test('cancel', async () => { + try { + const source = new CancellationTokenSource(); + const res = request({ + type: 'GET', + url: `http://127.0.0.1:${port}/noreply`, + }, source.token); + await new Promise(resolve => setTimeout(resolve, 100)); + source.cancel(); + await res; + assert.fail('Should fail with cancellation'); + } catch (err) { + assert.strictEqual(err.message, 'Canceled'); + } + }); + + ensureNoDisposablesAreLeakedInTestSuite(); +}); diff --git a/patched-vscode/src/vs/base/parts/sandbox/common/electronTypes.ts b/patched-vscode/src/vs/base/parts/sandbox/common/electronTypes.ts index 43fa7507..ef8c1026 100644 --- a/patched-vscode/src/vs/base/parts/sandbox/common/electronTypes.ts +++ b/patched-vscode/src/vs/base/parts/sandbox/common/electronTypes.ts @@ -217,24 +217,6 @@ export interface FileFilter { name: string; } -export interface OpenDevToolsOptions { - /** - * Opens the devtools with specified dock state, can be `left`, `right`, `bottom`, - * `undocked`, `detach`. Defaults to last used dock state. In `undocked` mode it's - * possible to dock back. In `detach` mode it's not. - */ - mode: ('left' | 'right' | 'bottom' | 'undocked' | 'detach'); - /** - * Whether to bring the opened devtools window to the foreground. The default is - * `true`. - */ - activate?: boolean; - /** - * A title for the DevTools window (only in `undocked` or `detach` mode). - */ - title?: string; -} - interface InputEvent { // Docs: https://electronjs.org/docs/api/structures/input-event diff --git a/patched-vscode/src/vs/base/parts/sandbox/common/sandboxTypes.ts b/patched-vscode/src/vs/base/parts/sandbox/common/sandboxTypes.ts index 8c296184..17f7d5fe 100644 --- a/patched-vscode/src/vs/base/parts/sandbox/common/sandboxTypes.ts +++ b/patched-vscode/src/vs/base/parts/sandbox/common/sandboxTypes.ts @@ -53,4 +53,26 @@ export interface ISandboxConfiguration { * Location of V8 code cache. */ codeCachePath?: string; + + /** + * NLS support + */ + nls: { + + /** + * All NLS messages produced by `localize` and `localize2` calls + * under `src/vs`. + */ + messages: string[]; + + /** + * The actual language of the NLS messages (e.g. 'en', de' or 'pt-br'). + */ + language: string | undefined; + }; + + /** + * DEV time only: All CSS-modules that we have. + */ + cssModules?: string[]; } diff --git a/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts b/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts index ba8ea644..a132d7d6 100644 --- a/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts +++ b/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/electronTypes.ts @@ -172,3 +172,19 @@ export interface AuthInfo { port: number; realm: string; } + +export interface WebUtils { + + // Docs: https://electronjs.org/docs/api/web-utils + + /** + * The file system path that this `File` object points to. In the case where the + * object passed in is not a `File` object an exception is thrown. In the case + * where the File object passed in was constructed in JS and is not backed by a + * file on disk an empty string is returned. + * + * This method superceded the previous augmentation to the `File` object with the + * `path` property. An example is included below. + */ + getPathForFile(file: File): string; +} diff --git a/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 44a54904..10737a23 100644 --- a/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -5,7 +5,7 @@ import { INodeProcess, IProcessEnvironment } from 'vs/base/common/platform'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; -import { IpcRenderer, ProcessMemoryInfo, WebFrame } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; +import { IpcRenderer, ProcessMemoryInfo, WebFrame, WebUtils } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; /** * In Electron renderers we cannot expose all of the `process` global of node.js @@ -121,6 +121,7 @@ export const ipcMessagePort: IpcMessagePort = vscodeGlobal.ipcMessagePort; export const webFrame: WebFrame = vscodeGlobal.webFrame; export const process: ISandboxNodeProcess = vscodeGlobal.process; export const context: ISandboxContext = vscodeGlobal.context; +export const webUtils: WebUtils = vscodeGlobal.webUtils; /** * A set of globals that are available in all windows that either diff --git a/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/preload.js b/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/preload.js index 90ac9408..7e2339e4 100644 --- a/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/preload.js +++ b/patched-vscode/src/vs/base/parts/sandbox/electron-sandbox/preload.js @@ -7,7 +7,14 @@ (function () { 'use strict'; - const { ipcRenderer, webFrame, contextBridge } = require('electron'); + /** + * @import { ISandboxConfiguration } from '../common/sandboxTypes' + * @import { IpcRenderer } from './electronTypes' + * @import { IpcRendererEvent } from 'electron' + * @import { ISandboxNodeProcess } from './globals' + */ + + const { ipcRenderer, webFrame, contextBridge, webUtils } = require('electron'); //#region Utilities @@ -41,10 +48,6 @@ //#region Resolve Configuration - /** - * @typedef {import('../common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration - */ - /** @type {ISandboxConfiguration | undefined} */ let configuration = undefined; @@ -123,9 +126,6 @@ * A minimal set of methods exposed from Electron's `ipcRenderer` * to support communication to main process. * - * @typedef {import('./electronTypes').IpcRenderer} IpcRenderer - * @typedef {import('electron').IpcRendererEvent} IpcRendererEvent - * * @type {IpcRenderer} */ @@ -237,14 +237,25 @@ } }, + /** + * Support for subset of Electron's `webUtils` type. + */ + webUtils: { + + /** + * @param {File} file + */ + getPathForFile(file) { + return webUtils.getPathForFile(file); + } + }, + /** * Support for a subset of access to node.js global `process`. * * Note: when `sandbox` is enabled, the only properties available * are https://github.com/electron/electron/blob/master/docs/api/process.md#sandbox * - * @typedef {import('./globals').ISandboxNodeProcess} ISandboxNodeProcess - * * @type {ISandboxNodeProcess} */ process: { diff --git a/patched-vscode/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts b/patched-vscode/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts index 491f2209..69c4d3d5 100644 --- a/patched-vscode/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts +++ b/patched-vscode/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { context, ipcRenderer, process, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import assert from 'assert'; +import { ipcRenderer, process, webFrame, webUtils } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Sandbox', () => { @@ -13,10 +13,7 @@ suite('Sandbox', () => { assert.ok(typeof ipcRenderer.send === 'function'); assert.ok(typeof webFrame.setZoomLevel === 'function'); assert.ok(typeof process.platform === 'string'); - - const config = await context.resolveConfiguration(); - assert.ok(config); - assert.ok(context.configuration()); + assert.ok(typeof webUtils.getPathForFile === 'function'); }); ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/patched-vscode/src/vs/base/parts/storage/node/storage.ts b/patched-vscode/src/vs/base/parts/storage/node/storage.ts index 5d942007..ed5e8937 100644 --- a/patched-vscode/src/vs/base/parts/storage/node/storage.ts +++ b/patched-vscode/src/vs/base/parts/storage/node/storage.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { timeout } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { mapToString, setToString } from 'vs/base/common/map'; @@ -194,7 +195,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { // Delete the existing DB. If the path does not exist or fails to // be deleted, we do not try to recover anymore because we assume // that the path is no longer writeable for us. - return Promises.unlink(this.path).then(() => { + return fs.promises.unlink(this.path).then(() => { // Re-open the DB fresh return this.doConnect(this.path).then(recoveryConnection => { @@ -280,7 +281,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { // folder is really not writeable for us. // try { - await Promises.unlink(path); + await fs.promises.unlink(path); try { await Promises.rename(this.toBackupPath(path), path, false /* no retry */); } catch (error) { @@ -308,8 +309,14 @@ export class SQLiteStorageDatabase implements IStorageDatabase { private doConnect(path: string): Promise { return new Promise((resolve, reject) => { import('@vscode/sqlite3').then(sqlite3 => { + // ESM-comment-begin + const ctor = (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database); + // ESM-comment-end + // ESM-uncomment-begin + // const ctor = (this.logger.isTracing ? sqlite3.default.verbose().Database : sqlite3.default.Database); + // ESM-uncomment-end const connection: IDatabaseConnection = { - db: new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, (error: (Error & { code?: string }) | null) => { + db: new ctor(path, (error: (Error & { code?: string }) | null) => { if (error) { return (connection.db && error.code !== 'SQLITE_CANTOPEN' /* https://github.com/TryGhost/node-sqlite3/issues/1617 */) ? connection.db.close(() => reject(error)) : reject(error); } diff --git a/patched-vscode/src/vs/base/parts/storage/test/node/storage.integrationTest.ts b/patched-vscode/src/vs/base/parts/storage/test/node/storage.integrationTest.ts index 51c66ca1..450687ab 100644 --- a/patched-vscode/src/vs/base/parts/storage/test/node/storage.integrationTest.ts +++ b/patched-vscode/src/vs/base/parts/storage/test/node/storage.integrationTest.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { deepStrictEqual, ok, strictEqual } from 'assert'; import { tmpdir } from 'os'; import { timeout } from 'vs/base/common/async'; @@ -24,7 +25,7 @@ flakySuite('Storage Library', function () { setup(function () { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary'); - return Promises.mkdir(testDir, { recursive: true }); + return fs.promises.mkdir(testDir, { recursive: true }); }); teardown(function () { @@ -353,7 +354,7 @@ flakySuite('SQLite Storage Library', function () { setup(function () { testdir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary'); - return Promises.mkdir(testdir, { recursive: true }); + return fs.promises.mkdir(testdir, { recursive: true }); }); teardown(function () { @@ -534,7 +535,7 @@ flakySuite('SQLite Storage Library', function () { // on shutdown. await storage.checkIntegrity(true).then(null, error => { } /* error is expected here but we do not want to fail */); - await Promises.unlink(backupPath); // also test that the recovery DB is backed up properly + await fs.promises.unlink(backupPath); // also test that the recovery DB is backed up properly let recoveryCalled = false; await storage.close(() => { @@ -798,7 +799,7 @@ flakySuite('SQLite Storage Library', function () { await storage.optimize(); await storage.close(); - const sizeBeforeDeleteAndOptimize = (await Promises.stat(dbPath)).size; + const sizeBeforeDeleteAndOptimize = (await fs.promises.stat(dbPath)).size; storage = new SQLiteStorageDatabase(dbPath); @@ -820,7 +821,7 @@ flakySuite('SQLite Storage Library', function () { await storage.close(); - const sizeAfterDeleteAndOptimize = (await Promises.stat(dbPath)).size; + const sizeAfterDeleteAndOptimize = (await fs.promises.stat(dbPath)).size; strictEqual(sizeAfterDeleteAndOptimize < sizeBeforeDeleteAndOptimize, true); }); diff --git a/patched-vscode/src/vs/base/test/browser/actionbar.test.ts b/patched-vscode/src/vs/base/test/browser/actionbar.test.ts index fc119f36..5d197e81 100644 --- a/patched-vscode/src/vs/base/test/browser/actionbar.test.ts +++ b/patched-vscode/src/vs/base/test/browser/actionbar.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ActionBar, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, Separator } from 'vs/base/common/actions'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/browser.test.ts b/patched-vscode/src/vs/base/test/browser/browser.test.ts index 115112ff..76049fb5 100644 --- a/patched-vscode/src/vs/base/test/browser/browser.test.ts +++ b/patched-vscode/src/vs/base/test/browser/browser.test.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 * as assert from 'assert'; +import assert from 'assert'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/comparers.test.ts b/patched-vscode/src/vs/base/test/browser/comparers.test.ts index 8848b04a..690e3eb4 100644 --- a/patched-vscode/src/vs/base/test/browser/comparers.test.ts +++ b/patched-vscode/src/vs/base/test/browser/comparers.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { compareFileExtensions, compareFileExtensionsDefault, compareFileExtensionsLower, compareFileExtensionsUnicode, compareFileExtensionsUpper, compareFileNames, compareFileNamesDefault, compareFileNamesLower, compareFileNamesUnicode, compareFileNamesUpper } from 'vs/base/common/comparers'; diff --git a/patched-vscode/src/vs/base/test/browser/dom.test.ts b/patched-vscode/src/vs/base/test/browser/dom.test.ts index 03d618b1..61358186 100644 --- a/patched-vscode/src/vs/base/test/browser/dom.test.ts +++ b/patched-vscode/src/vs/base/test/browser/dom.test.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { $, asCssValueWithDefault, h, multibyteAwareBtoa, trackAttributes, copyAttributes, disposableWindowInterval, getWindows, getWindowsCount, getWindowId, getWindowById, hasWindow, getWindow, getDocument, isHTMLElement } from 'vs/base/browser/dom'; +import assert from 'assert'; +import { $, asCssValueWithDefault, h, multibyteAwareBtoa, trackAttributes, copyAttributes, disposableWindowInterval, getWindows, getWindowsCount, getWindowId, getWindowById, hasWindow, getWindow, getDocument, isHTMLElement, SafeTriangle } from 'vs/base/browser/dom'; import { ensureCodeWindow, isAuxiliaryWindow, mainWindow } from 'vs/base/browser/window'; import { DeferredPromise, timeout } from 'vs/base/common/async'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; @@ -407,5 +407,38 @@ suite('dom', () => { }); }); + suite('SafeTriangle', () => { + const fakeElement = (left: number, right: number, top: number, bottom: number): HTMLElement => { + return { getBoundingClientRect: () => ({ left, right, top, bottom }) } as any; + }; + + test('works', () => { + const safeTriangle = new SafeTriangle(0, 0, fakeElement(10, 20, 10, 20)); + + assert.strictEqual(safeTriangle.contains(5, 5), true); // in triangle region + assert.strictEqual(safeTriangle.contains(15, 5), false); + assert.strictEqual(safeTriangle.contains(25, 5), false); + + assert.strictEqual(safeTriangle.contains(5, 15), false); + assert.strictEqual(safeTriangle.contains(15, 15), true); + assert.strictEqual(safeTriangle.contains(25, 15), false); + + assert.strictEqual(safeTriangle.contains(5, 25), false); + assert.strictEqual(safeTriangle.contains(15, 25), false); + assert.strictEqual(safeTriangle.contains(25, 25), false); + }); + + test('other dirations', () => { + const a = new SafeTriangle(30, 30, fakeElement(10, 20, 10, 20)); + assert.strictEqual(a.contains(25, 25), true); + + const b = new SafeTriangle(0, 30, fakeElement(10, 20, 10, 20)); + assert.strictEqual(b.contains(5, 25), true); + + const c = new SafeTriangle(30, 0, fakeElement(10, 20, 10, 20)); + assert.strictEqual(c.contains(25, 5), true); + }); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/patched-vscode/src/vs/base/test/browser/formattedTextRenderer.test.ts b/patched-vscode/src/vs/base/test/browser/formattedTextRenderer.test.ts index 12acef6e..d4ba4527 100644 --- a/patched-vscode/src/vs/base/test/browser/formattedTextRenderer.test.ts +++ b/patched-vscode/src/vs/base/test/browser/formattedTextRenderer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { renderFormattedText, renderText } from 'vs/base/browser/formattedTextRenderer'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/hash.test.ts b/patched-vscode/src/vs/base/test/browser/hash.test.ts index e613a191..d7f319bb 100644 --- a/patched-vscode/src/vs/base/test/browser/hash.test.ts +++ b/patched-vscode/src/vs/base/test/browser/hash.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { sha1Hex } from 'vs/base/browser/hash'; import { hash, StringSHA1 } from 'vs/base/common/hash'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/highlightedLabel.test.ts b/patched-vscode/src/vs/base/test/browser/highlightedLabel.test.ts index fe2ceb43..c18b2037 100644 --- a/patched-vscode/src/vs/base/test/browser/highlightedLabel.test.ts +++ b/patched-vscode/src/vs/base/test/browser/highlightedLabel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/iconLabels.test.ts b/patched-vscode/src/vs/base/test/browser/iconLabels.test.ts index 3972977f..2a10f472 100644 --- a/patched-vscode/src/vs/base/test/browser/iconLabels.test.ts +++ b/patched-vscode/src/vs/base/test/browser/iconLabels.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { isHTMLElement } from 'vs/base/browser/dom'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/indexedDB.test.ts b/patched-vscode/src/vs/base/test/browser/indexedDB.test.ts index a6266231..8b127156 100644 --- a/patched-vscode/src/vs/base/test/browser/indexedDB.test.ts +++ b/patched-vscode/src/vs/base/test/browser/indexedDB.test.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 * as assert from 'assert'; +import assert from 'assert'; import { IndexedDB } from 'vs/base/browser/indexedDB'; import { flakySuite } from 'vs/base/test/common/testUtils'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/markdownRenderer.test.ts b/patched-vscode/src/vs/base/test/browser/markdownRenderer.test.ts index f9099c65..ca8fc628 100644 --- a/patched-vscode/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/patched-vscode/src/vs/base/test/browser/markdownRenderer.test.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { fillInIncompleteTokens, renderMarkdown, renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { marked } from 'vs/base/common/marked/marked'; +import * as marked from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -259,8 +259,8 @@ suite('MarkdownRenderer', () => { suite('PlaintextMarkdownRender', () => { test('test code, blockquote, heading, list, listitem, paragraph, table, tablerow, tablecell, strong, em, br, del, text are rendered plaintext', () => { - const markdown = { value: '`code`\n>quote\n# heading\n- list\n\n\ntable | table2\n--- | --- \none | two\n\n\nbo**ld**\n_italic_\n~~del~~\nsome text' }; - const expected = 'code\nquote\nheading\nlist\ntable table2 one two \nbold\nitalic\ndel\nsome text\n'; + const markdown = { value: '`code`\n>quote\n# heading\n- list\n\ntable | table2\n--- | --- \none | two\n\n\nbo**ld**\n_italic_\n~~del~~\nsome text' }; + const expected = 'code\nquote\nheading\nlist\n\ntable table2\none two\nbold\nitalic\ndel\nsome text\n'; const result: string = renderMarkdownAsPlaintext(markdown); assert.strictEqual(result, expected); }); @@ -342,15 +342,15 @@ suite('MarkdownRenderer', () => { suite('table', () => { test('complete table', () => { - const tokens = marked.lexer(completeTable); + const tokens = marked.marked.lexer(completeTable); const newTokens = fillInIncompleteTokens(tokens); assert.equal(newTokens, tokens); }); test('full header only', () => { const incompleteTable = '| a | b |'; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(completeTable); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(completeTable); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, completeTableTokens); @@ -358,8 +358,8 @@ suite('MarkdownRenderer', () => { test('full header only with trailing space', () => { const incompleteTable = '| a | b | '; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(completeTable); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(completeTable); const newTokens = fillInIncompleteTokens(tokens); if (newTokens) { @@ -370,8 +370,8 @@ suite('MarkdownRenderer', () => { test('incomplete header', () => { const incompleteTable = '| a | b'; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(completeTable); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(completeTable); const newTokens = fillInIncompleteTokens(tokens); @@ -383,8 +383,8 @@ suite('MarkdownRenderer', () => { test('incomplete header one column', () => { const incompleteTable = '| a '; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(incompleteTable + '|\n| --- |'); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(incompleteTable + '|\n| --- |'); const newTokens = fillInIncompleteTokens(tokens); @@ -396,8 +396,8 @@ suite('MarkdownRenderer', () => { test('full header with extras', () => { const incompleteTable = '| a **bold** | b _italics_ |'; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(incompleteTable + '\n| --- | --- |'); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |'); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, completeTableTokens); @@ -406,8 +406,8 @@ suite('MarkdownRenderer', () => { test('full header with leading text', () => { // Parsing this gives one token and one 'text' subtoken const incompleteTable = 'here is a table\n| a | b |'; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(incompleteTable + '\n| --- | --- |'); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |'); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, completeTableTokens); @@ -416,8 +416,8 @@ suite('MarkdownRenderer', () => { test('full header with leading other stuff', () => { // Parsing this gives one token and one 'text' subtoken const incompleteTable = '```js\nconst xyz = 123;\n```\n| a | b |'; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(incompleteTable + '\n| --- | --- |'); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |'); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, completeTableTokens); @@ -425,8 +425,8 @@ suite('MarkdownRenderer', () => { test('full header with incomplete separator', () => { const incompleteTable = '| a | b |\n| ---'; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(completeTable); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(completeTable); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, completeTableTokens); @@ -434,8 +434,8 @@ suite('MarkdownRenderer', () => { test('full header with incomplete separator 2', () => { const incompleteTable = '| a | b |\n| --- |'; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(completeTable); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(completeTable); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, completeTableTokens); @@ -443,8 +443,8 @@ suite('MarkdownRenderer', () => { test('full header with incomplete separator 3', () => { const incompleteTable = '| a | b |\n|'; - const tokens = marked.lexer(incompleteTable); - const completeTableTokens = marked.lexer(completeTable); + const tokens = marked.marked.lexer(incompleteTable); + const completeTableTokens = marked.marked.lexer(completeTable); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, completeTableTokens); @@ -452,7 +452,7 @@ suite('MarkdownRenderer', () => { test('not a table', () => { const incompleteTable = '| a | b |\nsome text'; - const tokens = marked.lexer(incompleteTable); + const tokens = marked.marked.lexer(incompleteTable); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -460,104 +460,26 @@ suite('MarkdownRenderer', () => { test('not a table 2', () => { const incompleteTable = '| a | b |\n| --- |\nsome text'; - const tokens = marked.lexer(incompleteTable); + const tokens = marked.marked.lexer(incompleteTable); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); }); }); - suite('codeblock', () => { - test('complete code block', () => { - const completeCodeblock = '```js\nconst xyz = 123;\n```'; - const tokens = marked.lexer(completeCodeblock); - const newTokens = fillInIncompleteTokens(tokens); - assert.equal(newTokens, tokens); - }); - - test('code block header only', () => { - const incompleteCodeblock = '```js'; - const tokens = marked.lexer(incompleteCodeblock); - const newTokens = fillInIncompleteTokens(tokens); - - const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); - assert.deepStrictEqual(newTokens, completeCodeblockTokens); - }); - - test('code block header no lang', () => { - const incompleteCodeblock = '```'; - const tokens = marked.lexer(incompleteCodeblock); - const newTokens = fillInIncompleteTokens(tokens); - - const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); - assert.deepStrictEqual(newTokens, completeCodeblockTokens); - }); - - test('code block header and some code', () => { - const incompleteCodeblock = '```js\nconst'; - const tokens = marked.lexer(incompleteCodeblock); - const newTokens = fillInIncompleteTokens(tokens); - - const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); - assert.deepStrictEqual(newTokens, completeCodeblockTokens); - }); - - test('code block header with leading text', () => { - const incompleteCodeblock = 'some text\n```js'; - const tokens = marked.lexer(incompleteCodeblock); - const newTokens = fillInIncompleteTokens(tokens); - - const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); - assert.deepStrictEqual(newTokens, completeCodeblockTokens); - }); - - test('code block header with leading text and some code', () => { - const incompleteCodeblock = 'some text\n```js\nconst'; - const tokens = marked.lexer(incompleteCodeblock); - const newTokens = fillInIncompleteTokens(tokens); - - const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); - assert.deepStrictEqual(newTokens, completeCodeblockTokens); - }); - - test('code block header with more backticks', () => { - const incompleteCodeblock = 'some text\n`````js\nconst'; - const tokens = marked.lexer(incompleteCodeblock); - const newTokens = fillInIncompleteTokens(tokens); - - const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n`````'); - assert.deepStrictEqual(newTokens, completeCodeblockTokens); - }); - - test('code block header containing codeblock', () => { - const incompleteCodeblock = `some text -\`\`\`\`\`js -const x = 1; -\`\`\` -const y = 2; -\`\`\` -// foo`; - const tokens = marked.lexer(incompleteCodeblock); - const newTokens = fillInIncompleteTokens(tokens); - - const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n`````'); - assert.deepStrictEqual(newTokens, completeCodeblockTokens); - }); - }); - function simpleMarkdownTestSuite(name: string, delimiter: string): void { test(`incomplete ${name}`, () => { const incomplete = `${delimiter}code`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + delimiter); + const completeTokens = marked.marked.lexer(incomplete + delimiter); assert.deepStrictEqual(newTokens, completeTokens); }); test(`complete ${name}`, () => { const text = `leading text ${delimiter}code${delimiter} trailing text`; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -565,16 +487,16 @@ const y = 2; test(`${name} with leading text`, () => { const incomplete = `some text and ${delimiter}some code`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + delimiter); + const completeTokens = marked.marked.lexer(incomplete + delimiter); assert.deepStrictEqual(newTokens, completeTokens); }); test(`single loose "${delimiter}"`, () => { const text = `some text and ${delimiter}by itself\nmore text here`; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -582,37 +504,46 @@ const y = 2; test(`incomplete ${name} after newline`, () => { const text = `some text\nmore text here and ${delimiter}text`; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(text + delimiter); + const completeTokens = marked.marked.lexer(text + delimiter); assert.deepStrictEqual(newTokens, completeTokens); }); test(`incomplete after complete ${name}`, () => { const text = `leading text ${delimiter}code${delimiter} trailing text and ${delimiter}another`; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(text + delimiter); + const completeTokens = marked.marked.lexer(text + delimiter); assert.deepStrictEqual(newTokens, completeTokens); }); test(`incomplete ${name} in list`, () => { const text = `- list item one\n- list item two and ${delimiter}text`; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.marked.lexer(text + delimiter); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test(`incomplete ${name} in asterisk list`, () => { + const text = `* list item one\n* list item two and ${delimiter}text`; + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(text + delimiter); + const completeTokens = marked.marked.lexer(text + delimiter); assert.deepStrictEqual(newTokens, completeTokens); }); test(`incomplete ${name} in numbered list`, () => { const text = `1. list item one\n2. list item two and ${delimiter}text`; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(text + delimiter); + const completeTokens = marked.marked.lexer(text + delimiter); assert.deepStrictEqual(newTokens, completeTokens); }); } @@ -625,7 +556,7 @@ const y = 2; \`\`\` - list item two `; - const tokens = marked.lexer(list); + const tokens = marked.marked.lexer(list); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -636,10 +567,10 @@ const y = 2; \`\`\`js let x = 1;`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '\n ```'); + const completeTokens = marked.marked.lexer(incomplete + '\n ```'); assert.deepStrictEqual(newTokens, completeTokens); }); @@ -649,7 +580,7 @@ const y = 2; - text newline for some reason `; - const tokens = marked.lexer(list); + const tokens = marked.marked.lexer(list); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -661,7 +592,7 @@ const y = 2; 2. text newline for some reason `; - const tokens = marked.lexer(list); + const tokens = marked.marked.lexer(list); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -669,7 +600,7 @@ const y = 2; test('list with stuff', () => { const list = `- list item one \`codespan\` **bold** [link](http://microsoft.com) more text`; - const tokens = marked.lexer(list); + const tokens = marked.marked.lexer(list); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -678,70 +609,70 @@ const y = 2; test('list with incomplete link text', () => { const incomplete = `- list item one - item two [link`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '](https://microsoft.com)'); + const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)'); assert.deepStrictEqual(newTokens, completeTokens); }); test('list with incomplete link target', () => { const incomplete = `- list item one - item two [link](`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + ')'); + const completeTokens = marked.marked.lexer(incomplete + ')'); assert.deepStrictEqual(newTokens, completeTokens); }); test('ordered list with incomplete link target', () => { const incomplete = `1. list item one 2. item two [link](`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + ')'); + const completeTokens = marked.marked.lexer(incomplete + ')'); assert.deepStrictEqual(newTokens, completeTokens); }); test('ordered list with extra whitespace', () => { const incomplete = `1. list item one 2. item two [link](`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + ')'); + const completeTokens = marked.marked.lexer(incomplete + ')'); assert.deepStrictEqual(newTokens, completeTokens); }); test('list with extra whitespace', () => { const incomplete = `- list item one - item two [link](`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + ')'); + const completeTokens = marked.marked.lexer(incomplete + ')'); assert.deepStrictEqual(newTokens, completeTokens); }); test('list with incomplete link with other stuff', () => { const incomplete = `- list item one - item two [\`link`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '\`](https://microsoft.com)'); + const completeTokens = marked.marked.lexer(incomplete + '\`](https://microsoft.com)'); assert.deepStrictEqual(newTokens, completeTokens); }); test('ordered list with incomplete link with other stuff', () => { const incomplete = `1. list item one 1. item two [\`link`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '\`](https://microsoft.com)'); + const completeTokens = marked.marked.lexer(incomplete + '\`](https://microsoft.com)'); assert.deepStrictEqual(newTokens, completeTokens); }); }); @@ -751,19 +682,19 @@ const y = 2; test(`backtick between letters`, () => { const text = 'a`b'; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeCodespanTokens = marked.lexer(text + '`'); + const completeCodespanTokens = marked.marked.lexer(text + '`'); assert.deepStrictEqual(newTokens, completeCodespanTokens); }); test(`nested pattern`, () => { const text = 'sldkfjsd `abc __def__ ghi'; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(text + '`'); + const completeTokens = marked.marked.lexer(text + '`'); assert.deepStrictEqual(newTokens, completeTokens); }); }); @@ -773,19 +704,19 @@ const y = 2; test(`star between letters`, () => { const text = 'sldkfjsd a*b'; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(text + '*'); + const completeTokens = marked.marked.lexer(text + '*'); assert.deepStrictEqual(newTokens, completeTokens); }); test(`nested pattern`, () => { const text = 'sldkfjsd *abc __def__ ghi'; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(text + '*'); + const completeTokens = marked.marked.lexer(text + '*'); assert.deepStrictEqual(newTokens, completeTokens); }); }); @@ -795,10 +726,10 @@ const y = 2; test(`double star between letters`, () => { const text = 'a**b'; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(text + '**'); + const completeTokens = marked.marked.lexer(text + '**'); assert.deepStrictEqual(newTokens, completeTokens); }); }); @@ -808,7 +739,7 @@ const y = 2; test(`underscore between letters`, () => { const text = `this_not_italics`; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -820,7 +751,7 @@ const y = 2; test(`double underscore between letters`, () => { const text = `this__not__bold`; - const tokens = marked.lexer(text); + const tokens = marked.marked.lexer(text); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -830,115 +761,115 @@ const y = 2; suite('link', () => { test('incomplete link text', () => { const incomplete = 'abc [text'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '](https://microsoft.com)'); + const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)'); assert.deepStrictEqual(newTokens, completeTokens); }); test('incomplete link target', () => { const incomplete = 'foo [text](http://microsoft'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + ')'); + const completeTokens = marked.marked.lexer(incomplete + ')'); assert.deepStrictEqual(newTokens, completeTokens); }); test('incomplete link target 2', () => { const incomplete = 'foo [text](http://microsoft.com'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + ')'); + const completeTokens = marked.marked.lexer(incomplete + ')'); assert.deepStrictEqual(newTokens, completeTokens); }); test('incomplete link target with extra stuff', () => { const incomplete = '[before `text` after](http://microsoft.com'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + ')'); + const completeTokens = marked.marked.lexer(incomplete + ')'); assert.deepStrictEqual(newTokens, completeTokens); }); test('incomplete link target with extra stuff and incomplete arg', () => { const incomplete = '[before `text` after](http://microsoft.com "more text '; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '")'); + const completeTokens = marked.marked.lexer(incomplete + '")'); assert.deepStrictEqual(newTokens, completeTokens); }); test('incomplete link target with incomplete arg', () => { const incomplete = 'foo [text](http://microsoft.com "more text here '; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '")'); + const completeTokens = marked.marked.lexer(incomplete + '")'); assert.deepStrictEqual(newTokens, completeTokens); }); test('incomplete link target with incomplete arg 2', () => { const incomplete = '[text](command:_github.copilot.openRelativePath "arg'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '")'); + const completeTokens = marked.marked.lexer(incomplete + '")'); assert.deepStrictEqual(newTokens, completeTokens); }); test('incomplete link target with complete arg', () => { const incomplete = 'foo [text](http://microsoft.com "more text here"'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + ')'); + const completeTokens = marked.marked.lexer(incomplete + ')'); assert.deepStrictEqual(newTokens, completeTokens); }); test('link text with incomplete codespan', () => { const incomplete = `text [\`codespan`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '`](https://microsoft.com)'); + const completeTokens = marked.marked.lexer(incomplete + '`](https://microsoft.com)'); assert.deepStrictEqual(newTokens, completeTokens); }); test('link text with incomplete stuff', () => { const incomplete = `text [more text \`codespan\` text **bold`; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '**](https://microsoft.com)'); + const completeTokens = marked.marked.lexer(incomplete + '**](https://microsoft.com)'); assert.deepStrictEqual(newTokens, completeTokens); }); test('Looks like incomplete link target but isn\'t', () => { const complete = '**bold** `codespan` text]('; - const tokens = marked.lexer(complete); + const tokens = marked.marked.lexer(complete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(complete); + const completeTokens = marked.marked.lexer(complete); assert.deepStrictEqual(newTokens, completeTokens); }); test.skip('incomplete link in list', () => { const incomplete = '- [text'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); - const completeTokens = marked.lexer(incomplete + '](https://microsoft.com)'); + const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)'); assert.deepStrictEqual(newTokens, completeTokens); }); test('square brace between letters', () => { const incomplete = 'a[b'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -946,7 +877,7 @@ const y = 2; test('square brace on previous line', () => { const incomplete = 'text[\nmore text'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); @@ -954,7 +885,7 @@ const y = 2; test('complete link', () => { const incomplete = 'text [link](http://microsoft.com)'; - const tokens = marked.lexer(incomplete); + const tokens = marked.marked.lexer(incomplete); const newTokens = fillInIncompleteTokens(tokens); assert.deepStrictEqual(newTokens, tokens); diff --git a/patched-vscode/src/vs/base/test/browser/progressBar.test.ts b/patched-vscode/src/vs/base/test/browser/progressBar.test.ts index 9620ae31..8490fd17 100644 --- a/patched-vscode/src/vs/base/test/browser/progressBar.test.ts +++ b/patched-vscode/src/vs/base/test/browser/progressBar.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { mainWindow } from 'vs/base/browser/window'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -17,7 +17,7 @@ suite('ProgressBar', () => { }); teardown(() => { - mainWindow.document.body.removeChild(fixture); + fixture.remove(); }); test('Progress Bar', function () { diff --git a/patched-vscode/src/vs/base/test/browser/ui/contextview/contextview.test.ts b/patched-vscode/src/vs/base/test/browser/ui/contextview/contextview.test.ts index c302c358..45fded13 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/contextview/contextview.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/contextview/contextview.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/grid/grid.test.ts b/patched-vscode/src/vs/base/test/browser/ui/grid/grid.test.ts index 781a0a40..92296d18 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/grid/grid.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/grid/grid.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { createSerializedGrid, Direction, getRelativeLocation, Grid, GridNode, GridNodeDescriptor, ISerializableView, isGridBranchNode, IViewDeserializer, Orientation, sanitizeGridNodeDescriptor, SerializableGrid, Sizing } from 'vs/base/browser/ui/grid/grid'; import { Event } from 'vs/base/common/event'; import { deepClone } from 'vs/base/common/objects'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/grid/gridview.test.ts b/patched-vscode/src/vs/base/test/browser/ui/grid/gridview.test.ts index bbc7de07..df0c3838 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/grid/gridview.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/grid/gridview.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { $ } from 'vs/base/browser/dom'; import { GridView, IView, Orientation, Sizing } from 'vs/base/browser/ui/grid/gridview'; import { nodesToArrays, TestView } from 'vs/base/test/browser/ui/grid/util'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/grid/util.ts b/patched-vscode/src/vs/base/test/browser/ui/grid/util.ts index 9ebe81b3..ccadd786 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/grid/util.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/grid/util.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IView } from 'vs/base/browser/ui/grid/grid'; import { GridNode, isGridBranchNode } from 'vs/base/browser/ui/grid/gridview'; import { Emitter, Event } from 'vs/base/common/event'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/list/listView.test.ts b/patched-vscode/src/vs/base/test/browser/ui/list/listView.test.ts index 7dcab8e7..0a6dede2 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/list/listView.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/list/listView.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ListView } from 'vs/base/browser/ui/list/listView'; import { range } from 'vs/base/common/arrays'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/list/listWidget.test.ts b/patched-vscode/src/vs/base/test/browser/ui/list/listWidget.test.ts index 28fd9524..6d1263dd 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/list/listWidget.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/list/listWidget.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { range } from 'vs/base/common/arrays'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/list/rangeMap.test.ts b/patched-vscode/src/vs/base/test/browser/ui/list/rangeMap.test.ts index 5b3b4a6c..0a4625c7 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/list/rangeMap.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/list/rangeMap.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { consolidate, groupIntersect, RangeMap } from 'vs/base/browser/ui/list/rangeMap'; import { Range } from 'vs/base/common/range'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/menu/menubar.test.ts b/patched-vscode/src/vs/base/test/browser/ui/menu/menubar.test.ts index 49420cc0..af5e1da2 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/menu/menubar.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/menu/menubar.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { $ } from 'vs/base/browser/dom'; import { unthemedMenuStyles } from 'vs/base/browser/ui/menu/menu'; import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts b/patched-vscode/src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts index 30879e44..cc662ab2 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts b/patched-vscode/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts index 0a4a2dd0..39876bc5 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/patched-vscode/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 48ee43fe..7e20426f 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Sash, SashState } from 'vs/base/browser/ui/sash/sash'; import { IView, LayoutPriority, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { Emitter } from 'vs/base/common/event'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/patched-vscode/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index c92a6273..78b94a95 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { AsyncDataTree, CompressibleAsyncDataTree, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts b/patched-vscode/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts index 79de7324..9c5777b5 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { compress, CompressedObjectTreeModel, decompress, ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IObjectTreeModelSetChildrenOptions } from 'vs/base/browser/ui/tree/objectTreeModel'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/tree/dataTree.test.ts b/patched-vscode/src/vs/base/test/browser/ui/tree/dataTree.test.ts index fb821d3b..37c586f0 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/tree/dataTree.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/tree/dataTree.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DataTree } from 'vs/base/browser/ui/tree/dataTree'; import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/patched-vscode/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts index 5657e458..70277e01 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IIndexTreeModelSpliceOptions, IIndexTreeNode, IList, IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ITreeElement, ITreeFilter, ITreeNode, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { timeout } from 'vs/base/common/async'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/tree/objectTree.test.ts b/patched-vscode/src/vs/base/test/browser/ui/tree/objectTree.test.ts index 05594bf5..eaed09db 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/tree/objectTree.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/tree/objectTree.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { CompressibleObjectTree, ICompressibleTreeRenderer, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; diff --git a/patched-vscode/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/patched-vscode/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts index 4c895d94..ffef4afb 100644 --- a/patched-vscode/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts +++ b/patched-vscode/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; import { ITreeFilter, ITreeNode, ObjectTreeElementCollapseState, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; diff --git a/patched-vscode/src/vs/base/test/common/arrays.test.ts b/patched-vscode/src/vs/base/test/common/arrays.test.ts index 25a2620a..617a766c 100644 --- a/patched-vscode/src/vs/base/test/common/arrays.test.ts +++ b/patched-vscode/src/vs/base/test/common/arrays.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as arrays from 'vs/base/common/arrays'; import * as arraysFind from 'vs/base/common/arraysFind'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/arraysFind.test.ts b/patched-vscode/src/vs/base/test/common/arraysFind.test.ts index d932b68c..caeea0fe 100644 --- a/patched-vscode/src/vs/base/test/common/arraysFind.test.ts +++ b/patched-vscode/src/vs/base/test/common/arraysFind.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { MonotonousArray, findFirstMonotonous, findLastMonotonous } from 'vs/base/common/arraysFind'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/assert.test.ts b/patched-vscode/src/vs/base/test/common/assert.test.ts index ed4e60e8..f0052bad 100644 --- a/patched-vscode/src/vs/base/test/common/assert.test.ts +++ b/patched-vscode/src/vs/base/test/common/assert.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ok } from 'vs/base/common/assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/async.test.ts b/patched-vscode/src/vs/base/test/common/async.test.ts index da309f94..e55200ff 100644 --- a/patched-vscode/src/vs/base/test/common/async.test.ts +++ b/patched-vscode/src/vs/base/test/common/async.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as async from 'vs/base/common/async'; import * as MicrotaskDelay from "vs/base/common/symbols"; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; diff --git a/patched-vscode/src/vs/base/test/common/buffer.test.ts b/patched-vscode/src/vs/base/test/common/buffer.test.ts index 6c869a16..e94fd875 100644 --- a/patched-vscode/src/vs/base/test/common/buffer.test.ts +++ b/patched-vscode/src/vs/base/test/common/buffer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { bufferedStreamToBuffer, bufferToReadable, bufferToStream, decodeBase64, encodeBase64, newWriteableBufferStream, readableToBuffer, streamToBuffer, VSBuffer } from 'vs/base/common/buffer'; import { peekStream } from 'vs/base/common/stream'; diff --git a/patched-vscode/src/vs/base/test/common/cache.test.ts b/patched-vscode/src/vs/base/test/common/cache.test.ts index b1946ed3..ed4ca02f 100644 --- a/patched-vscode/src/vs/base/test/common/cache.test.ts +++ b/patched-vscode/src/vs/base/test/common/cache.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { Cache } from 'vs/base/common/cache'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/cancellation.test.ts b/patched-vscode/src/vs/base/test/common/cancellation.test.ts index 2e184c42..178f77cd 100644 --- a/patched-vscode/src/vs/base/test/common/cancellation.test.ts +++ b/patched-vscode/src/vs/base/test/common/cancellation.test.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 * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/charCode.test.ts b/patched-vscode/src/vs/base/test/common/charCode.test.ts index 49be69d8..59c4e3fb 100644 --- a/patched-vscode/src/vs/base/test/common/charCode.test.ts +++ b/patched-vscode/src/vs/base/test/common/charCode.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/collections.test.ts b/patched-vscode/src/vs/base/test/common/collections.test.ts index c2616cc3..b1304b3f 100644 --- a/patched-vscode/src/vs/base/test/common/collections.test.ts +++ b/patched-vscode/src/vs/base/test/common/collections.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as collections from 'vs/base/common/collections'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -32,4 +32,70 @@ suite('Collections', () => { assert.strictEqual(grouped[group2].length, 1); assert.strictEqual(grouped[group2][0].value, value3); }); + + suite('SetWithKey', () => { + let setWithKey: collections.SetWithKey<{ someProp: string }>; + + const initialValues = ['a', 'b', 'c'].map(s => ({ someProp: s })); + setup(() => { + setWithKey = new collections.SetWithKey<{ someProp: string }>(initialValues, value => value.someProp); + }); + + test('size', () => { + assert.strictEqual(setWithKey.size, 3); + }); + + test('add', () => { + setWithKey.add({ someProp: 'd' }); + assert.strictEqual(setWithKey.size, 4); + assert.strictEqual(setWithKey.has({ someProp: 'd' }), true); + }); + + test('delete', () => { + assert.strictEqual(setWithKey.has({ someProp: 'b' }), true); + setWithKey.delete({ someProp: 'b' }); + assert.strictEqual(setWithKey.size, 2); + assert.strictEqual(setWithKey.has({ someProp: 'b' }), false); + }); + + test('has', () => { + assert.strictEqual(setWithKey.has({ someProp: 'a' }), true); + assert.strictEqual(setWithKey.has({ someProp: 'b' }), true); + }); + + test('entries', () => { + const entries = Array.from(setWithKey.entries()); + assert.deepStrictEqual(entries, initialValues.map(value => [value, value])); + }); + + test('keys and values', () => { + const keys = Array.from(setWithKey.keys()); + const values = Array.from(setWithKey.values()); + assert.deepStrictEqual(keys, initialValues); + assert.deepStrictEqual(values, initialValues); + }); + + test('clear', () => { + setWithKey.clear(); + assert.strictEqual(setWithKey.size, 0); + }); + + test('forEach', () => { + const values: any[] = []; + setWithKey.forEach(value => values.push(value)); + assert.deepStrictEqual(values, initialValues); + }); + + test('iterator', () => { + const values: any[] = []; + for (const value of setWithKey) { + values.push(value); + } + assert.deepStrictEqual(values, initialValues); + }); + + test('toStringTag', () => { + assert.strictEqual(setWithKey[Symbol.toStringTag], 'SetWithKey'); + }); + }); }); diff --git a/patched-vscode/src/vs/base/test/common/color.test.ts b/patched-vscode/src/vs/base/test/common/color.test.ts index 857c394d..256397a7 100644 --- a/patched-vscode/src/vs/base/test/common/color.test.ts +++ b/patched-vscode/src/vs/base/test/common/color.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Color, HSLA, HSVA, RGBA } from 'vs/base/common/color'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/console.test.ts b/patched-vscode/src/vs/base/test/common/console.test.ts index 86842d1d..457255eb 100644 --- a/patched-vscode/src/vs/base/test/common/console.test.ts +++ b/patched-vscode/src/vs/base/test/common/console.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { getFirstFrame } from 'vs/base/common/console'; import { normalize } from 'vs/base/common/path'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/date.test.ts b/patched-vscode/src/vs/base/test/common/date.test.ts index 2a140e87..1e8809c2 100644 --- a/patched-vscode/src/vs/base/test/common/date.test.ts +++ b/patched-vscode/src/vs/base/test/common/date.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { strictEqual } from 'assert'; -import { fromNow, getDurationString } from 'vs/base/common/date'; +import { fromNow, fromNowByDay, getDurationString } from 'vs/base/common/date'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Date', () => { @@ -28,6 +28,23 @@ suite('Date', () => { }); }); + suite('fromNowByDay', () => { + test('today', () => { + const now = new Date(); + strictEqual(fromNowByDay(now), 'Today'); + }); + test('yesterday', () => { + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + strictEqual(fromNowByDay(yesterday), 'Yesterday'); + }); + test('daysAgo', () => { + const daysAgo = new Date(); + daysAgo.setDate(daysAgo.getDate() - 5); + strictEqual(fromNowByDay(daysAgo, true), '5 days ago'); + }); + }); + suite('getDurationString', () => { test('basic', () => { strictEqual(getDurationString(1), '1ms'); diff --git a/patched-vscode/src/vs/base/test/common/decorators.test.ts b/patched-vscode/src/vs/base/test/common/decorators.test.ts index 92bce8c6..33d78329 100644 --- a/patched-vscode/src/vs/base/test/common/decorators.test.ts +++ b/patched-vscode/src/vs/base/test/common/decorators.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { memoize, throttle } from 'vs/base/common/decorators'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/diff/diff.test.ts b/patched-vscode/src/vs/base/test/common/diff/diff.test.ts index 353b68eb..eeee6328 100644 --- a/patched-vscode/src/vs/base/test/common/diff/diff.test.ts +++ b/patched-vscode/src/vs/base/test/common/diff/diff.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IDiffChange, LcsDiff, StringDiffSequence } from 'vs/base/common/diff/diff'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/errors.test.ts b/patched-vscode/src/vs/base/test/common/errors.test.ts index 96210c55..3ebfaffe 100644 --- a/patched-vscode/src/vs/base/test/common/errors.test.ts +++ b/patched-vscode/src/vs/base/test/common/errors.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/event.test.ts b/patched-vscode/src/vs/base/test/common/event.test.ts index 161c7085..3c65d9e7 100644 --- a/patched-vscode/src/vs/base/test/common/event.test.ts +++ b/patched-vscode/src/vs/base/test/common/event.test.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 * as assert from 'assert'; +import assert from 'assert'; import { stub } from 'sinon'; import { tail2 } from 'vs/base/common/arrays'; import { DeferredPromise, timeout } from 'vs/base/common/async'; diff --git a/patched-vscode/src/vs/base/test/common/extpath.test.ts b/patched-vscode/src/vs/base/test/common/extpath.test.ts index c13210da..45e411a7 100644 --- a/patched-vscode/src/vs/base/test/common/extpath.test.ts +++ b/patched-vscode/src/vs/base/test/common/extpath.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; import * as extpath from 'vs/base/common/extpath'; import { isWindows } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/base/test/common/filters.test.ts b/patched-vscode/src/vs/base/test/common/filters.test.ts index 5bfe3856..5da3552e 100644 --- a/patched-vscode/src/vs/base/test/common/filters.test.ts +++ b/patched-vscode/src/vs/base/test/common/filters.test.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 * as assert from 'assert'; +import assert from 'assert'; import { anyScore, createMatches, fuzzyScore, fuzzyScoreGraceful, fuzzyScoreGracefulAggressive, FuzzyScorer, IFilter, IMatch, matchesCamelCase, matchesContiguousSubString, matchesPrefix, matchesStrictPrefix, matchesSubString, matchesWords, or } from 'vs/base/common/filters'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/fuzzyScorer.test.ts b/patched-vscode/src/vs/base/test/common/fuzzyScorer.test.ts index 747e2ee1..4e416626 100644 --- a/patched-vscode/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/patched-vscode/src/vs/base/test/common/fuzzyScorer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { compareItemsByFuzzyScore, FuzzyScore, FuzzyScore2, FuzzyScorerCache, IItemAccessor, IItemScore, pieceToQuery, prepareQuery, scoreFuzzy, scoreFuzzy2, scoreItemFuzzy } from 'vs/base/common/fuzzyScorer'; import { Schemas } from 'vs/base/common/network'; import { basename, dirname, posix, sep, win32 } from 'vs/base/common/path'; diff --git a/patched-vscode/src/vs/base/test/common/glob.test.ts b/patched-vscode/src/vs/base/test/common/glob.test.ts index 5bfb3dcc..2e721bd3 100644 --- a/patched-vscode/src/vs/base/test/common/glob.test.ts +++ b/patched-vscode/src/vs/base/test/common/glob.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as glob from 'vs/base/common/glob'; import { sep } from 'vs/base/common/path'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/base/test/common/history.test.ts b/patched-vscode/src/vs/base/test/common/history.test.ts index 39143609..07443052 100644 --- a/patched-vscode/src/vs/base/test/common/history.test.ts +++ b/patched-vscode/src/vs/base/test/common/history.test.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 * as assert from 'assert'; +import assert from 'assert'; import { HistoryNavigator, HistoryNavigator2 } from 'vs/base/common/history'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/iconLabels.test.ts b/patched-vscode/src/vs/base/test/common/iconLabels.test.ts index 4f2cd802..4ce6b493 100644 --- a/patched-vscode/src/vs/base/test/common/iconLabels.test.ts +++ b/patched-vscode/src/vs/base/test/common/iconLabels.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IMatch } from 'vs/base/common/filters'; import { escapeIcons, getCodiconAriaLabel, IParsedLabelWithIcons, markdownEscapeEscapedIcons, matchesFuzzyIconAware, parseLabelWithIcons, stripIcons } from 'vs/base/common/iconLabels'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/iterator.test.ts b/patched-vscode/src/vs/base/test/common/iterator.test.ts index ce69814a..0d95a4af 100644 --- a/patched-vscode/src/vs/base/test/common/iterator.test.ts +++ b/patched-vscode/src/vs/base/test/common/iterator.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Iterable } from 'vs/base/common/iterator'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/json.test.ts b/patched-vscode/src/vs/base/test/common/json.test.ts index 4ad12115..c8a6202b 100644 --- a/patched-vscode/src/vs/base/test/common/json.test.ts +++ b/patched-vscode/src/vs/base/test/common/json.test.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 * as assert from 'assert'; +import assert from 'assert'; import { createScanner, Node, parse, ParseError, ParseErrorCode, ParseOptions, parseTree, ScanError, SyntaxKind } from 'vs/base/common/json'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/jsonEdit.test.ts b/patched-vscode/src/vs/base/test/common/jsonEdit.test.ts index fa5d41b4..060884ae 100644 --- a/patched-vscode/src/vs/base/test/common/jsonEdit.test.ts +++ b/patched-vscode/src/vs/base/test/common/jsonEdit.test.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 * as assert from 'assert'; +import assert from 'assert'; import { removeProperty, setProperty } from 'vs/base/common/jsonEdit'; import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/jsonFormatter.test.ts b/patched-vscode/src/vs/base/test/common/jsonFormatter.test.ts index 636d987c..80d8a22f 100644 --- a/patched-vscode/src/vs/base/test/common/jsonFormatter.test.ts +++ b/patched-vscode/src/vs/base/test/common/jsonFormatter.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as Formatter from 'vs/base/common/jsonFormatter'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/stripComments.test.ts b/patched-vscode/src/vs/base/test/common/jsonParse.test.ts similarity index 57% rename from patched-vscode/src/vs/base/test/common/stripComments.test.ts rename to patched-vscode/src/vs/base/test/common/jsonParse.test.ts index 4aac8cdc..48aa377b 100644 --- a/patched-vscode/src/vs/base/test/common/stripComments.test.ts +++ b/patched-vscode/src/vs/base/test/common/jsonParse.test.ts @@ -2,14 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; -import { stripComments } from 'vs/base/common/stripComments'; +import { parse, stripComments } from 'vs/base/common/jsonc'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -// We use this regular expression quite often to strip comments in JSON files. - -suite('Strip Comments', () => { +suite('JSON Parse', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('Line comment', () => { @@ -23,7 +21,7 @@ suite('Strip Comments', () => { " \"prop\": 10 ", "}", ].join('\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - EOF', () => { const content: string = [ @@ -36,7 +34,7 @@ suite('Strip Comments', () => { "}", "" ].join('\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - \\r\\n', () => { const content: string = [ @@ -49,7 +47,7 @@ suite('Strip Comments', () => { " \"prop\": 10 ", "}", ].join('\r\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Line comment - EOF - \\r\\n', () => { const content: string = [ @@ -62,7 +60,7 @@ suite('Strip Comments', () => { "}", "" ].join('\r\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - single line', () => { const content: string = [ @@ -75,7 +73,7 @@ suite('Strip Comments', () => { " \"prop\": 10", "}", ].join('\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - multi line', () => { const content: string = [ @@ -92,7 +90,7 @@ suite('Strip Comments', () => { " \"prop\": 10", "}", ].join('\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Block comment - shortest match', () => { const content = "/* abc */ */"; @@ -110,7 +108,7 @@ suite('Strip Comments', () => { " \"/* */\": 10", "}" ].join('\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); }); test('No strings - single quote', () => { const content: string = [ @@ -136,7 +134,7 @@ suite('Strip Comments', () => { ` "a": 10`, "}" ].join('\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); }); test('Trailing comma in array', () => { const content: string = [ @@ -145,6 +143,52 @@ suite('Strip Comments', () => { const expected: string = [ `[ "a", "b", "c" ]` ].join('\n'); - assert.strictEqual(stripComments(content), expected); + assert.deepEqual(parse(content), JSON.parse(expected)); + }); + + test('Trailing comma', () => { + const content: string = [ + "{", + " \"propA\": 10, // a comment", + " \"propB\": false, // a trailing comma", + "}", + ].join('\n'); + const expected = [ + "{", + " \"propA\": 10,", + " \"propB\": false", + "}", + ].join('\n'); + assert.deepEqual(parse(content), JSON.parse(expected)); + }); + + test('Trailing comma - EOF', () => { + const content = ` +// This configuration file allows you to pass permanent command line arguments to VS Code. +// Only a subset of arguments is currently supported to reduce the likelihood of breaking +// the installation. +// +// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT +// +// NOTE: Changing this file requires a restart of VS Code. +{ + // Use software rendering instead of hardware accelerated rendering. + // This can help in cases where you see rendering issues in VS Code. + // "disable-hardware-acceleration": true, + // Allows to disable crash reporting. + // Should restart the app if the value is changed. + "enable-crash-reporter": true, + // Unique id used for correlating crash reports sent from this instance. + // Do not edit this value. + "crash-reporter-id": "aaaaab31-7453-4506-97d0-93411b2c21c7", + "locale": "en", + // "log-level": "trace" +} +`; + assert.deepEqual(parse(content), { + "enable-crash-reporter": true, + "crash-reporter-id": "aaaaab31-7453-4506-97d0-93411b2c21c7", + "locale": "en" + }); }); }); diff --git a/patched-vscode/src/vs/base/test/common/jsonSchema.test.ts b/patched-vscode/src/vs/base/test/common/jsonSchema.test.ts new file mode 100644 index 00000000..f47e1790 --- /dev/null +++ b/patched-vscode/src/vs/base/test/common/jsonSchema.test.ts @@ -0,0 +1,574 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import assert from 'assert'; +import { getCompressedContent, IJSONSchema } from 'vs/base/common/jsonSchema'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; + +suite('JSON Schema', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('getCompressedContent 1', () => { + + const schema: IJSONSchema = { + type: 'object', + properties: { + a: { + type: 'object', + description: 'a', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + }, + e: { + type: 'object', + description: 'e', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + } + } + }; + + const expected: IJSONSchema = { + type: 'object', + properties: { + a: { + type: 'object', + description: 'a', + properties: { + b: { + $ref: '#/$defs/_0' + } + } + }, + e: { + type: 'object', + description: 'e', + properties: { + b: { + $ref: '#/$defs/_0' + } + } + } + }, + $defs: { + "_0": { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + + }; + + assert.deepEqual(getCompressedContent(schema), JSON.stringify(expected)); + }); + + test('getCompressedContent 2', () => { + + const schema: IJSONSchema = { + type: 'object', + properties: { + a: { + type: 'object', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + }, + e: { + type: 'object', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + } + } + }; + + const expected: IJSONSchema = { + type: 'object', + properties: { + a: { + $ref: '#/$defs/_0' + + }, + e: { + $ref: '#/$defs/_0' + } + }, + $defs: { + "_0": { + type: 'object', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + } + } + + }; + + assert.deepEqual(getCompressedContent(schema), JSON.stringify(expected)); + }); + + test('getCompressedContent 3', () => { + + + const schema: IJSONSchema = { + type: 'object', + properties: { + a: { + type: 'object', + oneOf: [ + { + allOf: [ + { + properties: { + name: { + type: 'string' + }, + description: { + type: 'string' + } + } + }, + { + properties: { + street: { + type: 'string' + }, + } + } + ] + }, + { + allOf: [ + { + properties: { + name: { + type: 'string' + }, + description: { + type: 'string' + } + } + }, + { + properties: { + river: { + type: 'string' + }, + } + } + ] + }, + { + allOf: [ + { + properties: { + name: { + type: 'string' + }, + description: { + type: 'string' + } + } + }, + { + properties: { + mountain: { + type: 'string' + }, + } + } + ] + } + ] + }, + b: { + type: 'object', + properties: { + street: { + properties: { + street: { + type: 'string' + } + } + } + } + } + } + }; + + const expected: IJSONSchema = { + "type": "object", + "properties": { + "a": { + "type": "object", + "oneOf": [ + { + "allOf": [ + { + "$ref": "#/$defs/_0" + }, + { + "$ref": "#/$defs/_1" + } + ] + }, + { + "allOf": [ + { + "$ref": "#/$defs/_0" + }, + { + "properties": { + "river": { + "type": "string" + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/$defs/_0" + }, + { + "properties": { + "mountain": { + "type": "string" + } + } + } + ] + } + ] + }, + "b": { + "type": "object", + "properties": { + "street": { + "$ref": "#/$defs/_1" + } + } + } + }, + "$defs": { + "_0": { + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "_1": { + "properties": { + "street": { + "type": "string" + } + } + } + } + }; + + const actual = getCompressedContent(schema); + assert.deepEqual(actual, JSON.stringify(expected)); + }); + + test('getCompressedContent 4', () => { + + const schema: IJSONSchema = { + type: 'object', + properties: { + a: { + type: 'object', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + }, + e: { + type: 'object', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + }, + f: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + }; + + const expected: IJSONSchema = { + type: 'object', + properties: { + a: { + $ref: '#/$defs/_0' + }, + e: { + $ref: '#/$defs/_0' + }, + f: { + $ref: '#/$defs/_1' + } + }, + $defs: { + "_0": { + type: 'object', + properties: { + b: { + type: 'object', + properties: { + c: { + $ref: '#/$defs/_1' + } + } + } + } + }, + "_1": { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + + }; + + assert.deepEqual(getCompressedContent(schema), JSON.stringify(expected)); + }); + + test('getCompressedContent 5', () => { + + const schema: IJSONSchema = { + type: 'object', + properties: { + a: { + type: 'array', + items: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + }, + e: { + type: 'array', + items: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + }, + f: { + type: 'object', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + }, + g: { + type: 'object', + properties: { + b: { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + } + } + }; + + const expected: IJSONSchema = { + type: 'object', + properties: { + a: { + $ref: '#/$defs/_0' + }, + e: { + $ref: '#/$defs/_0' + }, + f: { + $ref: '#/$defs/_1' + }, + g: { + $ref: '#/$defs/_1' + } + }, + $defs: { + "_0": { + type: 'array', + items: { + $ref: '#/$defs/_2' + } + }, + "_1": { + type: 'object', + properties: { + b: { + $ref: '#/$defs/_2' + } + } + }, + "_2": { + type: 'object', + properties: { + c: { + type: 'object', + properties: { + d: { + type: 'string' + } + } + } + } + } + } + + }; + + assert.deepEqual(getCompressedContent(schema), JSON.stringify(expected)); + }); + + +}); diff --git a/patched-vscode/src/vs/base/test/common/keyCodes.test.ts b/patched-vscode/src/vs/base/test/common/keyCodes.test.ts index e3c73412..ea539be7 100644 --- a/patched-vscode/src/vs/base/test/common/keyCodes.test.ts +++ b/patched-vscode/src/vs/base/test/common/keyCodes.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { EVENT_KEY_CODE_MAP, IMMUTABLE_CODE_TO_KEY_CODE, IMMUTABLE_KEY_CODE_TO_CODE, KeyChord, KeyCode, KeyCodeUtils, KeyMod, NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE, ScanCode, ScanCodeUtils } from 'vs/base/common/keyCodes'; import { decodeKeybinding, KeyCodeChord, Keybinding } from 'vs/base/common/keybindings'; import { OperatingSystem } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/base/test/common/keybindings.test.ts b/patched-vscode/src/vs/base/test/common/keybindings.test.ts index ab26cf24..5959f510 100644 --- a/patched-vscode/src/vs/base/test/common/keybindings.test.ts +++ b/patched-vscode/src/vs/base/test/common/keybindings.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { KeyCode, ScanCode } from 'vs/base/common/keyCodes'; import { KeyCodeChord, ScanCodeChord } from 'vs/base/common/keybindings'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/labels.test.ts b/patched-vscode/src/vs/base/test/common/labels.test.ts index 6c9d3eb0..b74f0238 100644 --- a/patched-vscode/src/vs/base/test/common/labels.test.ts +++ b/patched-vscode/src/vs/base/test/common/labels.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as labels from 'vs/base/common/labels'; import { isMacintosh, isWindows, OperatingSystem } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/base/test/common/lazy.test.ts b/patched-vscode/src/vs/base/test/common/lazy.test.ts index 361e3305..220d0da6 100644 --- a/patched-vscode/src/vs/base/test/common/lazy.test.ts +++ b/patched-vscode/src/vs/base/test/common/lazy.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Lazy } from 'vs/base/common/lazy'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/lifecycle.test.ts b/patched-vscode/src/vs/base/test/common/lifecycle.test.ts index 103009b5..23080bc8 100644 --- a/patched-vscode/src/vs/base/test/common/lifecycle.test.ts +++ b/patched-vscode/src/vs/base/test/common/lifecycle.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore, dispose, IDisposable, markAsSingleton, ReferenceCollection, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite, throwIfDisposablesAreLeaked } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/linkedList.test.ts b/patched-vscode/src/vs/base/test/common/linkedList.test.ts index 181916d9..8bd3f651 100644 --- a/patched-vscode/src/vs/base/test/common/linkedList.test.ts +++ b/patched-vscode/src/vs/base/test/common/linkedList.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { LinkedList } from 'vs/base/common/linkedList'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/linkedText.test.ts b/patched-vscode/src/vs/base/test/common/linkedText.test.ts index dc3bdbd0..63f76d08 100644 --- a/patched-vscode/src/vs/base/test/common/linkedText.test.ts +++ b/patched-vscode/src/vs/base/test/common/linkedText.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { parseLinkedText } from 'vs/base/common/linkedText'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/map.test.ts b/patched-vscode/src/vs/base/test/common/map.test.ts index f92234c3..f837920f 100644 --- a/patched-vscode/src/vs/base/test/common/map.test.ts +++ b/patched-vscode/src/vs/base/test/common/map.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { BidirectionalMap, LinkedMap, LRUCache, mapsStrictEqualIgnoreOrder, MRUCache, ResourceMap, SetMap, Touch } from 'vs/base/common/map'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/base/test/common/markdownString.test.ts b/patched-vscode/src/vs/base/test/common/markdownString.test.ts index cb7df169..4c402692 100644 --- a/patched-vscode/src/vs/base/test/common/markdownString.test.ts +++ b/patched-vscode/src/vs/base/test/common/markdownString.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/marshalling.test.ts b/patched-vscode/src/vs/base/test/common/marshalling.test.ts index 7d3bb66c..94d30b5b 100644 --- a/patched-vscode/src/vs/base/test/common/marshalling.test.ts +++ b/patched-vscode/src/vs/base/test/common/marshalling.test.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 * as assert from 'assert'; +import assert from 'assert'; import { parse, stringify } from 'vs/base/common/marshalling'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/mime.test.ts b/patched-vscode/src/vs/base/test/common/mime.test.ts index fce56646..96fa3b62 100644 --- a/patched-vscode/src/vs/base/test/common/mime.test.ts +++ b/patched-vscode/src/vs/base/test/common/mime.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { normalizeMimeType } from 'vs/base/common/mime'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/network.test.ts b/patched-vscode/src/vs/base/test/common/network.test.ts index a85b2b39..d741b0e4 100644 --- a/patched-vscode/src/vs/base/test/common/network.test.ts +++ b/patched-vscode/src/vs/base/test/common/network.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; diff --git a/patched-vscode/src/vs/base/test/common/normalization.test.ts b/patched-vscode/src/vs/base/test/common/normalization.test.ts index 65b44eb6..58f4fe77 100644 --- a/patched-vscode/src/vs/base/test/common/normalization.test.ts +++ b/patched-vscode/src/vs/base/test/common/normalization.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { removeAccents } from 'vs/base/common/normalization'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/numbers.test.ts b/patched-vscode/src/vs/base/test/common/numbers.test.ts new file mode 100644 index 00000000..7095b7aa --- /dev/null +++ b/patched-vscode/src/vs/base/test/common/numbers.test.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { isPointWithinTriangle } from 'vs/base/common/numbers'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; + +suite('isPointWithinTriangle', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('should return true if the point is within the triangle', () => { + const result = isPointWithinTriangle(0.25, 0.25, 0, 0, 1, 0, 0, 1); + assert.ok(result); + }); + + test('should return false if the point is outside the triangle', () => { + const result = isPointWithinTriangle(2, 2, 0, 0, 1, 0, 0, 1); + assert.ok(!result); + }); + + test('should return true if the point is on the edge of the triangle', () => { + const result = isPointWithinTriangle(0.5, 0, 0, 0, 1, 0, 0, 1); + assert.ok(result); + }); +}); diff --git a/patched-vscode/src/vs/base/test/common/objects.test.ts b/patched-vscode/src/vs/base/test/common/objects.test.ts index 24655855..ce600eb0 100644 --- a/patched-vscode/src/vs/base/test/common/objects.test.ts +++ b/patched-vscode/src/vs/base/test/common/objects.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as objects from 'vs/base/common/objects'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/observable.test.ts b/patched-vscode/src/vs/base/test/common/observable.test.ts index e2694d5c..2cfbc025 100644 --- a/patched-vscode/src/vs/base/test/common/observable.test.ts +++ b/patched-vscode/src/vs/base/test/common/observable.test.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepObserved, waitForState, autorunHandleChanges, observableSignal } from 'vs/base/common/observable'; import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableInternal/base'; +import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('observables', () => { @@ -1123,6 +1125,31 @@ suite('observables', () => { ]); }); + test('bug: Event.fromObservable always should get events', () => { + const emitter = new Emitter(); + const log = new Log(); + let i = 0; + const obs = observableFromEvent(emitter.event, () => i); + + i++; + emitter.fire(1); + + const evt2 = Event.fromObservable(obs); + const d = evt2(e => { + log.log(`event fired ${e}`); + }); + + i++; + emitter.fire(2); + assert.deepStrictEqual(log.getAndClearEntries(), ["event fired 2"]); + + i++; + emitter.fire(3); + assert.deepStrictEqual(log.getAndClearEntries(), ["event fired 3"]); + + d.dispose(); + }); + test('dont run autorun after dispose', () => { const log = new Log(); const myObservable = new LoggingObservableValue('myObservable', 0, log); @@ -1236,6 +1263,35 @@ suite('observables', () => { 'rejected {\"state\":\"error\"}' ]); }); + + test('derived as lazy', () => { + const store = new DisposableStore(); + const log = new Log(); + let i = 0; + const d = derivedDisposable(() => { + const id = i++; + log.log('myDerived ' + id); + return { + dispose: () => log.log(`disposed ${id}`) + }; + }); + + d.get(); + assert.deepStrictEqual(log.getAndClearEntries(), ['myDerived 0', 'disposed 0']); + d.get(); + assert.deepStrictEqual(log.getAndClearEntries(), ['myDerived 1', 'disposed 1']); + + d.keepObserved(store); + assert.deepStrictEqual(log.getAndClearEntries(), []); + d.get(); + assert.deepStrictEqual(log.getAndClearEntries(), ['myDerived 2']); + d.get(); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + store.dispose(); + + assert.deepStrictEqual(log.getAndClearEntries(), ['disposed 2']); + }); }); test('observableValue', () => { diff --git a/patched-vscode/src/vs/base/test/common/paging.test.ts b/patched-vscode/src/vs/base/test/common/paging.test.ts index f0ff02a4..772083f3 100644 --- a/patched-vscode/src/vs/base/test/common/paging.test.ts +++ b/patched-vscode/src/vs/base/test/common/paging.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { disposableTimeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { CancellationError, isCancellationError } from 'vs/base/common/errors'; diff --git a/patched-vscode/src/vs/base/test/common/path.test.ts b/patched-vscode/src/vs/base/test/common/path.test.ts index 62b43c91..f42abd1f 100644 --- a/patched-vscode/src/vs/base/test/common/path.test.ts +++ b/patched-vscode/src/vs/base/test/common/path.test.ts @@ -27,7 +27,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -import * as assert from 'assert'; +import assert from 'assert'; import * as path from 'vs/base/common/path'; import { isWeb, isWindows } from 'vs/base/common/platform'; import * as process from 'vs/base/common/process'; diff --git a/patched-vscode/src/vs/base/test/common/prefixTree.test.ts b/patched-vscode/src/vs/base/test/common/prefixTree.test.ts index e5545734..10b77d96 100644 --- a/patched-vscode/src/vs/base/test/common/prefixTree.test.ts +++ b/patched-vscode/src/vs/base/test/common/prefixTree.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { WellDefinedPrefixTree } from 'vs/base/common/prefixTree'; -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('WellDefinedPrefixTree', () => { diff --git a/patched-vscode/src/vs/base/test/common/processes.test.ts b/patched-vscode/src/vs/base/test/common/processes.test.ts index d575590a..6c0d88e6 100644 --- a/patched-vscode/src/vs/base/test/common/processes.test.ts +++ b/patched-vscode/src/vs/base/test/common/processes.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as processes from 'vs/base/common/processes'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/resourceTree.test.ts b/patched-vscode/src/vs/base/test/common/resourceTree.test.ts index 9024269a..51fba68f 100644 --- a/patched-vscode/src/vs/base/test/common/resourceTree.test.ts +++ b/patched-vscode/src/vs/base/test/common/resourceTree.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ResourceTree } from 'vs/base/common/resourceTree'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/resources.test.ts b/patched-vscode/src/vs/base/test/common/resources.test.ts index d1e4e102..d2315644 100644 --- a/patched-vscode/src/vs/base/test/common/resources.test.ts +++ b/patched-vscode/src/vs/base/test/common/resources.test.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 * as assert from 'assert'; +import assert from 'assert'; import { toSlashes } from 'vs/base/common/extpath'; import { posix, win32 } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/base/test/common/scrollable.test.ts b/patched-vscode/src/vs/base/test/common/scrollable.test.ts index 7059d813..41a33727 100644 --- a/patched-vscode/src/vs/base/test/common/scrollable.test.ts +++ b/patched-vscode/src/vs/base/test/common/scrollable.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { SmoothScrollingOperation, SmoothScrollingUpdate } from 'vs/base/common/scrollable'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/skipList.test.ts b/patched-vscode/src/vs/base/test/common/skipList.test.ts index 8dadb0e3..096bd6a2 100644 --- a/patched-vscode/src/vs/base/test/common/skipList.test.ts +++ b/patched-vscode/src/vs/base/test/common/skipList.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { binarySearch } from 'vs/base/common/arrays'; import { SkipList } from 'vs/base/common/skipList'; import { StopWatch } from 'vs/base/common/stopwatch'; diff --git a/patched-vscode/src/vs/base/test/common/stream.test.ts b/patched-vscode/src/vs/base/test/common/stream.test.ts index 5589dfdc..a8473f0f 100644 --- a/patched-vscode/src/vs/base/test/common/stream.test.ts +++ b/patched-vscode/src/vs/base/test/common/stream.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { bufferToReadable, VSBuffer } from 'vs/base/common/buffer'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; diff --git a/patched-vscode/src/vs/base/test/common/strings.test.ts b/patched-vscode/src/vs/base/test/common/strings.test.ts index 4be439f4..b84a193e 100644 --- a/patched-vscode/src/vs/base/test/common/strings.test.ts +++ b/patched-vscode/src/vs/base/test/common/strings.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as strings from 'vs/base/common/strings'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/ternarySearchtree.test.ts b/patched-vscode/src/vs/base/test/common/ternarySearchtree.test.ts index c1a8cc77..1e684933 100644 --- a/patched-vscode/src/vs/base/test/common/ternarySearchtree.test.ts +++ b/patched-vscode/src/vs/base/test/common/ternarySearchtree.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { shuffle } from 'vs/base/common/arrays'; import { randomPath } from 'vs/base/common/extpath'; import { StopWatch } from 'vs/base/common/stopwatch'; diff --git a/patched-vscode/src/vs/base/test/common/tfIdf.test.ts b/patched-vscode/src/vs/base/test/common/tfIdf.test.ts index 3a423f25..df5e37da 100644 --- a/patched-vscode/src/vs/base/test/common/tfIdf.test.ts +++ b/patched-vscode/src/vs/base/test/common/tfIdf.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { TfIdfCalculator, TfIdfDocument, TfIdfScore } from 'vs/base/common/tfIdf'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/types.test.ts b/patched-vscode/src/vs/base/test/common/types.test.ts index 93e24649..f9b894fb 100644 --- a/patched-vscode/src/vs/base/test/common/types.test.ts +++ b/patched-vscode/src/vs/base/test/common/types.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as types from 'vs/base/common/types'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/uri.test.ts b/patched-vscode/src/vs/base/test/common/uri.test.ts index 69e2abe7..fa65209a 100644 --- a/patched-vscode/src/vs/base/test/common/uri.test.ts +++ b/patched-vscode/src/vs/base/test/common/uri.test.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 * as assert from 'assert'; +import assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; import { URI, UriComponents, isUriComponents } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/common/uuid.test.ts b/patched-vscode/src/vs/base/test/common/uuid.test.ts index a8defebb..1cfbf5c1 100644 --- a/patched-vscode/src/vs/base/test/common/uuid.test.ts +++ b/patched-vscode/src/vs/base/test/common/uuid.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as uuid from 'vs/base/common/uuid'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/node/crypto.test.ts b/patched-vscode/src/vs/base/test/node/crypto.test.ts index ff929aaa..5da7d91f 100644 --- a/patched-vscode/src/vs/base/test/node/crypto.test.ts +++ b/patched-vscode/src/vs/base/test/node/crypto.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { tmpdir } from 'os'; import { join } from 'vs/base/common/path'; import { checksum } from 'vs/base/node/crypto'; @@ -19,7 +20,7 @@ flakySuite('Crypto', () => { setup(function () { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'crypto'); - return Promises.mkdir(testDir, { recursive: true }); + return fs.promises.mkdir(testDir, { recursive: true }); }); teardown(function () { diff --git a/patched-vscode/src/vs/base/test/node/css.build.test.ts b/patched-vscode/src/vs/base/test/node/css.build.test.ts index 16c79d41..4f824b37 100644 --- a/patched-vscode/src/vs/base/test/node/css.build.test.ts +++ b/patched-vscode/src/vs/base/test/node/css.build.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CSSPluginUtilities, rewriteUrls } from 'vs/css.build'; diff --git a/patched-vscode/src/vs/base/test/node/extpath.test.ts b/patched-vscode/src/vs/base/test/node/extpath.test.ts index 04aa1873..bf895f67 100644 --- a/patched-vscode/src/vs/base/test/node/extpath.test.ts +++ b/patched-vscode/src/vs/base/test/node/extpath.test.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import * as fs from 'fs'; +import assert from 'assert'; import { tmpdir } from 'os'; import { realcase, realcaseSync, realpath, realpathSync } from 'vs/base/node/extpath'; import { Promises } from 'vs/base/node/pfs'; @@ -16,7 +17,7 @@ flakySuite('Extpath', () => { setup(() => { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'extpath'); - return Promises.mkdir(testDir, { recursive: true }); + return fs.promises.mkdir(testDir, { recursive: true }); }); teardown(() => { diff --git a/patched-vscode/src/vs/base/test/node/id.test.ts b/patched-vscode/src/vs/base/test/node/id.test.ts index 1a629134..259b2312 100644 --- a/patched-vscode/src/vs/base/test/node/id.test.ts +++ b/patched-vscode/src/vs/base/test/node/id.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { getMachineId, getSqmMachineId, getdevDeviceId } from 'vs/base/node/id'; import { getMac } from 'vs/base/node/macAddress'; import { flakySuite } from 'vs/base/test/node/testUtils'; diff --git a/patched-vscode/src/vs/base/test/node/nodeStreams.test.ts b/patched-vscode/src/vs/base/test/node/nodeStreams.test.ts index 8cab1cc8..44e7dd91 100644 --- a/patched-vscode/src/vs/base/test/node/nodeStreams.test.ts +++ b/patched-vscode/src/vs/base/test/node/nodeStreams.test.ts @@ -5,7 +5,7 @@ import { Writable } from 'stream'; -import * as assert from 'assert'; +import assert from 'assert'; import { StreamSplitter } from 'vs/base/node/nodeStreams'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/node/pfs/pfs.test.ts b/patched-vscode/src/vs/base/test/node/pfs/pfs.test.ts index b3ef62f2..1d97e68b 100644 --- a/patched-vscode/src/vs/base/test/node/pfs/pfs.test.ts +++ b/patched-vscode/src/vs/base/test/node/pfs/pfs.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as fs from 'fs'; import { tmpdir } from 'os'; import { timeout } from 'vs/base/common/async'; @@ -25,7 +25,7 @@ flakySuite('PFS', function () { setup(() => { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'pfs'); - return Promises.mkdir(testDir, { recursive: true }); + return fs.promises.mkdir(testDir, { recursive: true }); }); teardown(() => { @@ -39,7 +39,7 @@ flakySuite('PFS', function () { await Promises.writeFile(testFile, 'Hello World', (null!)); - assert.strictEqual((await Promises.readFile(testFile)).toString(), 'Hello World'); + assert.strictEqual((await fs.promises.readFile(testFile)).toString(), 'Hello World'); }); test('writeFile - parallel write on different files works', async () => { @@ -241,7 +241,7 @@ flakySuite('PFS', function () { const symLink = randomPath(testDir); const copyTarget = randomPath(testDir); - await Promises.mkdir(symbolicLinkTarget, { recursive: true }); + await fs.promises.mkdir(symbolicLinkTarget, { recursive: true }); fs.symlinkSync(symbolicLinkTarget, symLink, 'junction'); @@ -258,7 +258,7 @@ flakySuite('PFS', function () { assert.ok(symbolicLink); assert.ok(!symbolicLink.dangling); - const target = await Promises.readlink(copyTarget); + const target = await fs.promises.readlink(copyTarget); assert.strictEqual(target, symbolicLinkTarget); // Copy does not preserve symlinks if configured as such @@ -294,7 +294,7 @@ flakySuite('PFS', function () { const sourceLinkTestFolder = join(sourceFolder, 'link-test'); // copy-test/link-test const sourceLinkMD5JSFolder = join(sourceLinkTestFolder, 'md5'); // copy-test/link-test/md5 const sourceLinkMD5JSFile = join(sourceLinkMD5JSFolder, 'md5.js'); // copy-test/link-test/md5/md5.js - await Promises.mkdir(sourceLinkMD5JSFolder, { recursive: true }); + await fs.promises.mkdir(sourceLinkMD5JSFolder, { recursive: true }); await Promises.writeFile(sourceLinkMD5JSFile, 'Hello from MD5'); const sourceLinkMD5JSFolderLinked = join(sourceLinkTestFolder, 'md5-linked'); // copy-test/link-test/md5-linked @@ -319,7 +319,7 @@ flakySuite('PFS', function () { assert.ok(fs.existsSync(targetLinkMD5JSFolderLinked)); assert.ok(fs.lstatSync(targetLinkMD5JSFolderLinked).isSymbolicLink()); - const linkTarget = await Promises.readlink(targetLinkMD5JSFolderLinked); + const linkTarget = await fs.promises.readlink(targetLinkMD5JSFolderLinked); assert.strictEqual(linkTarget, targetLinkMD5JSFolder); await Promises.rm(targetLinkTestFolder); @@ -353,7 +353,7 @@ flakySuite('PFS', function () { const directory = randomPath(testDir); const symbolicLink = randomPath(testDir); - await Promises.mkdir(directory, { recursive: true }); + await fs.promises.mkdir(directory, { recursive: true }); fs.symlinkSync(directory, symbolicLink, 'junction'); @@ -369,7 +369,7 @@ flakySuite('PFS', function () { const directory = randomPath(testDir); const symbolicLink = randomPath(testDir); - await Promises.mkdir(directory, { recursive: true }); + await fs.promises.mkdir(directory, { recursive: true }); fs.symlinkSync(directory, symbolicLink, 'junction'); @@ -385,7 +385,7 @@ flakySuite('PFS', function () { const parent = randomPath(join(testDir, 'pfs')); const newDir = join(parent, 'öäü'); - await Promises.mkdir(newDir, { recursive: true }); + await fs.promises.mkdir(newDir, { recursive: true }); assert.ok(fs.existsSync(newDir)); @@ -397,7 +397,7 @@ flakySuite('PFS', function () { test('readdir (with file types)', async () => { if (typeof process.versions['electron'] !== 'undefined' /* needs electron */) { const newDir = join(testDir, 'öäü'); - await Promises.mkdir(newDir, { recursive: true }); + await fs.promises.mkdir(newDir, { recursive: true }); await Promises.writeFile(join(testDir, 'somefile.txt'), 'contents'); diff --git a/patched-vscode/src/vs/base/test/node/port.test.ts b/patched-vscode/src/vs/base/test/node/port.test.ts index 9230dc84..048cfc69 100644 --- a/patched-vscode/src/vs/base/test/node/port.test.ts +++ b/patched-vscode/src/vs/base/test/node/port.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as net from 'net'; import * as ports from 'vs/base/node/ports'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/base/test/node/powershell.test.ts b/patched-vscode/src/vs/base/test/node/powershell.test.ts index a710bb80..de9b669c 100644 --- a/patched-vscode/src/vs/base/test/node/powershell.test.ts +++ b/patched-vscode/src/vs/base/test/node/powershell.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as fs from 'fs'; import * as platform from 'vs/base/common/platform'; import { enumeratePowerShellInstallations, getFirstAvailablePowerShellInstallation, IPowerShellExeDetails } from 'vs/base/node/powershell'; diff --git a/patched-vscode/src/vs/base/test/node/processes/processes.integrationTest.ts b/patched-vscode/src/vs/base/test/node/processes/processes.integrationTest.ts index ff52bf02..f8e827a8 100644 --- a/patched-vscode/src/vs/base/test/node/processes/processes.integrationTest.ts +++ b/patched-vscode/src/vs/base/test/node/processes/processes.integrationTest.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as cp from 'child_process'; import { FileAccess } from 'vs/base/common/network'; import * as objects from 'vs/base/common/objects'; diff --git a/patched-vscode/src/vs/base/test/node/snapshot.test.ts b/patched-vscode/src/vs/base/test/node/snapshot.test.ts index 48621bed..c98dfc76 100644 --- a/patched-vscode/src/vs/base/test/node/snapshot.test.ts +++ b/patched-vscode/src/vs/base/test/node/snapshot.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { tmpdir } from 'os'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { Promises } from 'vs/base/node/pfs'; @@ -23,7 +24,7 @@ suite('snapshot', () => { setup(function () { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'snapshot'); - return Promises.mkdir(testDir, { recursive: true }); + return fs.promises.mkdir(testDir, { recursive: true }); }); teardown(function () { @@ -46,8 +47,8 @@ suite('snapshot', () => { const children = await Promises.readdir(dir); for (const child of children) { const p = path.join(dir, child); - if ((await Promises.stat(p)).isFile()) { - const content = await Promises.readFile(p, 'utf-8'); + if ((await fs.promises.stat(p)).isFile()) { + const content = await fs.promises.readFile(p, 'utf-8'); str += `${' '.repeat(indent)}${child}:\n`; for (const line of content.split('\n')) { str += `${' '.repeat(indent + 2)}${line}\n`; diff --git a/patched-vscode/src/vs/base/test/node/uri.perf.test.ts b/patched-vscode/src/vs/base/test/node/uri.perf.test.ts index 389f3c99..cd045f15 100644 --- a/patched-vscode/src/vs/base/test/node/uri.perf.test.ts +++ b/patched-vscode/src/vs/base/test/node/uri.perf.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { readFileSync } from 'fs'; import { FileAccess } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/base/test/node/zip/zip.test.ts b/patched-vscode/src/vs/base/test/node/zip/zip.test.ts index 0a898e2c..0db7981f 100644 --- a/patched-vscode/src/vs/base/test/node/zip/zip.test.ts +++ b/patched-vscode/src/vs/base/test/node/zip/zip.test.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import * as fs from 'fs'; +import assert from 'assert'; import { tmpdir } from 'os'; import { createCancelablePromise } from 'vs/base/common/async'; import { FileAccess } from 'vs/base/common/network'; @@ -19,7 +20,7 @@ suite('Zip', () => { test('extract should handle directories', async () => { const testDir = getRandomTestPath(tmpdir(), 'vsctests', 'zip'); - await Promises.mkdir(testDir, { recursive: true }); + await fs.promises.mkdir(testDir, { recursive: true }); const fixtures = FileAccess.asFileUri('vs/base/test/node/zip/fixtures').fsPath; const fixture = path.join(fixtures, 'extract.zip'); diff --git a/patched-vscode/src/vs/base/worker/workerMain.ts b/patched-vscode/src/vs/base/worker/workerMain.ts index 35c685ff..cb50be48 100644 --- a/patched-vscode/src/vs/base/worker/workerMain.ts +++ b/patched-vscode/src/vs/base/worker/workerMain.ts @@ -13,6 +13,7 @@ ): undefined | Pick, 'name' | Extract>; } const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; + const monacoBaseUrl = monacoEnvironment && monacoEnvironment.baseUrl ? monacoEnvironment.baseUrl : '../../../'; function createTrustedTypesPolicy( @@ -111,23 +112,43 @@ }); } - function loadCode(moduleId: string) { - loadAMDLoader().then(() => { + function loadCode(moduleId: string): Promise { + // ESM-uncomment-begin + // if (typeof loadAMDLoader === 'function') { /* fixes unused import, remove me */} + // const moduleUrl = new URL(`${moduleId}.js`, globalThis._VSCODE_FILE_ROOT); + // return import(moduleUrl.href); + // ESM-uncomment-end + + // ESM-comment-begin + return loadAMDLoader().then(() => { configureAMDLoader(); - require([moduleId], function (ws) { - setTimeout(function () { - const messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { - (globalThis).postMessage(msg, transfer); - }, null); - - globalThis.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data, e.ports); - while (beforeReadyMessages.length > 0) { - const e = beforeReadyMessages.shift()!; - messageHandler.onmessage(e.data, e.ports); - } - }, 0); + return new Promise((resolve, reject) => { + require([moduleId], resolve, reject); }); }); + // ESM-comment-end + } + + interface MessageHandler { + onmessage(msg: any, ports: readonly MessagePort[]): void; + } + + // shape of vs/base/common/worker/simpleWorker.ts + interface SimpleWorkerModule { + create(postMessage: (msg: any, transfer?: Transferable[]) => void): MessageHandler; + } + + function setupWorkerServer(ws: SimpleWorkerModule) { + setTimeout(function () { + const messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { + (globalThis).postMessage(msg, transfer); + }); + + self.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data, e.ports); + while (beforeReadyMessages.length > 0) { + self.onmessage(beforeReadyMessages.shift()!); + } + }, 0); } // If the loader is already defined, configure it immediately @@ -146,6 +167,10 @@ } isFirstMessage = false; - loadCode(message.data); + loadCode(message.data).then((ws) => { + setupWorkerServer(ws); + }, (err) => { + console.error(err); + }); }; })(); diff --git a/patched-vscode/src/vs/code/browser/workbench/workbench-dev.esm.html b/patched-vscode/src/vs/code/browser/workbench/workbench-dev.esm.html new file mode 100644 index 00000000..140030d8 --- /dev/null +++ b/patched-vscode/src/vs/code/browser/workbench/workbench-dev.esm.html @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/code/browser/workbench/workbench-dev.html b/patched-vscode/src/vs/code/browser/workbench/workbench-dev.html index f6448157..be6d30bf 100644 --- a/patched-vscode/src/vs/code/browser/workbench/workbench-dev.html +++ b/patched-vscode/src/vs/code/browser/workbench/workbench-dev.html @@ -38,7 +38,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/code/browser/workbench/workbench.html b/patched-vscode/src/vs/code/browser/workbench/workbench.html index cfee57cc..019f0b94 100644 --- a/patched-vscode/src/vs/code/browser/workbench/workbench.html +++ b/patched-vscode/src/vs/code/browser/workbench/workbench.html @@ -36,25 +36,14 @@ - + + + + diff --git a/patched-vscode/src/vs/code/browser/workbench/workbench.ts b/patched-vscode/src/vs/code/browser/workbench/workbench.ts index 624ac4f9..f8875029 100644 --- a/patched-vscode/src/vs/code/browser/workbench/workbench.ts +++ b/patched-vscode/src/vs/code/browser/workbench/workbench.ts @@ -304,8 +304,7 @@ class LocalStorageURLCallbackProvider extends Disposable implements IURLCallback this.startListening(); } - const path = (mainWindow.location.pathname + "/" + this._callbackRoute).replace(/\/\/+/g, "/"); - return URI.parse(mainWindow.location.href).with({ path: path, query: queryParams.join('&') }); + return URI.parse(mainWindow.location.href).with({ path: this._callbackRoute, query: queryParams.join('&') }); } private startListening(): void { diff --git a/patched-vscode/src/vs/code/electron-main/app.ts b/patched-vscode/src/vs/code/electron-main/app.ts index 46193cdb..ca47f74b 100644 --- a/patched-vscode/src/vs/code/electron-main/app.ts +++ b/patched-vscode/src/vs/code/electron-main/app.ts @@ -10,13 +10,12 @@ import { hostname, release } from 'os'; import { VSBuffer } from 'vs/base/common/buffer'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; -import { isEqualOrParent } from 'vs/base/common/extpath'; import { Event } from 'vs/base/common/event'; -import { stripComments } from 'vs/base/common/json'; +import { parse } from 'vs/base/common/jsonc'; import { getPathLabel } from 'vs/base/common/labels'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network'; -import { isAbsolute, join, posix } from 'vs/base/common/path'; +import { join, posix } from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isLinuxSnap, isMacintosh, isWindows, OS } from 'vs/base/common/platform'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -26,7 +25,7 @@ import { getDelayedChannel, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp'; import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net'; -import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; +import { IProxyAuthService, ProxyAuthService } from 'vs/platform/native/electron-main/auth'; import { localize } from 'vs/nls'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; @@ -52,8 +51,9 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IIssueMainService } from 'vs/platform/issue/common/issue'; +import { IProcessMainService, IIssueMainService } from 'vs/platform/issue/common/issue'; import { IssueMainService } from 'vs/platform/issue/electron-main/issueMainService'; +import { ProcessMainService } from 'vs/platform/issue/electron-main/processMainService'; import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService'; import { ILaunchMainService, LaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; @@ -121,6 +121,7 @@ import { Lazy } from 'vs/base/common/lazy'; import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { AuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService'; import { normalizeNFC } from 'vs/base/common/normalization'; +import { ICSSDevelopmentService, CSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; /** * The main VS Code application. There will only ever be one instance, @@ -366,7 +367,7 @@ export class CodeApplication extends Disposable { process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); // Dispose on shutdown - this.lifecycleMainService.onWillShutdown(() => this.dispose()); + Event.once(this.lifecycleMainService.onWillShutdown)(() => this.dispose()); // Contextmenu via IPC support registerContextMenuListener(); @@ -496,24 +497,6 @@ export class CodeApplication extends Disposable { return this.resolveShellEnvironment(args, env, false); }); - validatedIpcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => { - const uri = this.validateNlsPath([path]); - if (!uri || typeof data !== 'string') { - throw new Error('Invalid operation (vscode:writeNlsFile)'); - } - - return this.fileService.writeFile(uri, VSBuffer.fromString(data)); - }); - - validatedIpcMain.handle('vscode:readNlsFile', async (event, ...paths: unknown[]) => { - const uri = this.validateNlsPath(paths); - if (!uri) { - throw new Error('Invalid operation (vscode:readNlsFile)'); - } - - return (await this.fileService.readFile(uri)).value.toString(); - }); - validatedIpcMain.on('vscode:toggleDevTools', event => event.sender.toggleDevTools()); validatedIpcMain.on('vscode:openDevTools', event => event.sender.openDevTools()); @@ -529,26 +512,6 @@ export class CodeApplication extends Disposable { //#endregion } - private validateNlsPath(pathSegments: unknown[]): URI | undefined { - let path: string | undefined = undefined; - - for (const pathSegment of pathSegments) { - if (typeof pathSegment === 'string') { - if (typeof path !== 'string') { - path = pathSegment; - } else { - path = join(path, pathSegment); - } - } - } - - if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentMainService.cachedLanguagesPath, !isLinux)) { - return undefined; - } - - return URI.file(path); - } - private onUnexpectedError(error: Error): void { if (error) { @@ -598,7 +561,7 @@ export class CodeApplication extends Disposable { // Main process server (electron IPC based) const mainProcessElectronServer = new ElectronIPCServer(); - this.lifecycleMainService.onWillShutdown(e => { + Event.once(this.lifecycleMainService.onWillShutdown)(e => { if (e.reason === ShutdownReason.KILL) { // When we go down abnormally, make sure to free up // any IPC we accept from other windows to reduce @@ -625,7 +588,7 @@ export class CodeApplication extends Disposable { const appInstantiationService = await this.initServices(machineId, sqmId, devDeviceId, sharedProcessReady); // Auth Handler - this._register(appInstantiationService.createInstance(ProxyAuthHandler)); + appInstantiationService.invokeFunction(accessor => accessor.get(IProxyAuthService)); // Transient profiles handler this._register(appInstantiationService.createInstance(UserDataProfilesHandler)); @@ -865,34 +828,38 @@ export class CodeApplication extends Disposable { // To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */); + let authority: string; + let path: string; if (secondSlash !== -1) { - const authority = uri.path.substring(1, secondSlash); - const path = uri.path.substring(secondSlash); - - let query = uri.query; - const params = new URLSearchParams(uri.query); - if (params.get('windowId') === '_blank') { - // Make sure to unset any `windowId=_blank` here - // https://github.com/microsoft/vscode/issues/191902 - params.delete('windowId'); - query = params.toString(); - } + authority = uri.path.substring(1, secondSlash); + path = uri.path.substring(secondSlash); + } else { + authority = uri.path.substring(1); + path = '/'; + } - const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query, fragment: uri.fragment }); + let query = uri.query; + const params = new URLSearchParams(uri.query); + if (params.get('windowId') === '_blank') { + // Make sure to unset any `windowId=_blank` here + // https://github.com/microsoft/vscode/issues/191902 + params.delete('windowId'); + query = params.toString(); + } - if (hasWorkspaceFileExtension(path)) { - return { workspaceUri: remoteUri }; - } + const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query, fragment: uri.fragment }); - if (/:[\d]+$/.test(path)) { - // path with :line:column syntax - return { fileUri: remoteUri }; - } + if (hasWorkspaceFileExtension(path)) { + return { workspaceUri: remoteUri }; + } - return { folderUri: remoteUri }; + if (/:[\d]+$/.test(path)) { + // path with :line:column syntax + return { fileUri: remoteUri }; } - } + return { folderUri: remoteUri }; + } return undefined; } @@ -950,7 +917,7 @@ export class CodeApplication extends Disposable { this.logService.trace('app#handleProtocolUrl() opening protocol url as window:', windowOpenableFromProtocolUrl, uri.toString(true)); const window = firstOrDefault(await windowsMainService.open({ - context: OpenContext.API, + context: OpenContext.LINK, cli: { ...this.environmentMainService.args }, urisToOpen: [windowOpenableFromProtocolUrl], forceNewWindow: shouldOpenInNewWindow, @@ -969,7 +936,7 @@ export class CodeApplication extends Disposable { this.logService.trace('app#handleProtocolUrl() opening empty window and passing in protocol url:', uri.toString(true)); const window = firstOrDefault(await windowsMainService.open({ - context: OpenContext.API, + context: OpenContext.LINK, cli: { ...this.environmentMainService.args }, forceNewWindow: true, forceEmpty: true, @@ -1051,6 +1018,9 @@ export class CodeApplication extends Disposable { // Issues services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [this.userEnv])); + // Process + services.set(IProcessMainService, new SyncDescriptor(ProcessMainService, [this.userEnv])); + // Encryption services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService)); @@ -1130,6 +1100,12 @@ export class CodeApplication extends Disposable { // Utility Process Worker services.set(IUtilityProcessWorkerMainService, new SyncDescriptor(UtilityProcessWorkerMainService, undefined, true)); + // Proxy Auth + services.set(IProxyAuthService, new SyncDescriptor(ProxyAuthService)); + + // Dev Only: CSS service (for ESM) + services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true)); + // Init services that require it await Promises.settled([ backupMainService.initialize(), @@ -1183,6 +1159,10 @@ export class CodeApplication extends Disposable { const issueChannel = ProxyChannel.fromService(accessor.get(IIssueMainService), disposables); mainProcessElectronServer.registerChannel('issue', issueChannel); + // Process + const processChannel = ProxyChannel.fromService(accessor.get(IProcessMainService), disposables); + mainProcessElectronServer.registerChannel('process', processChannel); + // Encryption const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService), disposables); mainProcessElectronServer.registerChannel('encryption', encryptionChannel); @@ -1394,10 +1374,10 @@ export class CodeApplication extends Disposable { // Crash reporter this.updateCrashReporterEnablement(); + // macOS: rosetta translation warning if (isMacintosh && app.runningUnderARM64Translation) { this.windowsMainService?.sendToFocused('vscode:showTranslatedBuildWarning'); } - } private async installMutex(): Promise { @@ -1437,7 +1417,7 @@ export class CodeApplication extends Disposable { try { const argvContent = await this.fileService.readFile(this.environmentMainService.argvResource); const argvString = argvContent.value.toString(); - const argvJSON = JSON.parse(stripComments(argvString)); + const argvJSON = parse(argvString); const telemetryLevel = getTelemetryLevel(this.configurationService); const enableCrashReporter = telemetryLevel >= TelemetryLevel.CRASH; @@ -1468,6 +1448,9 @@ export class CodeApplication extends Disposable { } } catch (error) { this.logService.error(error); + + // Inform the user via notification + this.windowsMainService?.sendToFocused('vscode:showArgvParseWarning'); } } } diff --git a/patched-vscode/src/vs/code/electron-main/main.ts b/patched-vscode/src/vs/code/electron-main/main.ts index 8548fa7c..deb0d1ea 100644 --- a/patched-vscode/src/vs/code/electron-main/main.ts +++ b/patched-vscode/src/vs/code/electron-main/main.ts @@ -6,7 +6,7 @@ import 'vs/platform/update/common/update.config.contribution'; import { app, dialog } from 'electron'; -import { unlinkSync } from 'fs'; +import { unlinkSync, promises } from 'fs'; import { URI } from 'vs/base/common/uri'; import { coalesce, distinct } from 'vs/base/common/arrays'; import { Promises } from 'vs/base/common/async'; @@ -139,7 +139,7 @@ class CodeMain { Event.once(lifecycleMainService.onWillShutdown)(evt => { fileService.dispose(); configurationService.dispose(); - evt.join('instanceLockfile', FSPromises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ })); + evt.join('instanceLockfile', promises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ })); }); return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup(); @@ -257,7 +257,7 @@ class CodeMain { environmentMainService.workspaceStorageHome.with({ scheme: Schemas.file }).fsPath, environmentMainService.localHistoryHome.with({ scheme: Schemas.file }).fsPath, environmentMainService.backupHome - ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), + ].map(path => path ? promises.mkdir(path, { recursive: true }) : undefined)), // State service stateService.init(), diff --git a/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.esm.html b/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.esm.html new file mode 100644 index 00000000..19d194fc --- /dev/null +++ b/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.esm.html @@ -0,0 +1,42 @@ + + + + + + + + + +
    + + + + + + diff --git a/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.html b/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.html index 55f2c0fe..5bdf62c8 100644 --- a/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.html +++ b/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.html @@ -35,8 +35,6 @@ - - - + diff --git a/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer.esm.html b/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer.esm.html new file mode 100644 index 00000000..d2747202 --- /dev/null +++ b/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer.esm.html @@ -0,0 +1,42 @@ + + + + + + + + + + +
    + + + + + diff --git a/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer.js b/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer.js index 8234b734..98e67e7c 100644 --- a/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer.js +++ b/patched-vscode/src/vs/code/electron-sandbox/processExplorer/processExplorer.js @@ -4,8 +4,13 @@ *--------------------------------------------------------------------------------------------*/ //@ts-check +'use strict'; + (function () { - 'use strict'; + + /** + * @import { ISandboxConfiguration } from '../../../base/parts/sandbox/common/sandboxTypes' + */ const bootstrapWindow = bootstrapWindowLib(); @@ -21,12 +26,10 @@ }); /** - * @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration - * * @returns {{ * load: ( * modules: string[], - * resultCallback: (result, configuration: ISandboxConfiguration) => unknown, + * resultCallback: (result: any, configuration: ISandboxConfiguration) => unknown, * options?: { * configureDeveloperSettings?: (config: ISandboxConfiguration) => { * forceEnableDeveloperKeybindings?: boolean, diff --git a/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench-dev.esm.html b/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench-dev.esm.html new file mode 100644 index 00000000..ea5cbee8 --- /dev/null +++ b/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench-dev.esm.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench-dev.html b/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench-dev.html index a92bea24..b1be2b75 100644 --- a/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench-dev.html +++ b/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench-dev.html @@ -68,8 +68,7 @@ - - + diff --git a/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench.esm.html b/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench.esm.html new file mode 100644 index 00000000..1e89448e --- /dev/null +++ b/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench.esm.html @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench.js b/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench.js index 35e8368d..bed16c9f 100644 --- a/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/patched-vscode/src/vs/code/electron-sandbox/workbench/workbench.js @@ -3,24 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/// - //@ts-check +'use strict'; + (function () { - 'use strict'; + + /** + * @import {INativeWindowConfiguration} from '../../../platform/window/common/window' + * @import {NativeParsedArgs} from '../../../platform/environment/common/argv' + * @import {ISandboxConfiguration} from '../../../base/parts/sandbox/common/sandboxTypes' + */ const bootstrapWindow = bootstrapWindowLib(); // Add a perf entry right from the top performance.mark('code/didStartRenderer'); - // Load workbench main JS, CSS and NLS all in parallel. This is an + // Load workbench main JS and CSS all in parallel. This is an // optimization to prevent a waterfall of loading to happen, because // we know for a fact that workbench.desktop.main will depend on - // the related CSS and NLS counterparts. + // the related CSS counterpart. bootstrapWindow.load([ 'vs/workbench/workbench.desktop.main', - 'vs/nls!vs/workbench/workbench.desktop.main', 'vs/css!vs/workbench/workbench.desktop.main' ], function (desktopMain, configuration) { @@ -45,6 +49,7 @@ showSplash(windowConfig); }, beforeLoaderConfig: function (loaderConfig) { + // @ts-ignore loaderConfig.recordStats = true; }, beforeRequire: function (windowConfig) { @@ -74,14 +79,10 @@ //#region Helpers /** - * @typedef {import('../../../platform/window/common/window').INativeWindowConfiguration} INativeWindowConfiguration - * @typedef {import('../../../platform/environment/common/argv').NativeParsedArgs} NativeParsedArgs - * @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration - * * @returns {{ * load: ( * modules: string[], - * resultCallback: (result, configuration: INativeWindowConfiguration & NativeParsedArgs) => unknown, + * resultCallback: (result: any, configuration: INativeWindowConfiguration & NativeParsedArgs) => unknown, * options?: { * configureDeveloperSettings?: (config: INativeWindowConfiguration & NativeParsedArgs) => { * forceDisableShowDevtoolsOnError?: boolean, @@ -129,7 +130,9 @@ } // minimal color configuration (works with or without persisted data) - let baseTheme, shellBackground, shellForeground; + let baseTheme; + let shellBackground; + let shellForeground; if (data) { baseTheme = data.baseTheme; shellBackground = data.colorInfo.editorBackground; @@ -162,7 +165,9 @@ style.textContent = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`; // set zoom level as soon as possible + // @ts-ignore if (typeof data?.zoomLevel === 'number' && typeof globalThis.vscode?.webFrame?.setZoomLevel === 'function') { + // @ts-ignore globalThis.vscode.webFrame.setZoomLevel(data.zoomLevel); } @@ -172,9 +177,9 @@ const splash = document.createElement('div'); splash.id = 'monaco-parts-splash'; - splash.className = baseTheme; + splash.className = baseTheme ?? 'vs-dark'; - if (layoutInfo.windowBorder) { + if (layoutInfo.windowBorder && colorInfo.windowBorder) { splash.style.position = 'relative'; splash.style.height = 'calc(100vh - 2px)'; splash.style.width = 'calc(100vw - 2px)'; diff --git a/patched-vscode/src/vs/code/node/cli.ts b/patched-vscode/src/vs/code/node/cli.ts index 211d1dbf..58e39456 100644 --- a/patched-vscode/src/vs/code/node/cli.ts +++ b/patched-vscode/src/vs/code/node/cli.ts @@ -39,10 +39,6 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { || !!argv['telemetry']; } -interface IMainCli { - main: (argv: NativeParsedArgs) => Promise; -} - export async function main(argv: string[]): Promise { let args: NativeParsedArgs; @@ -59,19 +55,28 @@ export async function main(argv: string[]): Promise { console.error(`'${subcommand}' command not supported in ${product.applicationName}`); return; } + const env: IProcessEnvironment = { + ...process.env + }; + // bootstrap-amd.js determines the electron environment based + // on the following variable. For the server we need to unset + // it to prevent importing any electron specific modules. + // Refs https://github.com/microsoft/vscode/issues/221883 + delete env['ELECTRON_RUN_AS_NODE']; + const tunnelArgs = argv.slice(argv.indexOf(subcommand) + 1); // all arguments behind `tunnel` return new Promise((resolve, reject) => { let tunnelProcess: ChildProcess; const stdio: StdioOptions = ['ignore', 'pipe', 'pipe']; if (process.env['VSCODE_DEV']) { - tunnelProcess = spawn('cargo', ['run', '--', subcommand, ...tunnelArgs], { cwd: join(getAppRoot(), 'cli'), stdio }); + tunnelProcess = spawn('cargo', ['run', '--', subcommand, ...tunnelArgs], { cwd: join(getAppRoot(), 'cli'), stdio, env }); } else { const appPath = process.platform === 'darwin' // ./Contents/MacOS/Electron => ./Contents/Resources/app/bin/code-tunnel-insiders ? join(dirname(dirname(process.execPath)), 'Resources', 'app') : dirname(process.execPath); const tunnelCommand = join(appPath, 'bin', `${product.tunnelApplicationName}${isWindows ? '.exe' : ''}`); - tunnelProcess = spawn(tunnelCommand, [subcommand, ...tunnelArgs], { cwd: cwd(), stdio }); + tunnelProcess = spawn(tunnelCommand, [subcommand, ...tunnelArgs], { cwd: cwd(), stdio, env }); } tunnelProcess.stdout!.pipe(process.stdout); @@ -112,7 +117,7 @@ export async function main(argv: string[]): Promise { // Extensions Management else if (shouldSpawnCliProcess(args)) { - const cli = await new Promise((resolve, reject) => require(['vs/code/node/cliProcessMain'], resolve, reject)); + const cli = await import(['vs', 'code', 'node', 'cliProcessMain'].join('/') /* TODO@esm workaround to prevent esbuild from inlining this */); await cli.main(args); return; @@ -392,7 +397,10 @@ export async function main(argv: string[]): Promise { return false; } if (target.type === 'page') { - return target.url.indexOf('workbench/workbench.html') > 0 || target.url.indexOf('workbench/workbench-dev.html') > 0; + return target.url.indexOf('workbench/workbench.html') > 0 || + target.url.indexOf('workbench/workbench-dev.html') > 0 || + target.url.indexOf('workbench/workbench.esm.html') > 0 || + target.url.indexOf('workbench/workbench-dev.esm.html') > 0; } else { return true; } diff --git a/patched-vscode/src/vs/code/node/cliProcessMain.ts b/patched-vscode/src/vs/code/node/cliProcessMain.ts index 2c1d7afc..f940e372 100644 --- a/patched-vscode/src/vs/code/node/cliProcessMain.ts +++ b/patched-vscode/src/vs/code/node/cliProcessMain.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { hostname, release } from 'os'; import { raceTimeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -13,7 +14,6 @@ import { isAbsolute, join } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; -import { Promises } from 'vs/base/node/pfs'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { IDownloadService } from 'vs/platform/download/common/download'; @@ -124,7 +124,7 @@ class CliMain extends Disposable { await Promise.all([ this.allowWindowsUNCPath(environmentService.appSettingsHome.with({ scheme: Schemas.file }).fsPath), this.allowWindowsUNCPath(environmentService.extensionsPath) - ].map(path => path ? Promises.mkdir(path, { recursive: true }) : undefined)); + ].map(path => path ? fs.promises.mkdir(path, { recursive: true }) : undefined)); // Logger const loggerService = new LoggerService(getLogLevel(environmentService), environmentService.logsHome); diff --git a/patched-vscode/src/vs/code/node/sharedProcess/contrib/codeCacheCleaner.ts b/patched-vscode/src/vs/code/node/sharedProcess/contrib/codeCacheCleaner.ts index 77ae5d97..7ca147e3 100644 --- a/patched-vscode/src/vs/code/node/sharedProcess/contrib/codeCacheCleaner.ts +++ b/patched-vscode/src/vs/code/node/sharedProcess/contrib/codeCacheCleaner.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { RunOnceScheduler } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -54,7 +55,7 @@ export class CodeCacheCleaner extends Disposable { // Delete cache folder if old enough const codeCacheEntryPath = join(codeCacheRootPath, codeCache); - const codeCacheEntryStat = await Promises.stat(codeCacheEntryPath); + const codeCacheEntryStat = await fs.promises.stat(codeCacheEntryPath); if (codeCacheEntryStat.isDirectory() && (now - codeCacheEntryStat.mtime.getTime()) > this._DataMaxAge) { this.logService.trace(`[code cache cleanup]: Removing code cache folder ${codeCache}.`); diff --git a/patched-vscode/src/vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/patched-vscode/src/vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner.ts index 68d2cc16..0c7e517d 100644 --- a/patched-vscode/src/vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/patched-vscode/src/vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IStringDictionary } from 'vs/base/common/collections'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -58,7 +59,7 @@ export class LanguagePackCachedDataCleaner extends Disposable { try { const installed: IStringDictionary = Object.create(null); - const metaData: ILanguagePackFile = JSON.parse(await Promises.readFile(join(this.environmentService.userDataPath, 'languagepacks.json'), 'utf8')); + const metaData: ILanguagePackFile = JSON.parse(await fs.promises.readFile(join(this.environmentService.userDataPath, 'languagepacks.json'), 'utf8')); for (const locale of Object.keys(metaData)) { const entry = metaData[locale]; installed[`${entry.hash}.${locale}`] = true; @@ -93,7 +94,7 @@ export class LanguagePackCachedDataCleaner extends Disposable { } const candidate = join(folder, entry); - const stat = await Promises.stat(candidate); + const stat = await fs.promises.stat(candidate); if (stat.isDirectory() && (now - stat.mtime.getTime()) > this._DataMaxAge) { this.logService.trace(`[language pack cache cleanup]: Removing language pack cache folder: ${join(packEntry, entry)}`); diff --git a/patched-vscode/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/patched-vscode/src/vs/code/node/sharedProcess/sharedProcessMain.ts index f8e91549..69c81ac2 100644 --- a/patched-vscode/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/patched-vscode/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -71,7 +71,7 @@ import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyn import { UserDataSyncServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; -import { NativeUserDataProfileStorageService } from 'vs/platform/userDataProfile/node/userDataProfileStorageService'; +import { SharedProcessUserDataProfileStorageService } from 'vs/platform/userDataProfile/node/userDataProfileStorageService'; import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; @@ -355,7 +355,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { services.set(IUserDataSyncLocalStoreService, new SyncDescriptor(UserDataSyncLocalStoreService, undefined, false /* Eagerly cleans up old backups */)); services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService, undefined, true)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService, undefined, false /* Initializes the Sync State */)); - services.set(IUserDataProfileStorageService, new SyncDescriptor(NativeUserDataProfileStorageService, undefined, true)); + services.set(IUserDataProfileStorageService, new SyncDescriptor(SharedProcessUserDataProfileStorageService, undefined, true)); services.set(IUserDataSyncResourceProviderService, new SyncDescriptor(UserDataSyncResourceProviderService, undefined, true)); // Signing diff --git a/patched-vscode/src/vs/editor/browser/config/charWidthReader.ts b/patched-vscode/src/vs/editor/browser/config/charWidthReader.ts index 90bafb66..e1d36f14 100644 --- a/patched-vscode/src/vs/editor/browser/config/charWidthReader.ts +++ b/patched-vscode/src/vs/editor/browser/config/charWidthReader.ts @@ -56,7 +56,7 @@ class DomCharWidthReader { this._readFromDomElements(); // Remove the container from the DOM - targetWindow.document.body.removeChild(this._container!); + this._container?.remove(); this._container = null; this._testElements = null; diff --git a/patched-vscode/src/vs/editor/browser/controller/textAreaHandler.ts b/patched-vscode/src/vs/editor/browser/controller/textAreaHandler.ts index c8e5b7e5..a5f824ca 100644 --- a/patched-vscode/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/patched-vscode/src/vs/editor/browser/controller/textAreaHandler.ts @@ -950,7 +950,7 @@ function measureText(targetDocument: Document, text: string, fontInfo: FontInfo, const res = regularDomNode.offsetWidth; - targetDocument.body.removeChild(container); + container.remove(); return res; } diff --git a/patched-vscode/src/vs/editor/browser/coreCommands.ts b/patched-vscode/src/vs/editor/browser/coreCommands.ts index ca0a4bb8..e7f0743a 100644 --- a/patched-vscode/src/vs/editor/browser/coreCommands.ts +++ b/patched-vscode/src/vs/editor/browser/coreCommands.ts @@ -30,6 +30,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IViewModel } from 'vs/editor/common/viewModel'; import { ISelection } from 'vs/editor/common/core/selection'; import { getActiveElement } from 'vs/base/browser/dom'; +import { EnterOperation } from 'vs/editor/common/cursor/cursorTypeEditOperations'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -74,7 +75,7 @@ export namespace EditorScroll_ { return true; }; - export const metadata = { + export const metadata: ICommandMetadata = { description: 'Scroll editor in the given direction', args: [ { @@ -252,7 +253,7 @@ export namespace RevealLine_ { return true; }; - export const metadata = { + export const metadata: ICommandMetadata = { description: 'Reveal the given line at the given logical position', args: [ { @@ -1988,7 +1989,7 @@ export namespace CoreEditingCommands { public runCoreEditingCommand(editor: ICodeEditor, viewModel: IViewModel, args: unknown): void { editor.pushUndoStop(); - editor.executeCommands(this.id, TypeOperations.lineBreakInsert(viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection))); + editor.executeCommands(this.id, EnterOperation.lineBreakInsert(viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection))); } }); diff --git a/patched-vscode/src/vs/editor/browser/observableCodeEditor.ts b/patched-vscode/src/vs/editor/browser/observableCodeEditor.ts new file mode 100644 index 00000000..4bca7d67 --- /dev/null +++ b/patched-vscode/src/vs/editor/browser/observableCodeEditor.ts @@ -0,0 +1,284 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { equalsIfDefined, itemsEquals } from 'vs/base/common/equals'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, ITransaction, autorun, autorunOpts, autorunWithStoreHandleChanges, derived, derivedOpts, observableFromEvent, observableSignal, observableValue, observableValueOpts } from 'vs/base/common/observable'; +import { TransactionImpl } from 'vs/base/common/observableInternal/base'; +import { derivedWithSetter } from 'vs/base/common/observableInternal/derived'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { EditorOption, FindComputedEditorOptionValueById } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { Selection } from 'vs/editor/common/core/selection'; +import { ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; + +/** + * Returns a facade for the code editor that provides observables for various states/events. +*/ +export function observableCodeEditor(editor: ICodeEditor): ObservableCodeEditor { + return ObservableCodeEditor.get(editor); +} + +export class ObservableCodeEditor extends Disposable { + private static readonly _map = new Map(); + + /** + * Make sure that editor is not disposed yet! + */ + public static get(editor: ICodeEditor): ObservableCodeEditor { + let result = ObservableCodeEditor._map.get(editor); + if (!result) { + result = new ObservableCodeEditor(editor); + ObservableCodeEditor._map.set(editor, result); + const d = editor.onDidDispose(() => { + const item = ObservableCodeEditor._map.get(editor); + if (item) { + ObservableCodeEditor._map.delete(editor); + item.dispose(); + d.dispose(); + } + }); + } + return result; + } + + private _updateCounter = 0; + private _currentTransaction: TransactionImpl | undefined = undefined; + + private _beginUpdate(): void { + this._updateCounter++; + if (this._updateCounter === 1) { + this._currentTransaction = new TransactionImpl(() => { + /** @description Update editor state */ + }); + } + } + + private _endUpdate(): void { + this._updateCounter--; + if (this._updateCounter === 0) { + const t = this._currentTransaction!; + this._currentTransaction = undefined; + t.finish(); + } + } + + private constructor(public readonly editor: ICodeEditor) { + super(); + + this._register(this.editor.onBeginUpdate(() => this._beginUpdate())); + this._register(this.editor.onEndUpdate(() => this._endUpdate())); + + this._register(this.editor.onDidChangeModel(() => { + this._beginUpdate(); + try { + this._model.set(this.editor.getModel(), this._currentTransaction); + this._forceUpdate(); + } finally { + this._endUpdate(); + } + })); + + this._register(this.editor.onDidType((e) => { + this._beginUpdate(); + try { + this._forceUpdate(); + this.onDidType.trigger(this._currentTransaction, e); + } finally { + this._endUpdate(); + } + })); + + this._register(this.editor.onDidChangeModelContent(e => { + this._beginUpdate(); + try { + this._versionId.set(this.editor.getModel()?.getVersionId() ?? null, this._currentTransaction, e); + this._forceUpdate(); + } finally { + this._endUpdate(); + } + })); + + this._register(this.editor.onDidChangeCursorSelection(e => { + this._beginUpdate(); + try { + this._selections.set(this.editor.getSelections(), this._currentTransaction, e); + this._forceUpdate(); + } finally { + this._endUpdate(); + } + })); + } + + public forceUpdate(): void; + public forceUpdate(cb: (tx: ITransaction) => T): T; + public forceUpdate(cb?: (tx: ITransaction) => T): T { + this._beginUpdate(); + try { + this._forceUpdate(); + if (!cb) { return undefined as T; } + return cb(this._currentTransaction!); + } finally { + this._endUpdate(); + } + } + + private _forceUpdate(): void { + this._beginUpdate(); + try { + this._model.set(this.editor.getModel(), this._currentTransaction); + this._versionId.set(this.editor.getModel()?.getVersionId() ?? null, this._currentTransaction, undefined); + this._selections.set(this.editor.getSelections(), this._currentTransaction, undefined); + } finally { + this._endUpdate(); + } + } + + private readonly _model = observableValue(this, this.editor.getModel()); + public readonly model: IObservable = this._model; + + public readonly isReadonly = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.readOnly)); + + private readonly _versionId = observableValueOpts({ owner: this, lazy: true }, this.editor.getModel()?.getVersionId() ?? null); + public readonly versionId: IObservable = this._versionId; + + private readonly _selections = observableValueOpts( + { owner: this, equalsFn: equalsIfDefined(itemsEquals(Selection.selectionsEqual)), lazy: true }, + this.editor.getSelections() ?? null + ); + public readonly selections: IObservable = this._selections; + + + public readonly positions = derivedOpts( + { owner: this, equalsFn: equalsIfDefined(itemsEquals(Position.equals)) }, + reader => this.selections.read(reader)?.map(s => s.getStartPosition()) ?? null + ); + + public readonly isFocused = observableFromEvent(this, e => { + const d1 = this.editor.onDidFocusEditorWidget(e); + const d2 = this.editor.onDidBlurEditorWidget(e); + return { + dispose() { + d1.dispose(); + d2.dispose(); + } + }; + }, () => this.editor.hasWidgetFocus()); + + public readonly value = derivedWithSetter(this, + reader => { this.versionId.read(reader); return this.model.read(reader)?.getValue() ?? ''; }, + (value, tx) => { + const model = this.model.get(); + if (model !== null) { + if (value !== model.getValue()) { + model.setValue(value); + } + } + } + ); + public readonly valueIsEmpty = derived(this, reader => { this.versionId.read(reader); return this.editor.getModel()?.getValueLength() === 0; }); + public readonly cursorSelection = derivedOpts({ owner: this, equalsFn: equalsIfDefined(Selection.selectionsEqual) }, reader => this.selections.read(reader)?.[0] ?? null); + public readonly cursorPosition = derivedOpts({ owner: this, equalsFn: Position.equals }, reader => this.selections.read(reader)?.[0]?.getPosition() ?? null); + + public readonly onDidType = observableSignal(this); + + public readonly scrollTop = observableFromEvent(this.editor.onDidScrollChange, () => this.editor.getScrollTop()); + public readonly scrollLeft = observableFromEvent(this.editor.onDidScrollChange, () => this.editor.getScrollLeft()); + + public readonly layoutInfo = observableFromEvent(this.editor.onDidLayoutChange, () => this.editor.getLayoutInfo()); + public readonly layoutInfoContentLeft = this.layoutInfo.map(l => l.contentLeft); + public readonly layoutInfoDecorationsLeft = this.layoutInfo.map(l => l.decorationsLeft); + + public readonly contentWidth = observableFromEvent(this.editor.onDidContentSizeChange, () => this.editor.getContentWidth()); + + public getOption(id: T): IObservable> { + return observableFromEvent(this, cb => this.editor.onDidChangeConfiguration(e => { + if (e.hasChanged(id)) { cb(undefined); } + }), () => this.editor.getOption(id)); + } + + public setDecorations(decorations: IObservable): IDisposable { + const d = new DisposableStore(); + const decorationsCollection = this.editor.createDecorationsCollection(); + d.add(autorunOpts({ owner: this, debugName: () => `Apply decorations from ${decorations.debugName}` }, reader => { + const d = decorations.read(reader); + decorationsCollection.set(d); + })); + d.add({ + dispose: () => { + decorationsCollection.clear(); + } + }); + return d; + } + + private _overlayWidgetCounter = 0; + + public createOverlayWidget(widget: IObservableOverlayWidget): IDisposable { + const overlayWidgetId = 'observableOverlayWidget' + (this._overlayWidgetCounter++); + const w: IOverlayWidget = { + getDomNode: () => widget.domNode, + getPosition: () => widget.position.get(), + getId: () => overlayWidgetId, + allowEditorOverflow: widget.allowEditorOverflow, + getMinContentWidthInPx: () => widget.minContentWidthInPx.get(), + }; + this.editor.addOverlayWidget(w); + const d = autorun(reader => { + widget.position.read(reader); + widget.minContentWidthInPx.read(reader); + this.editor.layoutOverlayWidget(w); + }); + return toDisposable(() => { + d.dispose(); + this.editor.removeOverlayWidget(w); + }); + } +} + +interface IObservableOverlayWidget { + get domNode(): HTMLElement; + readonly position: IObservable; + readonly minContentWidthInPx: IObservable; + get allowEditorOverflow(): boolean; +} + +type RemoveUndefined = T extends undefined ? never : T; +export function reactToChange(observable: IObservable, cb: (value: T, deltas: RemoveUndefined[]) => void): IDisposable { + return autorunWithStoreHandleChanges({ + createEmptyChangeSummary: () => ({ deltas: [] as RemoveUndefined[], didChange: false }), + handleChange: (context, changeSummary) => { + if (context.didChange(observable)) { + const e = context.change; + if (e !== undefined) { + changeSummary.deltas.push(e as RemoveUndefined); + } + changeSummary.didChange = true; + } + return true; + }, + }, (reader, changeSummary) => { + const value = observable.read(reader); + if (changeSummary.didChange) { + cb(value, changeSummary.deltas); + } + }); +} + +export function reactToChangeWithStore(observable: IObservable, cb: (value: T, deltas: RemoveUndefined[], store: DisposableStore) => void): IDisposable { + const store = new DisposableStore(); + const disposable = reactToChange(observable, (value, deltas) => { + store.clear(); + cb(value, deltas, store); + }); + return { + dispose() { + disposable.dispose(); + store.dispose(); + } + }; +} diff --git a/patched-vscode/src/vs/editor/browser/observableUtilities.ts b/patched-vscode/src/vs/editor/browser/observableUtilities.ts deleted file mode 100644 index 7bcfe7ec..00000000 --- a/patched-vscode/src/vs/editor/browser/observableUtilities.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { autorunOpts, derivedOpts, IObservable, observableFromEvent } from 'vs/base/common/observable'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Position } from 'vs/editor/common/core/position'; -import { IModelDeltaDecoration } from 'vs/editor/common/model'; - -/** - * Returns a facade for the code editor that provides observables for various states/events. -*/ -export function obsCodeEditor(editor: ICodeEditor): ObservableCodeEditor { - return ObservableCodeEditor.get(editor); -} - -class ObservableCodeEditor { - private static _map = new Map(); - - /** - * Make sure that editor is not disposed yet! - */ - public static get(editor: ICodeEditor): ObservableCodeEditor { - let result = ObservableCodeEditor._map.get(editor); - if (!result) { - result = new ObservableCodeEditor(editor); - ObservableCodeEditor._map.set(editor, result); - const d = editor.onDidDispose(() => { - ObservableCodeEditor._map.delete(editor); - d.dispose(); - }); - } - return result; - } - - private constructor(public readonly editor: ICodeEditor) { - } - - public readonly model = observableFromEvent(this.editor.onDidChangeModel, () => this.editor.getModel()); - public readonly value = observableFromEvent(this.editor.onDidChangeModelContent, () => this.editor.getValue()); - public readonly valueIsEmpty = observableFromEvent(this.editor.onDidChangeModelContent, () => this.editor.getModel()?.getValueLength() === 0); - public readonly selections = observableFromEvent(this.editor.onDidChangeCursorSelection, () => this.editor.getSelections()); - public readonly cursorPosition = derivedOpts({ owner: this, equalsFn: Position.equals }, reader => this.selections.read(reader)?.[0]?.getPosition() ?? null); - public readonly isFocused = observableFromEvent(e => { - const d1 = this.editor.onDidFocusEditorWidget(e); - const d2 = this.editor.onDidBlurEditorWidget(e); - return { - dispose() { - d1.dispose(); - d2.dispose(); - } - }; - }, () => this.editor.hasWidgetFocus()); - - public setDecorations(decorations: IObservable): IDisposable { - const d = new DisposableStore(); - const decorationsCollection = this.editor.createDecorationsCollection(); - d.add(autorunOpts({ owner: this, debugName: () => `Apply decorations from ${decorations.debugName}` }, reader => { - const d = decorations.read(reader); - decorationsCollection.set(d); - })); - d.add({ - dispose: () => { - decorationsCollection.clear(); - } - }); - return d; - } -} diff --git a/patched-vscode/src/vs/editor/browser/services/abstractCodeEditorService.ts b/patched-vscode/src/vs/editor/browser/services/abstractCodeEditorService.ts index b960f481..1fedb4d1 100644 --- a/patched-vscode/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/patched-vscode/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -341,7 +341,7 @@ class RefCountedStyleSheet { public unref(): void { this._refCount--; if (this._refCount === 0) { - this._styleSheet.parentNode?.removeChild(this._styleSheet); + this._styleSheet.remove(); this._parent._removeEditorStyleSheets(this._editorId); } } diff --git a/patched-vscode/src/vs/editor/browser/services/editorWorkerService.ts b/patched-vscode/src/vs/editor/browser/services/editorWorkerService.ts index aa1e41c8..56204107 100644 --- a/patched-vscode/src/vs/editor/browser/services/editorWorkerService.ts +++ b/patched-vscode/src/vs/editor/browser/services/editorWorkerService.ts @@ -3,18 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IntervalTimer, timeout } from 'vs/base/common/async'; -import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/base/common/worker/simpleWorker'; -import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; +import { logOnceWebWorkerWarning, IWorkerClient, Proxied, IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import * as languages from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { DiffAlgorithmName, IDiffComputationResult, IEditorWorkerService, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { DiffAlgorithmName, IEditorWorkerService, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { isNonEmptyArray } from 'vs/base/common/arrays'; @@ -22,7 +22,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { StopWatch } from 'vs/base/common/stopwatch'; import { canceled, onUnexpectedError } from 'vs/base/common/errors'; import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; @@ -32,11 +31,8 @@ import { LineRange } from 'vs/editor/common/core/lineRange'; import { SectionHeader, FindSectionHeaderOptions } from 'vs/editor/common/services/findSectionHeaders'; import { mainWindow } from 'vs/base/browser/window'; import { WindowIntervalTimer } from 'vs/base/browser/dom'; - -/** - * Stop syncing a model to the worker if it was not needed for 1 min. - */ -const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000; +import { WorkerTextModelSyncClient } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; +import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; /** * Stop the worker if it was not needed for 5 min. @@ -54,7 +50,7 @@ function canSyncModel(modelService: IModelService, resource: URI): boolean { return true; } -export class EditorWorkerService extends Disposable implements IEditorWorkerService { +export abstract class EditorWorkerService extends Disposable implements IEditorWorkerService { declare readonly _serviceBrand: undefined; @@ -63,29 +59,30 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ private readonly _logService: ILogService; constructor( + workerDescriptor: IWorkerDescriptor, @IModelService modelService: IModelService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @ILogService logService: ILogService, - @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, + @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { super(); this._modelService = modelService; - this._workerManager = this._register(new WorkerManager(this._modelService, languageConfigurationService)); + this._workerManager = this._register(new WorkerManager(workerDescriptor, this._modelService)); this._logService = logService; // register default link-provider and default completions-provider this._register(languageFeaturesService.linkProvider.register({ language: '*', hasAccessToAllModels: true }, { - provideLinks: (model, token) => { + provideLinks: async (model, token) => { if (!canSyncModel(this._modelService, model.uri)) { return Promise.resolve({ links: [] }); // File too large } - return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)).then(links => { - return links && { links }; - }); + const worker = await this._workerWithResources([model.uri]); + const links = await worker.$computeLinks(model.uri.toString()); + return links && { links }; } })); - this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, languageConfigurationService))); + this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, this._languageConfigurationService))); } public override dispose(): void { @@ -96,12 +93,14 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return canSyncModel(this._modelService, uri); } - public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { - return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range)); + public async computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { + const worker = await this._workerWithResources([uri]); + return worker.$computeUnicodeHighlights(uri.toString(), options, range); } public async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { - const result = await this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options, algorithm)); + const worker = await this._workerWithResources([original, modified], /* forceLargeModels */true); + const result = await worker.$computeDiff(original.toString(), modified.toString(), options, algorithm); if (!result) { return null; } @@ -137,17 +136,18 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified)); } - public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { - return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace)); + public async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { + const worker = await this._workerWithResources([original, modified]); + return worker.$computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); } - public computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[] | null | undefined, pretty: boolean = false): Promise { + public async computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[] | null | undefined, pretty: boolean = false): Promise { if (isNonEmptyArray(edits)) { if (!canSyncModel(this._modelService, resource)) { return Promise.resolve(edits); // File too large } const sw = StopWatch.create(); - const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits, pretty)); + const result = this._workerWithResources([resource]).then(worker => worker.$computeMoreMinimalEdits(resource.toString(), edits, pretty)); result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed())); return Promise.race([result, timeout(1000).then(() => edits)]); @@ -162,12 +162,16 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return Promise.resolve(edits); // File too large } const sw = StopWatch.create(); - const result = this._workerManager.withWorker().then(client => client.computeHumanReadableDiff(resource, edits, - { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false, })).catch((err) => { - onUnexpectedError(err); - // In case of an exception, fall back to computeMoreMinimalEdits - return this.computeMoreMinimalEdits(resource, edits, true); - }); + const opts: ILinesDiffComputerOptions = { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false }; + const result = ( + this._workerWithResources([resource]) + .then(worker => worker.$computeHumanReadableDiff(resource.toString(), edits, opts)) + .catch((err) => { + onUnexpectedError(err); + // In case of an exception, fall back to computeMoreMinimalEdits + return this.computeMoreMinimalEdits(resource, edits, true); + }) + ); result.finally(() => this._logService.trace('FORMAT#computeHumanReadableDiff', resource.toString(true), sw.elapsed())); return result; @@ -180,20 +184,47 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return (canSyncModel(this._modelService, resource)); } - public navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { - return this._workerManager.withWorker().then(client => client.navigateValueSet(resource, range, up)); + public async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { + const model = this._modelService.getModel(resource); + if (!model) { + return null; + } + const wordDefRegExp = this._languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); + const wordDef = wordDefRegExp.source; + const wordDefFlags = wordDefRegExp.flags; + const worker = await this._workerWithResources([resource]); + return worker.$navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags); } - canComputeWordRanges(resource: URI): boolean { + public canComputeWordRanges(resource: URI): boolean { return canSyncModel(this._modelService, resource); } - computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { - return this._workerManager.withWorker().then(client => client.computeWordRanges(resource, range)); + public async computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { + const model = this._modelService.getModel(resource); + if (!model) { + return Promise.resolve(null); + } + const wordDefRegExp = this._languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); + const wordDef = wordDefRegExp.source; + const wordDefFlags = wordDefRegExp.flags; + const worker = await this._workerWithResources([resource]); + return worker.$computeWordRanges(resource.toString(), range, wordDef, wordDefFlags); + } + + public async findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise { + const worker = await this._workerWithResources([uri]); + return worker.$findSectionHeaders(uri.toString(), options); } - public findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise { - return this._workerManager.withWorker().then(client => client.findSectionHeaders(uri, options)); + public async computeDefaultDocumentColors(uri: URI): Promise { + const worker = await this._workerWithResources([uri]); + return worker.$computeDefaultDocumentColors(uri.toString()); + } + + private async _workerWithResources(resources: URI[], forceLargeModels: boolean = false): Promise> { + const worker = await this._workerManager.withWorker(); + return await worker.workerWithSyncedResources(resources, forceLargeModels); } } @@ -281,7 +312,10 @@ class WorkerManager extends Disposable { private _editorWorkerClient: EditorWorkerClient | null; private _lastWorkerUsedTime: number; - constructor(modelService: IModelService, private readonly languageConfigurationService: ILanguageConfigurationService) { + constructor( + private readonly _workerDescriptor: IWorkerDescriptor, + @IModelService modelService: IModelService + ) { super(); this._modelService = modelService; this._editorWorkerClient = null; @@ -335,124 +369,31 @@ class WorkerManager extends Disposable { public withWorker(): Promise { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this._modelService, false, 'editorWorkerService', this.languageConfigurationService); + this._editorWorkerClient = new EditorWorkerClient(this._workerDescriptor, false, this._modelService); } return Promise.resolve(this._editorWorkerClient); } } -class EditorModelManager extends Disposable { - - private readonly _proxy: EditorSimpleWorker; - private readonly _modelService: IModelService; - private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null); - private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null); - - constructor(proxy: EditorSimpleWorker, modelService: IModelService, keepIdleModels: boolean) { - super(); - this._proxy = proxy; - this._modelService = modelService; - - if (!keepIdleModels) { - const timer = new IntervalTimer(); - timer.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2)); - this._register(timer); - } - } - - public override dispose(): void { - for (const modelUrl in this._syncedModels) { - dispose(this._syncedModels[modelUrl]); - } - this._syncedModels = Object.create(null); - this._syncedModelsLastUsedTime = Object.create(null); - super.dispose(); - } - - public ensureSyncedResources(resources: URI[], forceLargeModels: boolean): void { - for (const resource of resources) { - const resourceStr = resource.toString(); - - if (!this._syncedModels[resourceStr]) { - this._beginModelSync(resource, forceLargeModels); - } - if (this._syncedModels[resourceStr]) { - this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime(); - } - } - } - - private _checkStopModelSync(): void { - const currentTime = (new Date()).getTime(); - - const toRemove: string[] = []; - for (const modelUrl in this._syncedModelsLastUsedTime) { - const elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl]; - if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) { - toRemove.push(modelUrl); - } - } - - for (const e of toRemove) { - this._stopModelSync(e); - } - } - - private _beginModelSync(resource: URI, forceLargeModels: boolean): void { - const model = this._modelService.getModel(resource); - if (!model) { - return; - } - if (!forceLargeModels && model.isTooLargeForSyncing()) { - return; - } - - const modelUrl = resource.toString(); - - this._proxy.acceptNewModel({ - url: model.uri.toString(), - lines: model.getLinesContent(), - EOL: model.getEOL(), - versionId: model.getVersionId() - }); - - const toDispose = new DisposableStore(); - toDispose.add(model.onDidChangeContent((e) => { - this._proxy.acceptModelChanged(modelUrl.toString(), e); - })); - toDispose.add(model.onWillDispose(() => { - this._stopModelSync(modelUrl); - })); - toDispose.add(toDisposable(() => { - this._proxy.acceptRemovedModel(modelUrl); - })); - - this._syncedModels[modelUrl] = toDispose; - } - - private _stopModelSync(modelUrl: string): void { - const toDispose = this._syncedModels[modelUrl]; - delete this._syncedModels[modelUrl]; - delete this._syncedModelsLastUsedTime[modelUrl]; - dispose(toDispose); - } -} - class SynchronousWorkerClient implements IWorkerClient { private readonly _instance: T; - private readonly _proxyObj: Promise; + public readonly proxy: Proxied; constructor(instance: T) { this._instance = instance; - this._proxyObj = Promise.resolve(this._instance); + this.proxy = this._instance as Proxied; } public dispose(): void { this._instance.dispose(); } - public getProxyObject(): Promise { - return this._proxyObj; + public setChannel(channel: string, handler: T): void { + throw new Error(`Not supported`); + } + + public getChannel(channel: string): Proxied { + throw new Error(`Not supported`); } } @@ -460,39 +401,22 @@ export interface IEditorWorkerClient { fhr(method: string, args: any[]): Promise; } -export class EditorWorkerHost implements IEditorWorkerHost { - - private readonly _workerClient: IEditorWorkerClient; - - constructor(workerClient: IEditorWorkerClient) { - this._workerClient = workerClient; - } - - // foreign host request - public fhr(method: string, args: any[]): Promise { - return this._workerClient.fhr(method, args); - } -} - export class EditorWorkerClient extends Disposable implements IEditorWorkerClient { private readonly _modelService: IModelService; private readonly _keepIdleModels: boolean; - protected _worker: IWorkerClient | null; - protected readonly _workerFactory: DefaultWorkerFactory; - private _modelManager: EditorModelManager | null; + private _worker: IWorkerClient | null; + private _modelManager: WorkerTextModelSyncClient | null; private _disposed = false; constructor( - modelService: IModelService, + private readonly _workerDescriptor: IWorkerDescriptor, keepIdleModels: boolean, - label: string | undefined, - private readonly languageConfigurationService: ILanguageConfigurationService + @IModelService modelService: IModelService, ) { super(); this._modelService = modelService; this._keepIdleModels = keepIdleModels; - this._workerFactory = new DefaultWorkerFactory(label); this._worker = null; this._modelManager = null; } @@ -505,123 +429,59 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien private _getOrCreateWorker(): IWorkerClient { if (!this._worker) { try { - this._worker = this._register(new SimpleWorkerClient( - this._workerFactory, - 'vs/editor/common/services/editorSimpleWorker', - new EditorWorkerHost(this) - )); + this._worker = this._register(createWebWorker(this._workerDescriptor)); + EditorWorkerHost.setChannel(this._worker, this._createEditorWorkerHost()); } catch (err) { logOnceWebWorkerWarning(err); - this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); + this._worker = this._createFallbackLocalWorker(); } } return this._worker; } - protected _getProxy(): Promise { - return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => { + protected async _getProxy(): Promise> { + try { + const proxy = this._getOrCreateWorker().proxy; + await proxy.$ping(); + return proxy; + } catch (err) { logOnceWebWorkerWarning(err); - this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); - return this._getOrCreateWorker().getProxyObject(); - }); + this._worker = this._createFallbackLocalWorker(); + return this._worker.proxy; + } } - private _getOrCreateModelManager(proxy: EditorSimpleWorker): EditorModelManager { + private _createFallbackLocalWorker(): SynchronousWorkerClient { + return new SynchronousWorkerClient(new EditorSimpleWorker(this._createEditorWorkerHost(), null)); + } + + private _createEditorWorkerHost(): EditorWorkerHost { + return { + $fhr: (method, args) => this.fhr(method, args) + }; + } + + private _getOrCreateModelManager(proxy: Proxied): WorkerTextModelSyncClient { if (!this._modelManager) { - this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, this._keepIdleModels)); + this._modelManager = this._register(new WorkerTextModelSyncClient(proxy, this._modelService, this._keepIdleModels)); } return this._modelManager; } - protected async _withSyncedResources(resources: URI[], forceLargeModels: boolean = false): Promise { + public async workerWithSyncedResources(resources: URI[], forceLargeModels: boolean = false): Promise> { if (this._disposed) { return Promise.reject(canceled()); } - return this._getProxy().then((proxy) => { - this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels); - return proxy; - }); - } - - public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { - return this._withSyncedResources([uri]).then(proxy => { - return proxy.computeUnicodeHighlights(uri.toString(), options, range); - }); - } - - public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { - return this._withSyncedResources([original, modified], /* forceLargeModels */true).then(proxy => { - return proxy.computeDiff(original.toString(), modified.toString(), options, algorithm); - }); - } - - public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { - return this._withSyncedResources([original, modified]).then(proxy => { - return proxy.computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); - }); - } - - public computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[], pretty: boolean): Promise { - return this._withSyncedResources([resource]).then(proxy => { - return proxy.computeMoreMinimalEdits(resource.toString(), edits, pretty); - }); - } - - public computeHumanReadableDiff(resource: URI, edits: languages.TextEdit[], options: ILinesDiffComputerOptions): Promise { - return this._withSyncedResources([resource]).then(proxy => { - return proxy.computeHumanReadableDiff(resource.toString(), edits, options); - }); - } - - public computeLinks(resource: URI): Promise { - return this._withSyncedResources([resource]).then(proxy => { - return proxy.computeLinks(resource.toString()); - }); - } - - public computeDefaultDocumentColors(resource: URI): Promise { - return this._withSyncedResources([resource]).then(proxy => { - return proxy.computeDefaultDocumentColors(resource.toString()); - }); + const proxy = await this._getProxy(); + this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels); + return proxy; } public async textualSuggest(resources: URI[], leadingWord: string | undefined, wordDefRegExp: RegExp): Promise<{ words: string[]; duration: number } | null> { - const proxy = await this._withSyncedResources(resources); + const proxy = await this.workerWithSyncedResources(resources); const wordDef = wordDefRegExp.source; const wordDefFlags = wordDefRegExp.flags; - return proxy.textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags); - } - - computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { - return this._withSyncedResources([resource]).then(proxy => { - const model = this._modelService.getModel(resource); - if (!model) { - return Promise.resolve(null); - } - const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); - const wordDef = wordDefRegExp.source; - const wordDefFlags = wordDefRegExp.flags; - return proxy.computeWordRanges(resource.toString(), range, wordDef, wordDefFlags); - }); - } - - public navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { - return this._withSyncedResources([resource]).then(proxy => { - const model = this._modelService.getModel(resource); - if (!model) { - return null; - } - const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); - const wordDef = wordDefRegExp.source; - const wordDefFlags = wordDefRegExp.flags; - return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags); - }); - } - - public findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise { - return this._withSyncedResources([uri]).then(proxy => { - return proxy.findSectionHeaders(uri.toString(), options); - }); + return proxy.$textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags); } override dispose(): void { diff --git a/patched-vscode/src/vs/editor/browser/services/hoverService/hoverService.ts b/patched-vscode/src/vs/editor/browser/services/hoverService/hoverService.ts index 0c554ca0..20faf6cf 100644 --- a/patched-vscode/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/patched-vscode/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -20,9 +20,9 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { mainWindow } from 'vs/base/browser/window'; import { ContextViewHandler } from 'vs/platform/contextview/browser/contextViewService'; -import type { IHoverOptions, IHoverWidget, IUpdatableHover, IUpdatableHoverContentOrFactory, IUpdatableHoverOptions } from 'vs/base/browser/ui/hover/hover'; +import type { IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from 'vs/base/browser/ui/hover/hover'; import type { IHoverDelegate, IHoverDelegateTarget } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { UpdatableHoverWidget } from 'vs/editor/browser/services/hoverService/updatableHoverWidget'; +import { ManagedHoverWidget } from 'vs/editor/browser/services/hoverService/updatableHoverWidget'; import { TimeoutTimer } from 'vs/base/common/async'; export class HoverService extends Disposable implements IHoverService { @@ -189,22 +189,22 @@ export class HoverService extends Disposable implements IHoverService { } } - private readonly _existingHovers = new Map(); + private readonly _managedHovers = new Map(); // TODO: Investigate performance of this function. There seems to be a lot of content created // and thrown away on start up - setupUpdatableHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IUpdatableHoverContentOrFactory, options?: IUpdatableHoverOptions | undefined): IUpdatableHover { + setupManagedHover(hoverDelegate: IHoverDelegate, targetElement: HTMLElement, content: IManagedHoverContentOrFactory, options?: IManagedHoverOptions | undefined): IManagedHover { - htmlElement.setAttribute('custom-hover', 'true'); + targetElement.setAttribute('custom-hover', 'true'); - if (htmlElement.title !== '') { + if (targetElement.title !== '') { console.warn('HTML element already has a title attribute, which will conflict with the custom hover. Please remove the title attribute.'); - console.trace('Stack trace:', htmlElement.title); - htmlElement.title = ''; + console.trace('Stack trace:', targetElement.title); + targetElement.title = ''; } let hoverPreparation: IDisposable | undefined; - let hoverWidget: UpdatableHoverWidget | undefined; + let hoverWidget: ManagedHoverWidget | undefined; const hideHover = (disposeWidget: boolean, disposePreparation: boolean) => { const hadHover = hoverWidget !== undefined; @@ -225,23 +225,23 @@ export class HoverService extends Disposable implements IHoverService { const triggerShowHover = (delay: number, focus?: boolean, target?: IHoverDelegateTarget, trapFocus?: boolean) => { return new TimeoutTimer(async () => { if (!hoverWidget || hoverWidget.isDisposed) { - hoverWidget = new UpdatableHoverWidget(hoverDelegate, target || htmlElement, delay > 0); + hoverWidget = new ManagedHoverWidget(hoverDelegate, target || targetElement, delay > 0); await hoverWidget.update(typeof content === 'function' ? content() : content, focus, { ...options, trapFocus }); } }, delay); }; let isMouseDown = false; - const mouseDownEmitter = addDisposableListener(htmlElement, EventType.MOUSE_DOWN, () => { + const mouseDownEmitter = addDisposableListener(targetElement, EventType.MOUSE_DOWN, () => { isMouseDown = true; hideHover(true, true); }, true); - const mouseUpEmitter = addDisposableListener(htmlElement, EventType.MOUSE_UP, () => { + const mouseUpEmitter = addDisposableListener(targetElement, EventType.MOUSE_UP, () => { isMouseDown = false; }, true); - const mouseLeaveEmitter = addDisposableListener(htmlElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { + const mouseLeaveEmitter = addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { isMouseDown = false; - hideHover(false, (e).fromElement === htmlElement); + hideHover(false, (e).fromElement === targetElement); }, true); const onMouseOver = (e: MouseEvent) => { @@ -252,53 +252,53 @@ export class HoverService extends Disposable implements IHoverService { const toDispose: DisposableStore = new DisposableStore(); const target: IHoverDelegateTarget = { - targetElements: [htmlElement], + targetElements: [targetElement], dispose: () => { } }; if (hoverDelegate.placement === undefined || hoverDelegate.placement === 'mouse') { // track the mouse position const onMouseMove = (e: MouseEvent) => { target.x = e.x + 10; - if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target, htmlElement) !== htmlElement) { + if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target, targetElement) !== targetElement) { hideHover(true, true); } }; - toDispose.add(addDisposableListener(htmlElement, EventType.MOUSE_MOVE, onMouseMove, true)); + toDispose.add(addDisposableListener(targetElement, EventType.MOUSE_MOVE, onMouseMove, true)); } hoverPreparation = toDispose; - if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target as HTMLElement, htmlElement) !== htmlElement) { + if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target as HTMLElement, targetElement) !== targetElement) { return; // Do not show hover when the mouse is over another hover target } toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); }; - const mouseOverDomEmitter = addDisposableListener(htmlElement, EventType.MOUSE_OVER, onMouseOver, true); + const mouseOverDomEmitter = addDisposableListener(targetElement, EventType.MOUSE_OVER, onMouseOver, true); const onFocus = () => { if (isMouseDown || hoverPreparation) { return; } const target: IHoverDelegateTarget = { - targetElements: [htmlElement], + targetElements: [targetElement], dispose: () => { } }; const toDispose: DisposableStore = new DisposableStore(); const onBlur = () => hideHover(true, true); - toDispose.add(addDisposableListener(htmlElement, EventType.BLUR, onBlur, true)); + toDispose.add(addDisposableListener(targetElement, EventType.BLUR, onBlur, true)); toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); hoverPreparation = toDispose; }; // Do not show hover when focusing an input or textarea let focusDomEmitter: undefined | IDisposable; - const tagName = htmlElement.tagName.toLowerCase(); + const tagName = targetElement.tagName.toLowerCase(); if (tagName !== 'input' && tagName !== 'textarea') { - focusDomEmitter = addDisposableListener(htmlElement, EventType.FOCUS, onFocus, true); + focusDomEmitter = addDisposableListener(targetElement, EventType.FOCUS, onFocus, true); } - const hover: IUpdatableHover = { + const hover: IManagedHover = { show: focus => { hideHover(false, true); // terminate a ongoing mouse over preparation triggerShowHover(0, focus, undefined, focus); // show hover immediately @@ -311,7 +311,7 @@ export class HoverService extends Disposable implements IHoverService { await hoverWidget?.update(content, undefined, hoverOptions); }, dispose: () => { - this._existingHovers.delete(htmlElement); + this._managedHovers.delete(targetElement); mouseOverDomEmitter.dispose(); mouseLeaveEmitter.dispose(); mouseDownEmitter.dispose(); @@ -320,19 +320,19 @@ export class HoverService extends Disposable implements IHoverService { hideHover(true, true); } }; - this._existingHovers.set(htmlElement, hover); + this._managedHovers.set(targetElement, hover); return hover; } - triggerUpdatableHover(target: HTMLElement): void { - const hover = this._existingHovers.get(target); + showManagedHover(target: HTMLElement): void { + const hover = this._managedHovers.get(target); if (hover) { hover.show(true); } } public override dispose(): void { - this._existingHovers.forEach(hover => hover.dispose()); + this._managedHovers.forEach(hover => hover.dispose()); super.dispose(); } } diff --git a/patched-vscode/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/patched-vscode/src/vs/editor/browser/services/hoverService/hoverWidget.ts index ed929bc9..2d8e9ae9 100644 --- a/patched-vscode/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/patched-vscode/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -222,7 +222,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } // Show the hover hint if needed - if (hideOnHover && options.appearance?.showHoverHint) { + if (options.appearance?.showHoverHint) { const statusBarElement = $('div.hover-row.status-bar'); const infoElement = $('div.info'); infoElement.textContent = localize('hoverhint', 'Hold {0} key to mouse over', isMacintosh ? 'Option' : 'Alt'); diff --git a/patched-vscode/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts b/patched-vscode/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts index 3b746de6..46f7423a 100644 --- a/patched-vscode/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts +++ b/patched-vscode/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isHTMLElement } from 'vs/base/browser/dom'; -import type { IHoverWidget, IUpdatableHoverContent, IUpdatableHoverOptions } from 'vs/base/browser/ui/hover/hover'; +import type { IHoverWidget, IManagedHoverContent, IManagedHoverOptions } from 'vs/base/browser/ui/hover/hover'; import type { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/hover/hoverDelegate'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -13,9 +13,9 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { isFunction, isString } from 'vs/base/common/types'; import { localize } from 'vs/nls'; -type IUpdatableHoverResolvedContent = IMarkdownString | string | HTMLElement | undefined; +type IManagedHoverResolvedContent = IMarkdownString | string | HTMLElement | undefined; -export class UpdatableHoverWidget implements IDisposable { +export class ManagedHoverWidget implements IDisposable { private _hoverWidget: IHoverWidget | undefined; private _cancellationTokenSource: CancellationTokenSource | undefined; @@ -23,7 +23,7 @@ export class UpdatableHoverWidget implements IDisposable { constructor(private hoverDelegate: IHoverDelegate, private target: IHoverDelegateTarget | HTMLElement, private fadeInAnimation: boolean) { } - async update(content: IUpdatableHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): Promise { + async update(content: IManagedHoverContent, focus?: boolean, options?: IManagedHoverOptions): Promise { if (this._cancellationTokenSource) { // there's an computation ongoing, cancel it this._cancellationTokenSource.dispose(true); @@ -64,21 +64,24 @@ export class UpdatableHoverWidget implements IDisposable { this.show(resolvedContent, focus, options); } - private show(content: IUpdatableHoverResolvedContent, focus?: boolean, options?: IUpdatableHoverOptions): void { + private show(content: IManagedHoverResolvedContent, focus?: boolean, options?: IManagedHoverOptions): void { const oldHoverWidget = this._hoverWidget; if (this.hasContent(content)) { const hoverOptions: IHoverDelegateOptions = { content, target: this.target, + actions: options?.actions, + linkHandler: options?.linkHandler, + trapFocus: options?.trapFocus, appearance: { showPointer: this.hoverDelegate.placement === 'element', skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing + showHoverHint: options?.appearance?.showHoverHint, }, position: { hoverPosition: HoverPosition.BELOW, }, - ...options }; this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus); @@ -86,7 +89,7 @@ export class UpdatableHoverWidget implements IDisposable { oldHoverWidget?.dispose(); } - private hasContent(content: IUpdatableHoverResolvedContent): content is NonNullable { + private hasContent(content: IManagedHoverResolvedContent): content is NonNullable { if (!content) { return false; } diff --git a/patched-vscode/src/vs/editor/browser/services/treeSitter/treeSitterParserService.ts b/patched-vscode/src/vs/editor/browser/services/treeSitter/treeSitterParserService.ts new file mode 100644 index 00000000..32e4962f --- /dev/null +++ b/patched-vscode/src/vs/editor/browser/services/treeSitter/treeSitterParserService.ts @@ -0,0 +1,471 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { Parser } from '@vscode/tree-sitter-wasm'; +import { AppResourcePath, FileAccess, nodeModulesAsarUnpackedPath, nodeModulesPath } from 'vs/base/common/network'; +import { EDITOR_EXPERIMENTAL_PREFER_TREESITTER, ITreeSitterParserService, ITreeSitterParseResult } from 'vs/editor/common/services/treeSitterParserService'; +import { IModelService } from 'vs/editor/common/services/model'; +import { Disposable, DisposableMap, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { ITextModel } from 'vs/editor/common/model'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IModelContentChange } from 'vs/editor/common/textModelEvents'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { setTimeout0 } from 'vs/base/common/platform'; +import { importAMDNodeModule } from 'vs/amdX'; +import { Emitter, Event } from 'vs/base/common/event'; +import { CancellationToken, cancelOnDispose } from 'vs/base/common/cancellation'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { canASAR } from 'vs/base/common/amd'; +import { CancellationError, isCancellationError } from 'vs/base/common/errors'; +import { PromiseResult } from 'vs/base/common/observableInternal/promise'; + +const EDITOR_TREESITTER_TELEMETRY = 'editor.experimental.treeSitterTelemetry'; +const MODULE_LOCATION_SUBPATH = `@vscode/tree-sitter-wasm/wasm`; +const FILENAME_TREESITTER_WASM = `tree-sitter.wasm`; + +function getModuleLocation(environmentService: IEnvironmentService): AppResourcePath { + return `${(canASAR && environmentService.isBuilt) ? nodeModulesAsarUnpackedPath : nodeModulesPath}/${MODULE_LOCATION_SUBPATH}`; +} + +export class TextModelTreeSitter extends Disposable { + private _parseResult: TreeSitterParseResult | undefined; + + get parseResult(): ITreeSitterParseResult | undefined { return this._parseResult; } + + constructor(readonly model: ITextModel, + private readonly _treeSitterLanguages: TreeSitterLanguages, + private readonly _treeSitterImporter: TreeSitterImporter, + private readonly _logService: ILogService, + private readonly _telemetryService: ITelemetryService + ) { + super(); + this._register(Event.runAndSubscribe(this.model.onDidChangeLanguage, (e => this._onDidChangeLanguage(e ? e.newLanguage : this.model.getLanguageId())))); + } + + private readonly _languageSessionDisposables = this._register(new DisposableStore()); + /** + * Be very careful when making changes to this method as it is easy to introduce race conditions. + */ + private async _onDidChangeLanguage(languageId: string) { + this._languageSessionDisposables.clear(); + this._parseResult = undefined; + + const token = cancelOnDispose(this._languageSessionDisposables); + let language: Parser.Language | undefined; + try { + language = await this._getLanguage(languageId, token); + } catch (e) { + if (isCancellationError(e)) { + return; + } + throw e; + } + + const Parser = await this._treeSitterImporter.getParserClass(); + if (token.isCancellationRequested) { + return; + } + + const treeSitterTree = this._languageSessionDisposables.add(new TreeSitterParseResult(new Parser(), language, this._logService, this._telemetryService)); + this._languageSessionDisposables.add(this.model.onDidChangeContent(e => this._onDidChangeContent(treeSitterTree, e.changes))); + await this._onDidChangeContent(treeSitterTree, []); + if (token.isCancellationRequested) { + return; + } + + this._parseResult = treeSitterTree; + } + + private _getLanguage(languageId: string, token: CancellationToken): Promise { + const language = this._treeSitterLanguages.getOrInitLanguage(languageId); + if (language) { + return Promise.resolve(language); + } + const disposables: IDisposable[] = []; + + return new Promise((resolve, reject) => { + disposables.push(this._treeSitterLanguages.onDidAddLanguage(e => { + if (e.id === languageId) { + dispose(disposables); + resolve(e.language); + } + })); + token.onCancellationRequested(() => { + dispose(disposables); + reject(new CancellationError()); + }, undefined, disposables); + }); + } + + private async _onDidChangeContent(treeSitterTree: TreeSitterParseResult, changes: IModelContentChange[]) { + return treeSitterTree.onDidChangeContent(this.model, changes); + } +} + +const enum TelemetryParseType { + Full = 'fullParse', + Incremental = 'incrementalParse' +} + +export class TreeSitterParseResult implements IDisposable, ITreeSitterParseResult { + private _tree: Parser.Tree | undefined; + private _isDisposed: boolean = false; + constructor(public readonly parser: Parser, + public /** exposed for tests **/ readonly language: Parser.Language, + private readonly _logService: ILogService, + private readonly _telemetryService: ITelemetryService) { + this.parser.setTimeoutMicros(50 * 1000); // 50 ms + this.parser.setLanguage(language); + } + dispose(): void { + this._isDisposed = true; + this._tree?.delete(); + this.parser?.delete(); + } + get tree() { return this._tree; } + private set tree(newTree: Parser.Tree | undefined) { + this._tree?.delete(); + this._tree = newTree; + } + get isDisposed() { return this._isDisposed; } + + private _onDidChangeContentQueue: Promise = Promise.resolve(); + public async onDidChangeContent(model: ITextModel, changes: IModelContentChange[]) { + this._applyEdits(model, changes); + this._onDidChangeContentQueue = this._onDidChangeContentQueue.then(() => { + if (this.isDisposed) { + // No need to continue the queue if we are disposed + return; + } + return this._parseAndUpdateTree(model); + }).catch((e) => { + this._logService.error('Error parsing tree-sitter tree', e); + }); + return this._onDidChangeContentQueue; + } + + private _newEdits = true; + private _applyEdits(model: ITextModel, changes: IModelContentChange[]) { + for (const change of changes) { + const newEndOffset = change.rangeOffset + change.text.length; + const newEndPosition = model.getPositionAt(newEndOffset); + + this.tree?.edit({ + startIndex: change.rangeOffset, + oldEndIndex: change.rangeOffset + change.rangeLength, + newEndIndex: change.rangeOffset + change.text.length, + startPosition: { row: change.range.startLineNumber - 1, column: change.range.startColumn - 1 }, + oldEndPosition: { row: change.range.endLineNumber - 1, column: change.range.endColumn - 1 }, + newEndPosition: { row: newEndPosition.lineNumber - 1, column: newEndPosition.column - 1 } + }); + this._newEdits = true; + } + } + + private async _parseAndUpdateTree(model: ITextModel) { + const tree = await this._parse(model); + if (!this._newEdits) { + this.tree = tree; + } + } + + private _parse(model: ITextModel): Promise { + let parseType: TelemetryParseType = TelemetryParseType.Full; + if (this.tree) { + parseType = TelemetryParseType.Incremental; + } + return this._parseAndYield(model, parseType); + } + + private async _parseAndYield(model: ITextModel, parseType: TelemetryParseType): Promise { + const language = model.getLanguageId(); + let tree: Parser.Tree | undefined; + let time: number = 0; + let passes: number = 0; + this._newEdits = false; + do { + const timer = performance.now(); + try { + tree = this.parser.parse((index: number, position?: Parser.Point) => this._parseCallback(model, index), this.tree); + } catch (e) { + // parsing can fail when the timeout is reached, will resume upon next loop + } finally { + time += performance.now() - timer; + passes++; + } + + // Even if the model changes and edits are applied, the tree parsing will continue correctly after the await. + await new Promise(resolve => setTimeout0(resolve)); + + if (model.isDisposed() || this.isDisposed) { + return; + } + } while (!tree && !this._newEdits); // exit if there a new edits, as anhy parsing done while there are new edits is throw away work + this.sendParseTimeTelemetry(parseType, language, time, passes); + return tree; + } + + private _parseCallback(textModel: ITextModel, index: number): string | null { + return textModel.getTextBuffer().getNearestChunk(index); + } + + private sendParseTimeTelemetry(parseType: TelemetryParseType, languageId: string, time: number, passes: number): void { + this._logService.debug(`Tree parsing (${parseType}) took ${time} ms and ${passes} passes.`); + type ParseTimeClassification = { + owner: 'alros'; + comment: 'Used to understand how long it takes to parse a tree-sitter tree'; + languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The programming language ID.' }; + time: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The ms it took to parse' }; + passes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of passes it took to parse' }; + }; + if (parseType === TelemetryParseType.Full) { + this._telemetryService.publicLog2<{ languageId: string; time: number; passes: number }, ParseTimeClassification>(`treeSitter.fullParse`, { languageId, time, passes }); + } else { + this._telemetryService.publicLog2<{ languageId: string; time: number; passes: number }, ParseTimeClassification>(`treeSitter.incrementalParse`, { languageId, time, passes }); + } + } +} + +export class TreeSitterLanguages extends Disposable { + private _languages: AsyncCache = new AsyncCache(); + public /*exposed for tests*/ readonly _onDidAddLanguage: Emitter<{ id: string; language: Parser.Language }> = this._register(new Emitter()); + /** + * If you're looking for a specific language, make sure to check if it already exists with `getLanguage` as it will kick off the process to add it if it doesn't exist. + */ + public readonly onDidAddLanguage: Event<{ id: string; language: Parser.Language }> = this._onDidAddLanguage.event; + + constructor(private readonly _treeSitterImporter: TreeSitterImporter, + private readonly _fileService: IFileService, + private readonly _environmentService: IEnvironmentService, + private readonly _registeredLanguages: Map, + ) { + super(); + } + + public getOrInitLanguage(languageId: string): Parser.Language | undefined { + if (this._languages.isCached(languageId)) { + return this._languages.getSyncIfCached(languageId); + } else { + // kick off adding the language, but don't wait + this._addLanguage(languageId); + return undefined; + } + } + + private async _addLanguage(languageId: string): Promise { + const languagePromise = this._languages.get(languageId); + if (!languagePromise) { + this._languages.set(languageId, this._fetchLanguage(languageId)); + const language = await this._languages.get(languageId); + if (!language) { + return undefined; + } + this._onDidAddLanguage.fire({ id: languageId, language }); + } + } + + private async _fetchLanguage(languageId: string): Promise { + const grammarName = this._registeredLanguages.get(languageId); + const languageLocation = this._getLanguageLocation(languageId); + if (!grammarName || !languageLocation) { + return undefined; + } + const wasmPath: AppResourcePath = `${languageLocation}/${grammarName}.wasm`; + const languageFile = await (this._fileService.readFile(FileAccess.asFileUri(wasmPath))); + const Parser = await this._treeSitterImporter.getParserClass(); + return Parser.Language.load(languageFile.value.buffer); + } + + private _getLanguageLocation(languageId: string): AppResourcePath | undefined { + const grammarName = this._registeredLanguages.get(languageId); + if (!grammarName) { + return undefined; + } + return getModuleLocation(this._environmentService); + } +} + +export class TreeSitterImporter { + private _treeSitterImport: typeof import('@vscode/tree-sitter-wasm') | undefined; + private async _getTreeSitterImport() { + if (!this._treeSitterImport) { + this._treeSitterImport = await importAMDNodeModule('@vscode/tree-sitter-wasm', 'wasm/tree-sitter.js'); + } + return this._treeSitterImport; + } + + private _parserClass: typeof Parser | undefined; + public async getParserClass() { + if (!this._parserClass) { + this._parserClass = (await this._getTreeSitterImport()).Parser; + } + return this._parserClass; + } +} + +export class TreeSitterTextModelService extends Disposable implements ITreeSitterParserService { + readonly _serviceBrand: undefined; + private _init!: Promise; + private _textModelTreeSitters: DisposableMap = this._register(new DisposableMap()); + private readonly _registeredLanguages: Map = new Map(); + private readonly _treeSitterImporter: TreeSitterImporter = new TreeSitterImporter(); + private readonly _treeSitterLanguages: TreeSitterLanguages; + + public readonly onDidAddLanguage: Event<{ id: string; language: Parser.Language }>; + + constructor(@IModelService private readonly _modelService: IModelService, + @IFileService fileService: IFileService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @ILogService private readonly _logService: ILogService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IEnvironmentService private readonly _environmentService: IEnvironmentService + ) { + super(); + this._treeSitterLanguages = this._register(new TreeSitterLanguages(this._treeSitterImporter, fileService, this._environmentService, this._registeredLanguages)); + this.onDidAddLanguage = this._treeSitterLanguages.onDidAddLanguage; + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(EDITOR_EXPERIMENTAL_PREFER_TREESITTER)) { + this._supportedLanguagesChanged(); + } + })); + this._supportedLanguagesChanged(); + } + + getOrInitLanguage(languageId: string): Parser.Language | undefined { + return this._treeSitterLanguages.getOrInitLanguage(languageId); + } + + getParseResult(textModel: ITextModel): ITreeSitterParseResult | undefined { + const textModelTreeSitter = this._textModelTreeSitters.get(textModel); + return textModelTreeSitter?.parseResult; + } + + private async _doInitParser() { + const Parser = await this._treeSitterImporter.getParserClass(); + const environmentService = this._environmentService; + await Parser.init({ + locateFile(_file: string, _folder: string) { + return FileAccess.asBrowserUri(`${getModuleLocation(environmentService)}/${FILENAME_TREESITTER_WASM}`).toString(true); + } + }); + return true; + } + + private _hasInit: boolean = false; + private async _initParser(hasLanguages: boolean): Promise { + if (this._hasInit) { + return this._init; + } + + if (hasLanguages) { + this._hasInit = true; + this._init = this._doInitParser(); + + // New init, we need to deal with all the existing text models and set up listeners + this._init.then(() => this._registerModelServiceListeners()); + } else { + this._init = Promise.resolve(false); + } + return this._init; + } + + private async _supportedLanguagesChanged() { + const setting = this._getSetting(); + + let hasLanguages = true; + if (setting.length === 0) { + hasLanguages = false; + } + + if (await this._initParser(hasLanguages)) { + // Eventually, this should actually use an extension point to add tree sitter grammars, but for now they are hard coded in core + if (setting.includes('typescript')) { + this._addGrammar('typescript', 'tree-sitter-typescript'); + } else { + this._removeGrammar('typescript'); + } + } + } + + private _getSetting(): string[] { + const setting = this._configurationService.getValue(EDITOR_EXPERIMENTAL_PREFER_TREESITTER); + if (setting && setting.length > 0) { + return setting; + } else { + const expSetting = this._configurationService.getValue(EDITOR_TREESITTER_TELEMETRY); + if (expSetting) { + return ['typescript']; + } + } + return []; + } + + private async _registerModelServiceListeners() { + this._register(this._modelService.onModelAdded(model => { + this._createTextModelTreeSitter(model); + })); + this._register(this._modelService.onModelRemoved(model => { + this._textModelTreeSitters.deleteAndDispose(model); + })); + this._modelService.getModels().forEach(model => this._createTextModelTreeSitter(model)); + } + + private _createTextModelTreeSitter(model: ITextModel) { + const textModelTreeSitter = new TextModelTreeSitter(model, this._treeSitterLanguages, this._treeSitterImporter, this._logService, this._telemetryService); + this._textModelTreeSitters.set(model, textModelTreeSitter); + } + + private _addGrammar(languageId: string, grammarName: string) { + if (!this._registeredLanguages.has(languageId)) { + this._registeredLanguages.set(languageId, grammarName); + } + } + + private _removeGrammar(languageId: string) { + if (this._registeredLanguages.has(languageId)) { + this._registeredLanguages.delete('typescript'); + } + } +} + +class PromiseWithSyncAccess { + private _result: PromiseResult | undefined; + /** + * Returns undefined if the promise did not resolve yet. + */ + get result(): PromiseResult | undefined { + return this._result; + } + + constructor(public readonly promise: Promise) { + promise.then(result => { + this._result = new PromiseResult(result, undefined); + }).catch(e => { + this._result = new PromiseResult(undefined, e); + }); + } +} + +class AsyncCache { + private readonly _values = new Map>(); + + set(key: TKey, promise: Promise) { + this._values.set(key, new PromiseWithSyncAccess(promise)); + } + + get(key: TKey): Promise | undefined { + return this._values.get(key)?.promise; + } + + getSyncIfCached(key: TKey): T | undefined { + return this._values.get(key)?.result?.data; + } + + isCached(key: TKey): boolean { + return this._values.get(key)?.result !== undefined; + } +} diff --git a/patched-vscode/src/vs/editor/browser/view.ts b/patched-vscode/src/vs/editor/browser/view.ts index 5558cf28..6ff5ac82 100644 --- a/patched-vscode/src/vs/editor/browser/view.ts +++ b/patched-vscode/src/vs/editor/browser/view.ts @@ -408,7 +408,7 @@ export class View extends ViewEventHandler { if (this._renderAnimationFrame === null) { const rendering = this._createCoordinatedRendering(); this._renderAnimationFrame = EditorRenderingCoordinator.INSTANCE.scheduleCoordinatedRendering({ - window: dom.getWindow(this.domNode.domNode), + window: dom.getWindow(this.domNode?.domNode), prepareRenderText: () => { if (this._store.isDisposed) { throw new BugIndicatingError(); diff --git a/patched-vscode/src/vs/editor/browser/view/domLineBreaksComputer.ts b/patched-vscode/src/vs/editor/browser/view/domLineBreaksComputer.ts index 64fb2185..2861fd8f 100644 --- a/patched-vscode/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/patched-vscode/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -184,7 +184,7 @@ function createLineBreaks(targetWindow: Window, requests: string[], fontInfo: Fo result[i] = new ModelLineProjectionData(injectionOffsets, injectionOptions, breakOffsets, breakOffsetsVisibleColumn, wrappedTextIndentLength); } - targetWindow.document.body.removeChild(containerDomNode); + containerDomNode.remove(); return result; } diff --git a/patched-vscode/src/vs/editor/browser/view/viewLayer.ts b/patched-vscode/src/vs/editor/browser/view/viewLayer.ts index bbbb0dd9..308e0ff5 100644 --- a/patched-vscode/src/vs/editor/browser/view/viewLayer.ts +++ b/patched-vscode/src/vs/editor/browser/view/viewLayer.ts @@ -35,13 +35,17 @@ export interface ILine { onTokensChanged(): void; } +export interface ILineFactory { + createLine(): T; +} + export class RenderedLinesCollection { - private readonly _createLine: () => T; private _lines!: T[]; private _rendLineNumberStart!: number; - constructor(createLine: () => T) { - this._createLine = createLine; + constructor( + private readonly _lineFactory: ILineFactory, + ) { this._set(1, []); } @@ -201,7 +205,7 @@ export class RenderedLinesCollection { // insert inside the viewport, push out some lines, but not all remaining lines const newLines: T[] = []; for (let i = 0; i < insertCnt; i++) { - newLines[i] = this._createLine(); + newLines[i] = this._lineFactory.createLine(); } const insertIndex = insertFromLineNumber - this._rendLineNumberStart; const beforeLines = this._lines.slice(0, insertIndex); @@ -245,20 +249,14 @@ export class RenderedLinesCollection { } } -export interface IVisibleLinesHost { - createVisibleLine(): T; -} - export class VisibleLinesCollection { - private readonly _host: IVisibleLinesHost; - public readonly domNode: FastDomNode; - private readonly _linesCollection: RenderedLinesCollection; + public readonly domNode: FastDomNode = this._createDomNode(); + private readonly _linesCollection: RenderedLinesCollection = new RenderedLinesCollection(this._lineFactory); - constructor(host: IVisibleLinesHost) { - this._host = host; - this.domNode = this._createDomNode(); - this._linesCollection = new RenderedLinesCollection(() => this._host.createVisibleLine()); + constructor( + private readonly _lineFactory: ILineFactory + ) { } private _createDomNode(): FastDomNode { @@ -295,9 +293,7 @@ export class VisibleLinesCollection { // Remove from DOM for (let i = 0, len = deleted.length; i < len; i++) { const lineDomNode = deleted[i].getDomNode(); - if (lineDomNode) { - this.domNode.domNode.removeChild(lineDomNode); - } + lineDomNode?.remove(); } } @@ -310,9 +306,7 @@ export class VisibleLinesCollection { // Remove from DOM for (let i = 0, len = deleted.length; i < len; i++) { const lineDomNode = deleted[i].getDomNode(); - if (lineDomNode) { - this.domNode.domNode.removeChild(lineDomNode); - } + lineDomNode?.remove(); } } @@ -349,7 +343,7 @@ export class VisibleLinesCollection { const inp = this._linesCollection._get(); - const renderer = new ViewLayerRenderer(this.domNode.domNode, this._host, viewportData); + const renderer = new ViewLayerRenderer(this.domNode.domNode, this._lineFactory, viewportData); const ctx: IRendererContext = { rendLineNumberStart: inp.rendLineNumberStart, @@ -374,14 +368,11 @@ class ViewLayerRenderer { private static _ttPolicy = createTrustedTypesPolicy('editorViewLayer', { createHTML: value => value }); - readonly domNode: HTMLElement; - readonly host: IVisibleLinesHost; - readonly viewportData: ViewportData; - - constructor(domNode: HTMLElement, host: IVisibleLinesHost, viewportData: ViewportData) { - this.domNode = domNode; - this.host = host; - this.viewportData = viewportData; + constructor( + private readonly _domNode: HTMLElement, + private readonly _lineFactory: ILineFactory, + private readonly _viewportData: ViewportData, + ) { } public render(inContext: IRendererContext, startLineNumber: number, stopLineNumber: number, deltaTop: number[]): IRendererContext { @@ -398,7 +389,7 @@ class ViewLayerRenderer { ctx.linesLength = stopLineNumber - startLineNumber + 1; ctx.lines = []; for (let x = startLineNumber; x <= stopLineNumber; x++) { - ctx.lines[x - startLineNumber] = this.host.createVisibleLine(); + ctx.lines[x - startLineNumber] = this._lineFactory.createLine(); } this._finishRendering(ctx, true, deltaTop); return ctx; @@ -465,7 +456,7 @@ class ViewLayerRenderer { for (let i = startIndex; i <= endIndex; i++) { const lineNumber = rendLineNumberStart + i; - lines[i].layoutLine(lineNumber, deltaTop[lineNumber - deltaLN], this.viewportData.lineHeight); + lines[i].layoutLine(lineNumber, deltaTop[lineNumber - deltaLN], this._viewportData.lineHeight); } } @@ -473,7 +464,7 @@ class ViewLayerRenderer { const newLines: T[] = []; let newLinesLen = 0; for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) { - newLines[newLinesLen++] = this.host.createVisibleLine(); + newLines[newLinesLen++] = this._lineFactory.createLine(); } ctx.lines = newLines.concat(ctx.lines); } @@ -481,9 +472,7 @@ class ViewLayerRenderer { private _removeLinesBefore(ctx: IRendererContext, removeCount: number): void { for (let i = 0; i < removeCount; i++) { const lineDomNode = ctx.lines[i].getDomNode(); - if (lineDomNode) { - this.domNode.removeChild(lineDomNode); - } + lineDomNode?.remove(); } ctx.lines.splice(0, removeCount); } @@ -492,7 +481,7 @@ class ViewLayerRenderer { const newLines: T[] = []; let newLinesLen = 0; for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) { - newLines[newLinesLen++] = this.host.createVisibleLine(); + newLines[newLinesLen++] = this._lineFactory.createLine(); } ctx.lines = ctx.lines.concat(newLines); } @@ -502,9 +491,7 @@ class ViewLayerRenderer { for (let i = 0; i < removeCount; i++) { const lineDomNode = ctx.lines[removeIndex + i].getDomNode(); - if (lineDomNode) { - this.domNode.removeChild(lineDomNode); - } + lineDomNode?.remove(); } ctx.lines.splice(removeIndex, removeCount); } @@ -513,14 +500,14 @@ class ViewLayerRenderer { if (ViewLayerRenderer._ttPolicy) { newLinesHTML = ViewLayerRenderer._ttPolicy.createHTML(newLinesHTML as string); } - const lastChild = this.domNode.lastChild; + const lastChild = this._domNode.lastChild; if (domNodeIsEmpty || !lastChild) { - this.domNode.innerHTML = newLinesHTML as string; // explains the ugly casts -> https://github.com/microsoft/vscode/issues/106396#issuecomment-692625393; + this._domNode.innerHTML = newLinesHTML as string; // explains the ugly casts -> https://github.com/microsoft/vscode/issues/106396#issuecomment-692625393; } else { lastChild.insertAdjacentHTML('afterend', newLinesHTML as string); } - let currChild = this.domNode.lastChild; + let currChild = this._domNode.lastChild; for (let i = ctx.linesLength - 1; i >= 0; i--) { const line = ctx.lines[i]; if (wasNew[i]) { @@ -573,7 +560,7 @@ class ViewLayerRenderer { continue; } - const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this.viewportData.lineHeight, this.viewportData, sb); + const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this._viewportData.lineHeight, this._viewportData, sb); if (!renderResult) { // line does not need rendering continue; @@ -603,7 +590,7 @@ class ViewLayerRenderer { continue; } - const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this.viewportData.lineHeight, this.viewportData, sb); + const renderResult = line.renderLine(i + rendLineNumberStart, deltaTop[i], this._viewportData.lineHeight, this._viewportData, sb); if (!renderResult) { // line does not need rendering continue; diff --git a/patched-vscode/src/vs/editor/browser/view/viewOverlays.ts b/patched-vscode/src/vs/editor/browser/view/viewOverlays.ts index 1041fd58..1fd1c011 100644 --- a/patched-vscode/src/vs/editor/browser/view/viewOverlays.ts +++ b/patched-vscode/src/vs/editor/browser/view/viewOverlays.ts @@ -6,7 +6,7 @@ import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; -import { IVisibleLine, IVisibleLinesHost, VisibleLinesCollection } from 'vs/editor/browser/view/viewLayer'; +import { IVisibleLine, VisibleLinesCollection } from 'vs/editor/browser/view/viewLayer'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; @@ -15,26 +15,24 @@ import * as viewEvents from 'vs/editor/common/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -export class ViewOverlays extends ViewPart implements IVisibleLinesHost { - +export class ViewOverlays extends ViewPart { private readonly _visibleLines: VisibleLinesCollection; protected readonly domNode: FastDomNode; - private _dynamicOverlays: DynamicViewOverlay[]; - private _isFocused: boolean; + private _dynamicOverlays: DynamicViewOverlay[] = []; + private _isFocused: boolean = false; constructor(context: ViewContext) { super(context); - this._visibleLines = new VisibleLinesCollection(this); + this._visibleLines = new VisibleLinesCollection({ + createLine: () => new ViewOverlayLine(this._dynamicOverlays) + }); this.domNode = this._visibleLines.domNode; const options = this._context.configuration.options; const fontInfo = options.get(EditorOption.fontInfo); applyFontInfo(this.domNode, fontInfo); - this._dynamicOverlays = []; - this._isFocused = false; - this.domNode.setClassName('view-overlays'); } @@ -67,14 +65,6 @@ export class ViewOverlays extends ViewPart implements IVisibleLinesHost, IViewLines { +export class ViewLines extends ViewPart implements IViewLines { /** * Adds this amount of pixels to the right of lines (no-one wants to type near the edge of the viewport) */ @@ -122,10 +122,6 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, constructor(context: ViewContext, linesContent: FastDomNode) { super(context); - this._linesContent = linesContent; - this._textRangeRestingSpot = document.createElement('div'); - this._visibleLines = new VisibleLinesCollection(this); - this.domNode = this._visibleLines.domNode; const conf = this._context.configuration; const options = this._context.configuration.options; @@ -141,6 +137,13 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._canUseLayerHinting = !options.get(EditorOption.disableLayerHinting); this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type); + this._linesContent = linesContent; + this._textRangeRestingSpot = document.createElement('div'); + this._visibleLines = new VisibleLinesCollection({ + createLine: () => new ViewLine(this._viewLineOptions), + }); + this.domNode = this._visibleLines.domNode; + PartFingerprints.write(this.domNode, PartFingerprint.ViewLines); this.domNode.setClassName(`view-lines ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`); applyFontInfo(this.domNode, fontInfo); @@ -173,14 +176,6 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return this.domNode; } - // ---- begin IVisibleLinesHost - - public createVisibleLine(): ViewLine { - return new ViewLine(this._viewLineOptions); - } - - // ---- end IVisibleLinesHost - // ---- begin view event handlers public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { @@ -710,12 +705,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, let paddingBottom: number = 0; if (!shouldIgnoreScrollOff) { - const context = Math.min((viewportHeight / this._lineHeight) / 2, this._cursorSurroundingLines); - if (this._stickyScrollEnabled) { - paddingTop = Math.max(context, this._maxNumberStickyLines) * this._lineHeight; - } else { - paddingTop = context * this._lineHeight; - } + const maxLinesInViewport = (viewportHeight / this._lineHeight); + const surroundingLines = Math.max(this._cursorSurroundingLines, this._stickyScrollEnabled ? this._maxNumberStickyLines : 0); + const context = Math.min(maxLinesInViewport / 2, surroundingLines); + paddingTop = context * this._lineHeight; paddingBottom = Math.max(0, (context - 1)) * this._lineHeight; } else { if (!minimalReveal) { diff --git a/patched-vscode/src/vs/editor/browser/viewParts/minimap/minimap.ts b/patched-vscode/src/vs/editor/browser/viewParts/minimap/minimap.ts index 7b49f5b0..19db0c6b 100644 --- a/patched-vscode/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/patched-vscode/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -445,9 +445,9 @@ class RenderData { ) { this.renderedLayout = renderedLayout; this._imageData = imageData; - this._renderedLines = new RenderedLinesCollection( - () => MinimapLine.INVALID - ); + this._renderedLines = new RenderedLinesCollection({ + createLine: () => MinimapLine.INVALID + }); this._renderedLines._set(renderedLayout.startLineNumber, lines); } diff --git a/patched-vscode/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/patched-vscode/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 0ca31065..8e3eb566 100644 --- a/patched-vscode/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/patched-vscode/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -512,9 +512,7 @@ export class DecorationsOverviewRuler extends ViewPart { canvasCtx.strokeStyle = this._settings.borderColor; canvasCtx.moveTo(0, 0); canvasCtx.lineTo(0, canvasHeight); - canvasCtx.stroke(); - - canvasCtx.moveTo(0, 0); + canvasCtx.moveTo(1, 0); canvasCtx.lineTo(canvasWidth, 0); canvasCtx.stroke(); } diff --git a/patched-vscode/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/patched-vscode/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index 37914a70..a99dac77 100644 --- a/patched-vscode/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/patched-vscode/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -271,12 +271,12 @@ export class ViewZones extends ViewPart { zone.domNode.removeAttribute('monaco-visible-view-zone'); zone.domNode.removeAttribute('monaco-view-zone'); - zone.domNode.domNode.parentNode!.removeChild(zone.domNode.domNode); + zone.domNode.domNode.remove(); if (zone.marginDomNode) { zone.marginDomNode.removeAttribute('monaco-visible-view-zone'); zone.marginDomNode.removeAttribute('monaco-view-zone'); - zone.marginDomNode.domNode.parentNode!.removeChild(zone.marginDomNode.domNode); + zone.marginDomNode.domNode.remove(); } this.setShouldRender(); diff --git a/patched-vscode/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts b/patched-vscode/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index 07753688..660e00ee 100644 --- a/patched-vscode/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts +++ b/patched-vscode/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -577,7 +577,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData) { return -1; } - return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, 1, includeViewZones); + const maxCol = this._modelData.model.getLineMaxColumn(lineNumber); + return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, maxCol, includeViewZones); } public setHiddenAreas(ranges: IRange[], source?: unknown): void { @@ -1613,7 +1614,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE public setBanner(domNode: HTMLElement | null, domNodeHeight: number): void { if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) { - this._domElement.removeChild(this._bannerDomNode); + this._bannerDomNode.remove(); } this._bannerDomNode = domNode; @@ -1648,6 +1649,16 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this.languageConfigurationService, this._themeService, attachedView, + { + batchChanges: (cb) => { + try { + this._beginUpdate(); + return cb(); + } finally { + this._endUpdate(); + } + }, + } ); // Someone might destroy the model from under the editor, so prevent any exceptions by setting a null model @@ -1874,10 +1885,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._domElement.removeAttribute('data-mode-id'); if (removeDomNode && this._domElement.contains(removeDomNode)) { - this._domElement.removeChild(removeDomNode); + removeDomNode.remove(); } if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) { - this._domElement.removeChild(this._bannerDomNode); + this._bannerDomNode.remove(); } return model; } diff --git a/patched-vscode/src/vs/editor/browser/widget/codeEditor/editor.css b/patched-vscode/src/vs/editor/browser/widget/codeEditor/editor.css index 5ef7246d..a6d82d58 100644 --- a/patched-vscode/src/vs/editor/browser/widget/codeEditor/editor.css +++ b/patched-vscode/src/vs/editor/browser/widget/codeEditor/editor.css @@ -23,6 +23,7 @@ -webkit-text-size-adjust: 100%; color: var(--vscode-editor-foreground); background-color: var(--vscode-editor-background); + overflow-wrap: initial; } .monaco-editor-background { background-color: var(--vscode-editor-background); diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorDecorations.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorDecorations.ts index a5992771..efbbe00f 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorDecorations.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorDecorations.ts @@ -6,6 +6,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, derived } from 'vs/base/common/observable'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; +import { allowsTrueInlineDiffRendering } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; @@ -28,7 +29,8 @@ export class DiffEditorDecorations extends Disposable { } private readonly _decorations = derived(this, (reader) => { - const diff = this._diffModel.read(reader)?.diff.read(reader); + const diffModel = this._diffModel.read(reader); + const diff = diffModel?.diff.read(reader); if (!diff) { return null; } @@ -56,13 +58,29 @@ export class DiffEditorDecorations extends Disposable { modifiedDecorations.push({ range: m.lineRangeMapping.modified.toInclusiveRange()!, options: diffWholeLineAddDecoration }); } } else { + const useInlineDiff = this._options.useTrueInlineDiffRendering.read(reader) && allowsTrueInlineDiffRendering(m.lineRangeMapping); for (const i of m.lineRangeMapping.innerChanges || []) { // Don't show empty markers outside the line range if (m.lineRangeMapping.original.contains(i.originalRange.startLineNumber)) { originalDecorations.push({ range: i.originalRange, options: (i.originalRange.isEmpty() && showEmptyDecorations) ? diffDeleteDecorationEmpty : diffDeleteDecoration }); } if (m.lineRangeMapping.modified.contains(i.modifiedRange.startLineNumber)) { - modifiedDecorations.push({ range: i.modifiedRange, options: (i.modifiedRange.isEmpty() && showEmptyDecorations) ? diffAddDecorationEmpty : diffAddDecoration }); + modifiedDecorations.push({ range: i.modifiedRange, options: (i.modifiedRange.isEmpty() && showEmptyDecorations && !useInlineDiff) ? diffAddDecorationEmpty : diffAddDecoration }); + } + if (useInlineDiff) { + const deletedText = diffModel!.model.original.getValueInRange(i.originalRange); + modifiedDecorations.push({ + range: i.modifiedRange, + options: { + description: 'deleted-text', + before: { + content: deletedText, + inlineClassName: 'inline-deleted-text', + }, + zIndex: 100000, + showIfCollapsed: true, + } + }); } } } diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index db99842d..ee7935fd 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IReader, autorunHandleChanges, derived, derivedOpts, observableFromEvent } from 'vs/base/common/observable'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { obsCodeEditor } from 'vs/editor/browser/observableUtilities'; +import { observableCodeEditor } from 'vs/editor/browser/observableCodeEditor'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature'; @@ -27,18 +27,21 @@ export class DiffEditorEditors extends Disposable { private readonly _onDidContentSizeChange = this._register(new Emitter()); public get onDidContentSizeChange() { return this._onDidContentSizeChange.event; } - public readonly modifiedScrollTop = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this.modified.getScrollTop()); - public readonly modifiedScrollHeight = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight()); + public readonly modifiedScrollTop = observableFromEvent(this, this.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this.modified.getScrollTop()); + public readonly modifiedScrollHeight = observableFromEvent(this, this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight()); - public readonly modifiedModel = obsCodeEditor(this.modified).model; + public readonly modifiedObs = observableCodeEditor(this.modified); + public readonly originalObs = observableCodeEditor(this.original); - public readonly modifiedSelections = observableFromEvent(this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []); + public readonly modifiedModel = this.modifiedObs.model; + + public readonly modifiedSelections = observableFromEvent(this, this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []); public readonly modifiedCursor = derivedOpts({ owner: this, equalsFn: Position.equals }, reader => this.modifiedSelections.read(reader)[0]?.getPosition() ?? new Position(1, 1)); - public readonly originalCursor = observableFromEvent(this.original.onDidChangeCursorPosition, () => this.original.getPosition() ?? new Position(1, 1)); + public readonly originalCursor = observableFromEvent(this, this.original.onDidChangeCursorPosition, () => this.original.getPosition() ?? new Position(1, 1)); - public readonly isOriginalFocused = obsCodeEditor(this.original).isFocused; - public readonly isModifiedFocused = obsCodeEditor(this.modified).isFocused; + public readonly isOriginalFocused = observableCodeEditor(this.original).isFocused; + public readonly isModifiedFocused = observableCodeEditor(this.modified).isFocused; public readonly isFocused = derived(this, reader => this.isOriginalFocused.read(reader) || this.isModifiedFocused.read(reader)); diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index 23a75bac..3b1b222d 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -30,6 +30,7 @@ import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewMod import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DiffEditorOptions } from '../../diffEditorOptions'; +import { Range } from 'vs/editor/common/core/range'; /** * Ensures both editors have the same height by aligning unchanged lines. @@ -81,7 +82,7 @@ export class DiffEditorViewZones extends Disposable { })); const originalModelTokenizationCompleted = this._diffModel.map(m => - m ? observableFromEvent(m.model.original.onDidChangeTokens, () => m.model.original.tokenization.backgroundTokenizationState === BackgroundTokenizationState.Completed) : undefined + m ? observableFromEvent(this, m.model.original.onDidChangeTokens, () => m.model.original.tokenization.backgroundTokenizationState === BackgroundTokenizationState.Completed) : undefined ).map((m, reader) => m?.read(reader)); const alignments = derived((reader) => { @@ -187,7 +188,7 @@ export class DiffEditorViewZones extends Disposable { const renderOptions = RenderOptions.fromEditor(this._editors.modified); for (const a of alignmentsVal) { - if (a.diff && !renderSideBySide) { + if (a.diff && !renderSideBySide && (!this._options.useTrueInlineDiffRendering.read(reader) || !allowsTrueInlineDiffRendering(a.diff))) { if (!a.originalRange.isEmpty) { originalModelTokenizationCompleted.read(reader); // Update view-zones once tokenization completes @@ -525,13 +526,15 @@ function computeRangeAlignment( let lastModLineNumber = c.modified.startLineNumber; let lastOrigLineNumber = c.original.startLineNumber; - function emitAlignment(origLineNumberExclusive: number, modLineNumberExclusive: number) { + function emitAlignment(origLineNumberExclusive: number, modLineNumberExclusive: number, forceAlignment = false) { if (origLineNumberExclusive < lastOrigLineNumber || modLineNumberExclusive < lastModLineNumber) { return; } if (first) { first = false; - } else if (origLineNumberExclusive === lastOrigLineNumber || modLineNumberExclusive === lastModLineNumber) { + } else if (!forceAlignment && (origLineNumberExclusive === lastOrigLineNumber || modLineNumberExclusive === lastModLineNumber)) { + // This causes a re-alignment of an already aligned line. + // However, we don't care for the final alignment. return; } const originalRange = new LineRange(lastOrigLineNumber, origLineNumberExclusive); @@ -575,7 +578,7 @@ function computeRangeAlignment( } } - emitAlignment(c.original.endLineNumberExclusive, c.modified.endLineNumberExclusive); + emitAlignment(c.original.endLineNumberExclusive, c.modified.endLineNumberExclusive, true); lastOriginalLineNumber = c.original.endLineNumberExclusive; lastModifiedLineNumber = c.modified.endLineNumberExclusive; @@ -625,3 +628,17 @@ function getAdditionalLineHeights(editor: CodeEditorWidget, viewZonesToIgnore: R return result; } + +export function allowsTrueInlineDiffRendering(mapping: DetailedLineRangeMapping): boolean { + if (!mapping.innerChanges) { + return false; + } + return mapping.innerChanges.every(c => + (rangeIsSingleLine(c.modifiedRange) && rangeIsSingleLine(c.originalRange)) + || c.originalRange.equalsRange(new Range(1, 1, 1, 1)) + ); +} + +function rangeIsSingleLine(range: Range): boolean { + return range.startLineNumber === range.endLineNumber; +} diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts index f5bbdb1b..ea3506b7 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts @@ -4,9 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IObservable, ISettableObservable, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { derivedConstOnceDefined } from 'vs/base/common/observableInternal/utils'; import { Constants } from 'vs/base/common/uint'; +import { allowsTrueInlineDiffRendering } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones'; +import { DiffEditorViewModel, DiffState } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { diffEditorDefaultOptions } from 'vs/editor/common/config/diffEditor'; import { IDiffEditorBaseOptions, IDiffEditorOptions, IEditorOptions, ValidDiffEditorBaseOptions, clampedFloat, clampedInt, boolean as validateBooleanOption, stringSet as validateStringSetOption } from 'vs/editor/common/config/editorOptions'; +import { LineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export class DiffEditorOptions { @@ -16,7 +20,7 @@ export class DiffEditorOptions { private readonly _diffEditorWidth = observableValue(this, 0); - private readonly _screenReaderMode = observableFromEvent(this._accessibilityService.onDidChangeScreenReaderOptimized, () => this._accessibilityService.isScreenReaderOptimized()); + private readonly _screenReaderMode = observableFromEvent(this, this._accessibilityService.onDidChangeScreenReaderOptimized, () => this._accessibilityService.isScreenReaderOptimized()); constructor( options: Readonly, @@ -31,9 +35,16 @@ export class DiffEditorOptions { ); public readonly renderOverviewRuler = derived(this, reader => this._options.read(reader).renderOverviewRuler); - public readonly renderSideBySide = derived(this, reader => this._options.read(reader).renderSideBySide - && !(this._options.read(reader).useInlineViewWhenSpaceIsLimited && this.couldShowInlineViewBecauseOfSize.read(reader) && !this._screenReaderMode.read(reader)) - ); + public readonly renderSideBySide = derived(this, reader => { + if (this.compactMode.read(reader)) { + if (this.shouldRenderInlineViewInSmartMode.read(reader)) { + return false; + } + } + + return this._options.read(reader).renderSideBySide + && !(this._options.read(reader).useInlineViewWhenSpaceIsLimited && this.couldShowInlineViewBecauseOfSize.read(reader) && !this._screenReaderMode.read(reader)); + }); public readonly readOnly = derived(this, reader => this._options.read(reader).readOnly); public readonly shouldRenderOldRevertArrows = derived(this, reader => { @@ -59,6 +70,14 @@ export class DiffEditorOptions { public readonly diffAlgorithm = derived(this, reader => this._options.read(reader).diffAlgorithm); public readonly showEmptyDecorations = derived(this, reader => this._options.read(reader).experimental.showEmptyDecorations!); public readonly onlyShowAccessibleDiffViewer = derived(this, reader => this._options.read(reader).onlyShowAccessibleDiffViewer); + public readonly compactMode = derived(this, reader => this._options.read(reader).compactMode); + private readonly trueInlineDiffRenderingEnabled: IObservable = derived(this, reader => + this._options.read(reader).experimental.useTrueInlineView! + ); + + public readonly useTrueInlineDiffRendering: IObservable = derived(this, reader => + !this.renderSideBySide.read(reader) && this.trueInlineDiffRenderingEnabled.read(reader) + ); public readonly hideUnchangedRegions = derived(this, reader => this._options.read(reader).hideUnchangedRegions.enabled!); public readonly hideUnchangedRegionsRevealLineCount = derived(this, reader => this._options.read(reader).hideUnchangedRegions.revealLineCount!); @@ -74,9 +93,37 @@ export class DiffEditorOptions { public setWidth(width: number): void { this._diffEditorWidth.set(width, undefined); } + + private readonly _model = observableValue(this, undefined); + + public setModel(model: DiffEditorViewModel | undefined) { + this._model.set(model, undefined); + } + + private readonly shouldRenderInlineViewInSmartMode = this._model + .map(this, model => derivedConstOnceDefined(this, reader => { + const diffs = model?.diff.read(reader); + return diffs ? isSimpleDiff(diffs, this.trueInlineDiffRenderingEnabled.read(reader)) : undefined; + })) + .flatten() + .map(this, v => !!v); + + public readonly inlineViewHideOriginalLineNumbers = this.compactMode; +} + +function isSimpleDiff(diff: DiffState, supportsTrueDiffRendering: boolean): boolean { + return diff.mappings.every(m => isInsertion(m.lineRangeMapping) || isDeletion(m.lineRangeMapping) || (supportsTrueDiffRendering && allowsTrueInlineDiffRendering(m.lineRangeMapping))); +} + +function isInsertion(mapping: LineRangeMapping): boolean { + return mapping.original.length === 0; +} + +function isDeletion(mapping: LineRangeMapping): boolean { + return mapping.modified.length === 0; } -function validateDiffEditorOptions(options: Readonly, defaults: ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions { +function validateDiffEditorOptions(options: Readonly, defaults: typeof diffEditorDefaultOptions | ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions { return { enableSplitViewResizing: validateBooleanOption(options.enableSplitViewResizing, defaults.enableSplitViewResizing), splitViewDefaultRatio: clampedFloat(options.splitViewDefaultRatio, 0.5, 0.1, 0.9), @@ -95,6 +142,7 @@ function validateDiffEditorOptions(options: Readonly, defaul experimental: { showMoves: validateBooleanOption(options.experimental?.showMoves, defaults.experimental.showMoves!), showEmptyDecorations: validateBooleanOption(options.experimental?.showEmptyDecorations, defaults.experimental.showEmptyDecorations!), + useTrueInlineView: validateBooleanOption(options.experimental?.useTrueInlineView, defaults.experimental.useTrueInlineView!), }, hideUnchangedRegions: { enabled: validateBooleanOption(options.hideUnchangedRegions?.enabled ?? (options.experimental as any)?.collapseUnchangedRegions, defaults.hideUnchangedRegions.enabled!), @@ -107,5 +155,6 @@ function validateDiffEditorOptions(options: Readonly, defaul renderSideBySideInlineBreakpoint: clampedInt(options.renderSideBySideInlineBreakpoint, defaults.renderSideBySideInlineBreakpoint, 0, Constants.MAX_SAFE_SMALL_INTEGER), useInlineViewWhenSpaceIsLimited: validateBooleanOption(options.useInlineViewWhenSpaceIsLimited, defaults.useInlineViewWhenSpaceIsLimited), renderGutterMenu: validateBooleanOption(options.renderGutterMenu, defaults.renderGutterMenu), + compactMode: validateBooleanOption(options.compactMode, defaults.compactMode), }; } diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts index b1520c88..557b7575 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts @@ -8,7 +8,8 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ISettableObservable, ITransaction, autorun, autorunWithStore, derived, observableSignal, observableSignalFromEvent, observableValue, transaction, waitForState } from 'vs/base/common/observable'; import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; -import { filterWithPrevious, readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; +import { filterWithPrevious } from 'vs/editor/browser/widget/diffEditor/utils'; +import { readHotReloadableExport } from 'vs/base/common/hotReloadHelpers'; import { ISerializedLineRange, LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange'; import { DefaultLinesDiffComputer } from 'vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer'; import { IDocumentDiff } from 'vs/editor/common/diff/documentDiffProvider'; @@ -393,6 +394,7 @@ function normalizeRangeMapping(rangeMapping: RangeMapping, original: ITextModel, let originalRange = rangeMapping.originalRange; let modifiedRange = rangeMapping.modifiedRange; if ( + originalRange.startColumn === 1 && modifiedRange.startColumn === 1 && (originalRange.endColumn !== 1 || modifiedRange.endColumn !== 1) && originalRange.endColumn === original.getLineMaxColumn(originalRange.endLineNumber) && modifiedRange.endColumn === modified.getLineMaxColumn(modifiedRange.endLineNumber) diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index c3c79852..e22b0291 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -5,10 +5,10 @@ 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'; +import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, ITransaction, autorun, autorunWithStore, derived, observableFromEvent, observableValue, recomputeInitiallyAndOnChange, subtransaction, transaction } from 'vs/base/common/observable'; +import { IObservable, ITransaction, autorun, autorunWithStore, derived, disposableObservableValue, observableFromEvent, observableValue, recomputeInitiallyAndOnChange, subtransaction, transaction } from 'vs/base/common/observable'; import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; import 'vs/css!./style'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; @@ -26,7 +26,8 @@ import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor import { MovedBlocksLinesFeature } from 'vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature'; import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature'; import { RevertButtonsFeature } from 'vs/editor/browser/widget/diffEditor/features/revertButtonsFeature'; -import { CSSStyle, ObservableElementSizeObserver, applyStyle, applyViewZones, readHotReloadableExport, translatePosition } from 'vs/editor/browser/widget/diffEditor/utils'; +import { CSSStyle, ObservableElementSizeObserver, RefCounted, applyStyle, applyViewZones, translatePosition } from 'vs/editor/browser/widget/diffEditor/utils'; +import { readHotReloadableExport } from 'vs/base/common/hotReloadHelpers'; import { bindContextKey } from 'vs/platform/observable/common/platformObservableUtils'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; @@ -61,8 +62,8 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { h('div.editor.modified@modified', { style: { position: 'absolute', height: '100%', } }), h('div.accessibleDiffViewer@accessibleDiffViewer', { style: { position: 'absolute', height: '100%' } }), ]); - private readonly _diffModel = observableValue(this, undefined); - private _shouldDisposeDiffModel = false; + private readonly _diffModelSrc = this._register(disposableObservableValue | undefined>(this, undefined)); + private readonly _diffModel = derived(this, reader => this._diffModelSrc.read(reader)?.object); public readonly onDidChangeModel = Event.fromObservableLight(this._diffModel); public get onDidContentSizeChange() { return this._editors.onDidContentSizeChange; } @@ -111,7 +112,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._contextKeyService.createKey('isInDiffEditor', true); this._domElement.appendChild(this.elements.root); - this._register(toDisposable(() => this._domElement.removeChild(this.elements.root))); + this._register(toDisposable(() => this.elements.root.remove())); this._rootSizeObserver = this._register(new ObservableElementSizeObserver(this.elements.root, options.dimension)); this._rootSizeObserver.setAutomaticLayout(options.automaticLayout ?? false); @@ -317,14 +318,23 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { } })); - this._register(toDisposable(() => { - if (this._shouldDisposeDiffModel) { - this._diffModel.get()?.dispose(); - } + this._register(autorunWithStore((reader, store) => { + store.add(new (readHotReloadableExport(RevertButtonsFeature, reader))(this._editors, this._diffModel, this._options, this)); })); this._register(autorunWithStore((reader, store) => { - store.add(new (readHotReloadableExport(RevertButtonsFeature, reader))(this._editors, this._diffModel, this._options, this)); + const model = this._diffModel.read(reader); + if (!model) { return; } + for (const m of [model.model.original, model.model.modified]) { + store.add(m.onWillDispose(e => { + onUnexpectedError(new BugIndicatingError('TextModel got disposed before DiffEditorWidget model got reset')); + this.setModel(null); + })); + } + })); + + this._register(autorun(reader => { + this._options.setModel(this._diffModel.read(reader)); })); } @@ -375,8 +385,13 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { } else { gutterLeft = 0; + const shouldHideOriginalLineNumbers = this._options.inlineViewHideOriginalLineNumbers.read(reader); originalLeft = gutterWidth; - originalWidth = Math.max(5, this._editors.original.getLayoutInfo().decorationsLeft); + if (shouldHideOriginalLineNumbers) { + originalWidth = 0; + } else { + originalWidth = Math.max(5, this._editors.originalObs.layoutInfoDecorationsLeft.read(reader)); + } modifiedLeft = gutterWidth + originalWidth; modifiedWidth = fullWidth - modifiedLeft - overviewRulerPartWidth; @@ -462,30 +477,36 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { override getModel(): IDiffEditorModel | null { return this._diffModel.get()?.model ?? null; } - override setModel(model: IDiffEditorModel | null | IDiffEditorViewModel, tx?: ITransaction): void { - if (!model && this._diffModel.get()) { + override setModel(model: IDiffEditorModel | null | IDiffEditorViewModel): void { + const vm = !model ? null + : ('model' in model) ? RefCounted.create(model).createNewRef(this) + : RefCounted.create(this.createViewModel(model), this); + this.setDiffModel(vm); + } + + setDiffModel(viewModel: RefCounted | null, tx?: ITransaction): void { + const currentModel = this._diffModel.get(); + + if (!viewModel && currentModel) { // Transitioning from a model to no-model this._accessibleDiffViewer.get().close(); } - const vm = model ? ('model' in model) ? { model, shouldDispose: false } : { model: this.createViewModel(model), shouldDispose: true } : undefined; - - if (this._diffModel.get() !== vm?.model) { + if (this._diffModel.get() !== viewModel?.object) { subtransaction(tx, tx => { + const vm = viewModel?.object; /** @description DiffEditorWidget.setModel */ observableFromEvent.batchEventsGlobally(tx, () => { - this._editors.original.setModel(vm ? vm.model.model.original : null); - this._editors.modified.setModel(vm ? vm.model.model.modified : null); + this._editors.original.setModel(vm ? vm.model.original : null); + this._editors.modified.setModel(vm ? vm.model.modified : null); }); - const prevValue = this._diffModel.get(); - const shouldDispose = this._shouldDisposeDiffModel; - - this._shouldDisposeDiffModel = vm?.shouldDispose ?? false; - this._diffModel.set(vm?.model as (DiffEditorViewModel | undefined), tx); - - if (shouldDispose) { - prevValue?.dispose(); - } + const prevValueRef = this._diffModelSrc.get()?.createNewRef(this); + this._diffModelSrc.set(viewModel?.createNewRef(this) as RefCounted | undefined, tx); + setTimeout(() => { + // async, so that this runs after the transaction finished. + // TODO: use the transaction to schedule disposal + prevValueRef?.dispose(); + }, 0); }); } } diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffProviderFactoryService.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffProviderFactoryService.ts index 46751851..19cee599 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffProviderFactoryService.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/diffProviderFactoryService.ts @@ -152,7 +152,7 @@ export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, I // max 10 items in cache if (WorkerBasedDocumentDiffProvider.diffCache.size > 10) { - WorkerBasedDocumentDiffProvider.diffCache.delete(WorkerBasedDocumentDiffProvider.diffCache.keys().next().value); + WorkerBasedDocumentDiffProvider.diffCache.delete(WorkerBasedDocumentDiffProvider.diffCache.keys().next().value!); } WorkerBasedDocumentDiffProvider.diffCache.set(uriKey, { result, context }); diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts index e7c76c72..010fba5e 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/gutterFeature.ts @@ -37,7 +37,7 @@ const width = 35; export class DiffEditorGutter extends Disposable { private readonly _menu = this._register(this._menuService.createMenu(MenuId.DiffEditorHunkToolbar, this._contextKeyService)); - private readonly _actions = observableFromEvent(this._menu.onDidChange, () => this._menu.getActions()); + private readonly _actions = observableFromEvent(this, this._menu.onDidChange, () => this._menu.getActions()); private readonly _hasActions = this._actions.map(a => a.length > 0); private readonly _showSash = derived(this, reader => this._options.renderSideBySide.read(reader) && this._hasActions.read(reader)); diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index 248f2b46..336bdb43 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -8,11 +8,12 @@ import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/i import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, IReader, autorun, derived, derivedWithStore, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { IObservable, IReader, autorun, derived, derivedWithStore, observableValue, transaction } from 'vs/base/common/observable'; import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { observableCodeEditor } from 'vs/editor/browser/observableCodeEditor'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; import { DiffEditorViewModel, RevealPreference, UnchangedRegion } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; @@ -31,7 +32,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti * Make sure to add the view zones to the editor! */ export class HideUnchangedRegionsFeature extends Disposable { - private static readonly _breadcrumbsSourceFactory = observableValue<((textModel: ITextModel, instantiationService: IInstantiationService) => IDiffEditorBreadcrumbsSource) | undefined>('breadcrumbsSourceFactory', undefined); + private static readonly _breadcrumbsSourceFactory = observableValue<((textModel: ITextModel, instantiationService: IInstantiationService) => IDiffEditorBreadcrumbsSource)>( + HideUnchangedRegionsFeature, () => ({ + dispose() { + }, + getBreadcrumbItems(startRange, reader) { + return []; + }, + })); public static setBreadcrumbsSourceFactory(factory: (textModel: ITextModel, instantiationService: IInstantiationService) => IDiffEditorBreadcrumbsSource) { this._breadcrumbsSourceFactory.set(factory, undefined); } @@ -97,41 +105,72 @@ export class HideUnchangedRegionsFeature extends Disposable { const modViewZones: IObservableViewZone[] = []; const sideBySide = this._options.renderSideBySide.read(reader); + const compactMode = this._options.compactMode.read(reader); + const curUnchangedRegions = unchangedRegions.read(reader); - for (const r of curUnchangedRegions) { + for (let i = 0; i < curUnchangedRegions.length; i++) { + const r = curUnchangedRegions[i]; if (r.shouldHideControls(reader)) { continue; } - { - const d = derived(this, reader => /** @description hiddenOriginalRangeStart */ r.getHiddenOriginalRange(reader).startLineNumber - 1); - const origVz = new PlaceholderViewZone(d, 24); - origViewZones.push(origVz); - store.add(new CollapsedCodeOverlayWidget( - this._editors.original, - origVz, - r, - r.originalUnchangedRange, - !sideBySide, - modifiedOutlineSource, - l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, RevealPreference.FromBottom, undefined), - this._options, - )); + if (compactMode && (i === 0 || i === curUnchangedRegions.length - 1)) { + continue; } - { - const d = derived(this, reader => /** @description hiddenModifiedRangeStart */ r.getHiddenModifiedRange(reader).startLineNumber - 1); - const modViewZone = new PlaceholderViewZone(d, 24); - modViewZones.push(modViewZone); - store.add(new CollapsedCodeOverlayWidget( - this._editors.modified, - modViewZone, - r, - r.modifiedUnchangedRange, - false, - modifiedOutlineSource, - l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, RevealPreference.FromBottom, undefined), - this._options, - )); + + if (compactMode) { + { + const d = derived(this, reader => /** @description hiddenOriginalRangeStart */ r.getHiddenOriginalRange(reader).startLineNumber - 1); + const origVz = new PlaceholderViewZone(d, 12); + origViewZones.push(origVz); + store.add(new CompactCollapsedCodeOverlayWidget( + this._editors.original, + origVz, + r, + !sideBySide, + )); + } + { + const d = derived(this, reader => /** @description hiddenModifiedRangeStart */ r.getHiddenModifiedRange(reader).startLineNumber - 1); + const modViewZone = new PlaceholderViewZone(d, 12); + modViewZones.push(modViewZone); + store.add(new CompactCollapsedCodeOverlayWidget( + this._editors.modified, + modViewZone, + r, + )); + } + } else { + { + const d = derived(this, reader => /** @description hiddenOriginalRangeStart */ r.getHiddenOriginalRange(reader).startLineNumber - 1); + const origVz = new PlaceholderViewZone(d, 24); + origViewZones.push(origVz); + store.add(new CollapsedCodeOverlayWidget( + this._editors.original, + origVz, + r, + r.originalUnchangedRange, + !sideBySide, + modifiedOutlineSource, + l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, RevealPreference.FromBottom, undefined), + this._options, + )); + } + { + const d = derived(this, reader => /** @description hiddenModifiedRangeStart */ r.getHiddenModifiedRange(reader).startLineNumber - 1); + const modViewZone = new PlaceholderViewZone(d, 24); + modViewZones.push(modViewZone); + store.add(new CollapsedCodeOverlayWidget( + this._editors.modified, + modViewZone, + r, + r.modifiedUnchangedRange, + false, + modifiedOutlineSource, + l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, RevealPreference.FromBottom, undefined), + this._options, + )); + } } } @@ -228,6 +267,39 @@ export class HideUnchangedRegionsFeature extends Disposable { } } +class CompactCollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { + private readonly _nodes = h('div.diff-hidden-lines-compact', [ + h('div.line-left', []), + h('div.text@text', []), + h('div.line-right', []) + ]); + + constructor( + editor: ICodeEditor, + _viewZone: PlaceholderViewZone, + private readonly _unchangedRegion: UnchangedRegion, + private readonly _hide: boolean = false, + ) { + const root = h('div.diff-hidden-lines-widget'); + super(editor, _viewZone, root.root); + root.root.appendChild(this._nodes.root); + + if (this._hide) { + this._nodes.root.replaceChildren(); + } + + this._register(autorun(reader => { + /** @description update labels */ + + if (!this._hide) { + const lineCount = this._unchangedRegion.getHiddenModifiedRange(reader).length; + const linesHiddenText = localize('hiddenLines', '{0} hidden lines', lineCount); + this._nodes.text.innerText = linesHiddenText; + } + })); + } +} + class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { private readonly _nodes = h('div.diff-hidden-lines', [ h('div.top@top', { title: localize('diff.hiddenLines.top', 'Click or drag to show more above') }), @@ -255,12 +327,8 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { super(_editor, _viewZone, root.root); root.root.appendChild(this._nodes.root); - const layoutInfo = observableFromEvent(this._editor.onDidLayoutChange, () => - this._editor.getLayoutInfo() - ); - if (!this._hide) { - this._register(applyStyle(this._nodes.first, { width: layoutInfo.map((l) => l.contentLeft) })); + this._register(applyStyle(this._nodes.first, { width: observableCodeEditor(this._editor).layoutInfoContentLeft })); } else { reset(this._nodes.first); } diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts index 7d0d3a99..afb29693 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts @@ -26,8 +26,8 @@ export class MovedBlocksLinesFeature extends Disposable { public static readonly movedCodeBlockPadding = 4; private readonly _element: SVGElement; - private readonly _originalScrollTop = observableFromEvent(this._editors.original.onDidScrollChange, () => this._editors.original.getScrollTop()); - private readonly _modifiedScrollTop = observableFromEvent(this._editors.modified.onDidScrollChange, () => this._editors.modified.getScrollTop()); + private readonly _originalScrollTop = observableFromEvent(this, this._editors.original.onDidScrollChange, () => this._editors.original.getScrollTop()); + private readonly _modifiedScrollTop = observableFromEvent(this, this._editors.modified.onDidScrollChange, () => this._editors.modified.getScrollTop()); private readonly _viewZonesChanged = observableSignalFromEvent('onDidChangeViewZones', this._editors.modified.onDidChangeViewZones); public readonly width = observableValue(this, 0); diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts index 8141cd94..017d8268 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts @@ -23,7 +23,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; export class OverviewRulerFeature extends Disposable { private static readonly ONE_OVERVIEW_WIDTH = 15; - public static readonly ENTIRE_DIFF_OVERVIEW_WIDTH = OverviewRulerFeature.ONE_OVERVIEW_WIDTH * 2; + public static readonly ENTIRE_DIFF_OVERVIEW_WIDTH = this.ONE_OVERVIEW_WIDTH * 2; public readonly width = OverviewRulerFeature.ENTIRE_DIFF_OVERVIEW_WIDTH; constructor( diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/registrations.contribution.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/registrations.contribution.ts index 36bd4d46..80b553bc 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/registrations.contribution.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/registrations.contribution.ts @@ -12,13 +12,13 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export const diffMoveBorder = registerColor( 'diffEditor.move.border', - { dark: '#8b8b8b9c', light: '#8b8b8b9c', hcDark: '#8b8b8b9c', hcLight: '#8b8b8b9c', }, + '#8b8b8b9c', localize('diffEditor.move.border', 'The border color for text that got moved in the diff editor.') ); export const diffMoveBorderActive = registerColor( 'diffEditor.moveActive.border', - { dark: '#FFA500', light: '#FFA500', hcDark: '#FFA500', hcLight: '#FFA500', }, + '#FFA500', localize('diffEditor.moveActive.border', 'The active border color for text that got moved in the diff editor.') ); diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/style.css b/patched-vscode/src/vs/editor/browser/widget/diffEditor/style.css index ebb52234..4489d84b 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/style.css +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/style.css @@ -276,10 +276,14 @@ background-color: var(--vscode-diffEditorGutter-insertedLineBackground, var(--vscode-diffEditor-insertedLineBackground), var(--vscode-diffEditor-insertedTextBackground)); } -.monaco-editor .char-delete, .monaco-diff-editor .char-delete { +.monaco-editor .char-delete, .monaco-diff-editor .char-delete, .monaco-editor .inline-deleted-text { background-color: var(--vscode-diffEditor-removedTextBackground); } +.monaco-editor .inline-deleted-text { + text-decoration: line-through; +} + .monaco-editor .line-delete, .monaco-diff-editor .line-delete { background-color: var(--vscode-diffEditor-removedLineBackground, var(--vscode-diffEditor-removedTextBackground)); } @@ -395,3 +399,29 @@ } } } + + +.monaco-diff-editor .diff-hidden-lines-compact { + display: flex; + height: 11px; + .line-left, .line-right { + height: 1px; + border-top: 1px solid; + border-color: var(--vscode-editorCodeLens-foreground); + opacity: 0.5; + margin: auto; + width: 100%; + } + + .line-left { + width: 20px; + } + + .text { + color: var(--vscode-editorCodeLens-foreground); + text-wrap: nowrap; + font-size: 11px; + line-height: 11px; + margin: 0 4px; + } +} diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/utils.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/utils.ts index 76c37c41..ed24ac50 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -6,9 +6,8 @@ import { IDimension } from 'vs/base/browser/dom'; import { findLast } from 'vs/base/common/arraysFind'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { isHotReloadEnabled, registerHotReloadHandler } from 'vs/base/common/hotReload'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, IReader, ISettableObservable, autorun, autorunHandleChanges, autorunOpts, autorunWithStore, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, ISettableObservable, autorun, autorunHandleChanges, autorunOpts, autorunWithStore, observableValue, transaction } from 'vs/base/common/observable'; import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; import { ICodeEditor, IOverlayWidget, IViewZone } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; @@ -76,14 +75,14 @@ export function applyObservableDecorations(editor: ICodeEditor, decorations: IOb export function appendRemoveOnDispose(parent: HTMLElement, child: HTMLElement) { parent.appendChild(child); return toDisposable(() => { - parent.removeChild(child); + child.remove(); }); } export function prependRemoveOnDispose(parent: HTMLElement, child: HTMLElement) { parent.prepend(child); return toDisposable(() => { - parent.removeChild(child); + child.remove(); }); } @@ -298,29 +297,6 @@ export function applyStyle(domNode: HTMLElement, style: Partial<{ [TKey in keyof }); } -export function readHotReloadableExport(value: T, reader: IReader | undefined): T { - observeHotReloadableExports([value], reader); - return value; -} - -export function observeHotReloadableExports(values: any[], reader: IReader | undefined): void { - if (isHotReloadEnabled()) { - const o = observableSignalFromEvent( - 'reload', - event => registerHotReloadHandler(({ oldExports }) => { - if (![...Object.values(oldExports)].some(v => values.includes(v))) { - return undefined; - } - return (_newExports) => { - event(undefined); - return true; - }; - }) - ); - o.read(reader); - } -} - export function applyViewZones(editor: ICodeEditor, viewZones: IObservable, setIsUpdating?: (isUpdatingViewZones: boolean) => void, zoneIds?: Set): IDisposable { const store = new DisposableStore(); const lastViewZoneIds: string[] = []; @@ -439,3 +415,104 @@ export function filterWithPrevious(arr: T[], filter: (cur: T, prev: T | undef return result; }); } + +export interface IRefCounted extends IDisposable { + createNewRef(): this; +} + +export abstract class RefCounted implements IDisposable, IReference { + public static create(value: T, debugOwner: object | undefined = undefined): RefCounted { + return new BaseRefCounted(value, value, debugOwner); + } + + public static createWithDisposable(value: T, disposable: IDisposable, debugOwner: object | undefined = undefined): RefCounted { + const store = new DisposableStore(); + store.add(disposable); + store.add(value); + return new BaseRefCounted(value, store, debugOwner); + } + + public static createOfNonDisposable(value: T, disposable: IDisposable, debugOwner: object | undefined = undefined): RefCounted { + return new BaseRefCounted(value, disposable, debugOwner); + } + + public abstract createNewRef(debugOwner?: object | undefined): RefCounted; + + public abstract dispose(): void; + + public abstract get object(): T; +} + +class BaseRefCounted extends RefCounted { + private _refCount = 1; + private _isDisposed = false; + private readonly _owners: object[] = []; + + constructor( + public override readonly object: T, + private readonly _disposable: IDisposable, + private readonly _debugOwner: object | undefined, + ) { + super(); + + if (_debugOwner) { + this._addOwner(_debugOwner); + } + } + + private _addOwner(debugOwner: object | undefined) { + if (debugOwner) { + this._owners.push(debugOwner); + } + } + + public createNewRef(debugOwner?: object | undefined): RefCounted { + this._refCount++; + if (debugOwner) { + this._addOwner(debugOwner); + } + return new ClonedRefCounted(this, debugOwner); + } + + public dispose(): void { + if (this._isDisposed) { return; } + this._isDisposed = true; + this._decreaseRefCount(this._debugOwner); + } + + public _decreaseRefCount(debugOwner?: object | undefined): void { + this._refCount--; + if (this._refCount === 0) { + this._disposable.dispose(); + } + + if (debugOwner) { + const idx = this._owners.indexOf(debugOwner); + if (idx !== -1) { + this._owners.splice(idx, 1); + } + } + } +} + +class ClonedRefCounted extends RefCounted { + private _isDisposed = false; + constructor( + private readonly _base: BaseRefCounted, + private readonly _debugOwner: object | undefined, + ) { + super(); + } + + public get object(): T { return this._base.object; } + + public createNewRef(debugOwner?: object | undefined): RefCounted { + return this._base.createNewRef(debugOwner); + } + + public dispose(): void { + if (this._isDisposed) { return; } + this._isDisposed = true; + this._base._decreaseRefCount(this._debugOwner); + } +} diff --git a/patched-vscode/src/vs/editor/browser/widget/diffEditor/utils/editorGutter.ts b/patched-vscode/src/vs/editor/browser/widget/diffEditor/utils/editorGutter.ts index 1c3341a7..3d76c01e 100644 --- a/patched-vscode/src/vs/editor/browser/widget/diffEditor/utils/editorGutter.ts +++ b/patched-vscode/src/vs/editor/browser/widget/diffEditor/utils/editorGutter.ts @@ -11,12 +11,12 @@ import { LineRange } from 'vs/editor/common/core/lineRange'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; export class EditorGutter extends Disposable { - private readonly scrollTop = observableFromEvent( + private readonly scrollTop = observableFromEvent(this, this._editor.onDidScrollChange, (e) => /** @description editor.onDidScrollChange */ this._editor.getScrollTop() ); private readonly isScrollTopZero = this.scrollTop.map((scrollTop) => /** @description isScrollTopZero */ scrollTop === 0); - private readonly modelAttached = observableFromEvent( + private readonly modelAttached = observableFromEvent(this, this._editor.onDidChangeModel, (e) => /** @description editor.onDidChangeModel */ this._editor.hasModel() ); @@ -118,10 +118,10 @@ export class EditorGutter extends D gutterItem.range.startLineNumber <= this._editor.getModel()!.getLineCount() ? this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, true) - scrollTop : this._editor.getBottomForLineNumber(gutterItem.range.startLineNumber - 1, false) - scrollTop; - const bottom = gutterItem.range.isEmpty - // Don't trust that `getBottomForLineNumber` for the previous line equals `getTopForLineNumber` for the current one. - ? top - : (this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop); + const bottom = + gutterItem.range.endLineNumberExclusive === 1 ? + Math.max(top, this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, false) - scrollTop) + : Math.max(top, this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop); const height = bottom - top; view.domNode.style.top = `${top}px`; @@ -136,7 +136,7 @@ export class EditorGutter extends D for (const id of unusedIds) { const view = this.views.get(id)!; view.gutterItemView.dispose(); - this._domNode.removeChild(view.domNode); + view.domNode.remove(); this.views.delete(id); } } diff --git a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/colors.ts b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/colors.ts index d58781aa..c9e2cca5 100644 --- a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/colors.ts +++ b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/colors.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, editorBackground } from 'vs/platform/theme/common/colorRegistry'; export const multiDiffEditorHeaderBackground = registerColor( 'multiDiffEditor.headerBackground', @@ -14,7 +14,7 @@ export const multiDiffEditorHeaderBackground = registerColor( export const multiDiffEditorBackground = registerColor( 'multiDiffEditor.background', - { dark: 'editorBackground', light: 'editorBackground', hcDark: 'editorBackground', hcLight: 'editorBackground', }, + editorBackground, localize('multiDiffEditor.background', 'The background color of the multi file diff editor') ); diff --git a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 7ea85f66..d9b72607 100644 --- a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -6,20 +6,22 @@ import { h } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; -import { IObservable, globalTransaction, observableValue } from 'vs/base/common/observableInternal/base'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { autorun, derived } from 'vs/base/common/observable'; +import { globalTransaction, observableValue } from 'vs/base/common/observableInternal/base'; +import { observableCodeEditor } from 'vs/editor/browser/observableCodeEditor'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { DocumentDiffItemViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IObjectData, IPooledObject } from './objectPool'; import { ActionRunnerWithContext } from './utils'; -import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IContextKeyService, type IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; export class TemplateData implements IObjectData { constructor( @@ -81,8 +83,8 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< overflowWidgetsDomNode: this._overflowWidgetsDomNode, }, {})); - private readonly isModifedFocused = isFocused(this.editor.getModifiedEditor()); - private readonly isOriginalFocused = isFocused(this.editor.getOriginalEditor()); + private readonly isModifedFocused = observableCodeEditor(this.editor.getModifiedEditor()).isFocused; + private readonly isOriginalFocused = observableCodeEditor(this.editor.getOriginalEditor()).isFocused; public readonly isFocused = derived(this, reader => this.isModifedFocused.read(reader) || this.isOriginalFocused.read(reader)); private readonly _resourceLabel = this._workbenchUIElementFactory.createResourceLabel @@ -94,12 +96,14 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< : undefined; private readonly _outerEditorHeight: number; + private readonly _contextKeyService: IScopedContextKeyService; constructor( private readonly _container: HTMLElement, private readonly _overflowWidgetsDomNode: HTMLElement, private readonly _workbenchUIElementFactory: IWorkbenchUIElementFactory, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IContextKeyService _parentContextKeyService: IContextKeyService, ) { super(); @@ -155,13 +159,15 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._container.appendChild(this._elements.root); this._outerEditorHeight = this._headerHeight; - this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.actions, MenuId.MultiDiffEditorFileToolbar, { + this._contextKeyService = this._register(_parentContextKeyService.createScoped(this._elements.actions)); + const instantiationService = this._register(this._instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService]))); + this._register(instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.actions, MenuId.MultiDiffEditorFileToolbar, { actionRunner: this._register(new ActionRunnerWithContext(() => (this._viewModel.get()?.modifiedUri))), menuOptions: { shouldForwardArgs: true, }, toolbarOptions: { primaryGroup: g => g.startsWith('navigation') }, - actionViewItemProvider: (action, options) => createActionViewItem(_instantiationService, action, options), + actionViewItemProvider: (action, options) => createActionViewItem(instantiationService, action, options), })); } @@ -173,11 +179,11 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< } } - private readonly _dataStore = new DisposableStore(); + private readonly _dataStore = this._register(new DisposableStore()); private _data: TemplateData | undefined; - public setData(data: TemplateData): void { + public setData(data: TemplateData | undefined): void { this._data = data; function updateOptions(options: IDiffEditorOptions): IDiffEditorOptions { return { @@ -198,13 +204,17 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }; } - const value = data.viewModel.entry.value!; // TODO - - if (value.onOptionsDidChange) { - this._dataStore.add(value.onOptionsDidChange(() => { - this.editor.updateOptions(updateOptions(value.options ?? {})); - })); + if (!data) { + globalTransaction(tx => { + this._viewModel.set(undefined, tx); + this.editor.setDiffModel(null, tx); + this._dataStore.clear(); + }); + return; } + + const value = data.viewModel.documentDiffItem; + globalTransaction(tx => { this._resourceLabel?.setUri(data.viewModel.modifiedUri ?? data.viewModel.originalUri!, { strikethrough: data.viewModel.modifiedUri === undefined }); @@ -231,9 +241,25 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._dataStore.clear(); this._viewModel.set(data.viewModel, tx); - this.editor.setModel(data.viewModel.diffEditorViewModel, tx); + this.editor.setDiffModel(data.viewModel.diffEditorViewModelRef, tx); this.editor.updateOptions(updateOptions(value.options ?? {})); }); + if (value.onOptionsDidChange) { + this._dataStore.add(value.onOptionsDidChange(() => { + this.editor.updateOptions(updateOptions(value.options ?? {})); + })); + } + data.viewModel.isAlive.recomputeInitiallyAndOnChange(this._dataStore, value => { + if (!value) { + this.setData(undefined); + } + }); + + if (data.viewModel.documentDiffItem.contextKeys) { + for (const [key, value] of Object.entries(data.viewModel.documentDiffItem.contextKeys)) { + this._contextKeyService.createKey(key, value); + } + } } private readonly _headerHeight = /*this._elements.header.clientHeight*/ 40; @@ -276,15 +302,3 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible } } - -function isFocused(editor: ICodeEditor): IObservable { - return observableFromEvent( - h => { - const store = new DisposableStore(); - store.add(editor.onDidFocusEditorWidget(() => h(true))); - store.add(editor.onDidBlurEditorWidget(() => h(false))); - return store; - }, - () => editor.hasTextFocus() - ); -} diff --git a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/model.ts b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/model.ts index de8f43e4..d3ee5972 100644 --- a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/model.ts +++ b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/model.ts @@ -4,37 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { Event, IValueWithChangeEvent } from 'vs/base/common/event'; +import { RefCounted } from 'vs/editor/browser/widget/diffEditor/utils'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; import { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; export interface IMultiDiffEditorModel { - readonly documents: IValueWithChangeEvent[]>; + readonly documents: IValueWithChangeEvent[]>; readonly contextKeys?: Record; } -export interface LazyPromise { - request(): Promise; - readonly value: T | undefined; - readonly onHasValueDidChange: Event; -} - -export class ConstLazyPromise implements LazyPromise { - public readonly onHasValueDidChange = Event.None; - - constructor( - private readonly _value: T - ) { } - - public request(): Promise { - return Promise.resolve(this._value); - } - - public get value(): T { - return this._value; - } -} - export interface IDocumentDiffItem { /** * undefined if the file was created. @@ -47,4 +26,5 @@ export interface IDocumentDiffItem { readonly modified: ITextModel | undefined; readonly options?: IDiffEditorOptions; readonly onOptionsDidChange?: Event; + readonly contextKeys?: Record; } diff --git a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts index a044eb43..30924647 100644 --- a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts +++ b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, ITransaction, derived, observableValue, transaction } from 'vs/base/common/observable'; import { constObservable, derivedObservableWithWritableCache, mapObservableArrayCached, observableFromValueWithChangeEvent } from 'vs/base/common/observableInternal/utils'; import { URI } from 'vs/base/common/uri'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; -import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditor/model'; +import { RefCounted } from 'vs/editor/browser/widget/diffEditor/utils'; +import { IDocumentDiffItem, IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditor/model'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Selection } from 'vs/editor/common/core/selection'; import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; @@ -18,9 +19,13 @@ import { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class MultiDiffEditorViewModel extends Disposable { - private readonly _documents = observableFromValueWithChangeEvent(this.model, this.model.documents); + private readonly _documents: IObservable[]> = observableFromValueWithChangeEvent(this.model, this.model.documents); - public readonly items = mapObservableArrayCached(this, this._documents, (d, store) => store.add(this._instantiationService.createInstance(DocumentDiffItemViewModel, d, this))) + public readonly items: IObservable = mapObservableArrayCached( + this, + this._documents, + (d, store) => store.add(this._instantiationService.createInstance(DocumentDiffItemViewModel, d, this)) + ) .recomputeInitiallyAndOnChange(this._store); public readonly focusedDiffItem = derived(this, reader => this.items.read(reader).find(i => i.isFocused.read(reader))); @@ -61,7 +66,13 @@ export class MultiDiffEditorViewModel extends Disposable { } export class DocumentDiffItemViewModel extends Disposable { - public readonly diffEditorViewModel: IDiffEditorViewModel; + /** + * The diff editor view model keeps its inner objects alive. + */ + public readonly diffEditorViewModelRef: RefCounted; + public get diffEditorViewModel(): IDiffEditorViewModel { + return this.diffEditorViewModelRef.object; + } public readonly collapsed = observableValue(this, false); public readonly lastTemplateData = observableValue<{ contentHeight: number; selections: Selection[] | undefined }>( @@ -69,8 +80,8 @@ export class DocumentDiffItemViewModel extends Disposable { { contentHeight: 500, selections: undefined, } ); - public get originalUri(): URI | undefined { return this.entry.value!.original?.uri; } - public get modifiedUri(): URI | undefined { return this.entry.value!.modified?.uri; } + public get originalUri(): URI | undefined { return this.documentDiffItem.original?.uri; } + public get modifiedUri(): URI | undefined { return this.documentDiffItem.modified?.uri; } public readonly isActive: IObservable = derived(this, reader => this._editorViewModel.activeDiffItem.read(reader) === this); @@ -81,14 +92,27 @@ export class DocumentDiffItemViewModel extends Disposable { this._isFocusedSource.set(source, tx); } + private readonly documentDiffItemRef: RefCounted; + public get documentDiffItem(): IDocumentDiffItem { + return this.documentDiffItemRef.object; + } + + public readonly isAlive = observableValue(this, true); + constructor( - public readonly entry: LazyPromise, + documentDiffItem: RefCounted, private readonly _editorViewModel: MultiDiffEditorViewModel, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IModelService private readonly _modelService: IModelService, ) { super(); + this._register(toDisposable(() => { + this.isAlive.set(false, undefined); + })); + + this.documentDiffItemRef = this._register(documentDiffItem.createNewRef(this)); + function updateOptions(options: IDiffEditorOptions): IDiffEditorOptions { return { ...options, @@ -98,20 +122,26 @@ export class DocumentDiffItemViewModel extends Disposable { }; } - const options = this._instantiationService.createInstance(DiffEditorOptions, updateOptions(this.entry.value!.options || {})); - if (this.entry.value!.onOptionsDidChange) { - this._register(this.entry.value!.onOptionsDidChange(() => { - options.updateOptions(updateOptions(this.entry.value!.options || {})); + const options = this._instantiationService.createInstance(DiffEditorOptions, updateOptions(this.documentDiffItem.options || {})); + if (this.documentDiffItem.onOptionsDidChange) { + this._register(this.documentDiffItem.onOptionsDidChange(() => { + options.updateOptions(updateOptions(this.documentDiffItem.options || {})); })); } - const originalTextModel = this.entry.value!.original ?? this._register(this._modelService.createModel('', null)); - const modifiedTextModel = this.entry.value!.modified ?? this._register(this._modelService.createModel('', null)); - - this.diffEditorViewModel = this._register(this._instantiationService.createInstance(DiffEditorViewModel, { - original: originalTextModel, - modified: modifiedTextModel, - }, options)); + const diffEditorViewModelStore = new DisposableStore(); + const originalTextModel = this.documentDiffItem.original ?? diffEditorViewModelStore.add(this._modelService.createModel('', null)); + const modifiedTextModel = this.documentDiffItem.modified ?? diffEditorViewModelStore.add(this._modelService.createModel('', null)); + diffEditorViewModelStore.add(this.documentDiffItemRef.createNewRef(this)); + + this.diffEditorViewModelRef = this._register(RefCounted.createWithDisposable( + this._instantiationService.createInstance(DiffEditorViewModel, { + original: originalTextModel, + modified: modifiedTextModel, + }, options), + diffEditorViewModelStore, + this + )); } public getKey(): string { diff --git a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts index 496b0024..8275c4f7 100644 --- a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts +++ b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts @@ -6,8 +6,8 @@ import { Dimension } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; -import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; -import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditor/model'; +import { readHotReloadableExport } from 'vs/base/common/hotReloadHelpers'; +import { IDocumentDiffItem, IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditor/model'; import { IMultiDiffEditorViewState, IMultiDiffResourceId, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -81,6 +81,10 @@ export class MultiDiffEditorWidget extends Disposable { public tryGetCodeEditor(resource: URI): { diffEditor: IDiffEditor; editor: ICodeEditor } | undefined { return this._widgetImpl.get().tryGetCodeEditor(resource); } + + public findDocumentDiffItem(resource: URI): IDocumentDiffItem | undefined { + return this._widgetImpl.get().findDocumentDiffItem(resource); + } } export interface RevealOptions { diff --git a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index c29fc74b..a8a2f96f 100644 --- a/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts +++ b/patched-vscode/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -31,6 +31,7 @@ import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate'; import { DocumentDiffItemViewModel, MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { ObjectPool } from './objectPool'; import { localize } from 'vs/nls'; +import { IDocumentDiffItem } from 'vs/editor/browser/widget/multiDiffEditor/model'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _scrollableElements = h('div.scrollContent', [ @@ -73,8 +74,8 @@ export class MultiDiffEditorWidgetImpl extends Disposable { return template; })); - public readonly scrollTop = observableFromEvent(this._scrollableElement.onScroll, () => /** @description scrollTop */ this._scrollableElement.getScrollPosition().scrollTop); - public readonly scrollLeft = observableFromEvent(this._scrollableElement.onScroll, () => /** @description scrollLeft */ this._scrollableElement.getScrollPosition().scrollLeft); + public readonly scrollTop = observableFromEvent(this, this._scrollableElement.onScroll, () => /** @description scrollTop */ this._scrollableElement.getScrollPosition().scrollTop); + public readonly scrollLeft = observableFromEvent(this, this._scrollableElement.onScroll, () => /** @description scrollLeft */ this._scrollableElement.getScrollPosition().scrollLeft); private readonly _viewItemsInfo = derivedWithStore<{ items: readonly VirtualizedViewItem[]; getItem: (viewModel: DocumentDiffItemViewModel) => VirtualizedViewItem }>(this, (reader, store) => { @@ -263,6 +264,14 @@ export class MultiDiffEditorWidgetImpl extends Disposable { }); } + public findDocumentDiffItem(resource: URI): IDocumentDiffItem | undefined { + const item = this._viewItems.get().find(v => + v.viewModel.diffEditorViewModel.model.modified.uri.toString() === resource.toString() + || v.viewModel.diffEditorViewModel.model.original.uri.toString() === resource.toString() + ); + return item?.viewModel.documentDiffItem; + } + public tryGetCodeEditor(resource: URI): { diffEditor: IDiffEditor; editor: ICodeEditor } | undefined { const item = this._viewItems.get().find(v => v.viewModel.diffEditorViewModel.model.modified.uri.toString() === resource.toString() @@ -272,6 +281,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { if (!editor) { return undefined; } + if (item.viewModel.diffEditorViewModel.model.modified.uri.toString() === resource.toString()) { return { diffEditor: editor, editor: editor.getModifiedEditor() }; } else { @@ -396,7 +406,7 @@ class VirtualizedViewItem extends Disposable { } public override toString(): string { - return `VirtualViewItem(${this.viewModel.entry.value!.modified?.uri.toString()})`; + return `VirtualViewItem(${this.viewModel.documentDiffItem.modified?.uri.toString()})`; } public getKey(): string { diff --git a/patched-vscode/src/vs/editor/common/config/diffEditor.ts b/patched-vscode/src/vs/editor/common/config/diffEditor.ts index 2d0a3123..ff97a530 100644 --- a/patched-vscode/src/vs/editor/common/config/diffEditor.ts +++ b/patched-vscode/src/vs/editor/common/config/diffEditor.ts @@ -24,6 +24,7 @@ export const diffEditorDefaultOptions = { experimental: { showMoves: false, showEmptyDecorations: true, + useTrueInlineView: false, }, hideUnchangedRegions: { enabled: false, @@ -35,4 +36,5 @@ export const diffEditorDefaultOptions = { onlyShowAccessibleDiffViewer: false, renderSideBySideInlineBreakpoint: 900, useInlineViewWhenSpaceIsLimited: true, + compactMode: false, } satisfies ValidDiffEditorBaseOptions; diff --git a/patched-vscode/src/vs/editor/common/config/editorConfigurationSchema.ts b/patched-vscode/src/vs/editor/common/config/editorConfigurationSchema.ts index ab2b8cc7..ea51e836 100644 --- a/patched-vscode/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/patched-vscode/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -94,7 +94,7 @@ const editorConfiguration: IConfigurationNode = { }, 'editor.experimental.asyncTokenization': { type: 'boolean', - default: false, + default: true, description: nls.localize('editor.experimental.asyncTokenization', "Controls whether the tokenization should happen asynchronously on a web worker."), tags: ['experimental'], }, @@ -109,6 +109,12 @@ const editorConfiguration: IConfigurationNode = { description: nls.localize('editor.experimental.asyncTokenizationVerification', "Controls whether async tokenization should be verified against legacy background tokenization. Might slow down tokenization. For debugging only."), tags: ['experimental'], }, + 'editor.experimental.treeSitterTelemetry': { + type: 'boolean', + default: false, + markdownDescription: nls.localize('editor.experimental.treeSitterTelemetry', "Controls whether tree sitter parsing should be turned on and telemetry collected. Setting `editor.experimental.preferTreeSitter` for specific languages will take precedence."), + tags: ['experimental'] + }, 'editor.language.brackets': { type: ['array', 'null'], default: null, // We want to distinguish the empty array from not configured. @@ -247,7 +253,12 @@ const editorConfiguration: IConfigurationNode = { type: 'boolean', default: diffEditorDefaultOptions.experimental.showEmptyDecorations, description: nls.localize('showEmptyDecorations', "Controls whether the diff editor shows empty decorations to see where characters got inserted or deleted."), - } + }, + 'diffEditor.experimental.useTrueInlineView': { + type: 'boolean', + default: diffEditorDefaultOptions.experimental.useTrueInlineView, + description: nls.localize('useTrueInlineView', "If enabled and the editor uses the inline view, word changes are rendered inline."), + }, } }; diff --git a/patched-vscode/src/vs/editor/common/config/editorOptions.ts b/patched-vscode/src/vs/editor/common/config/editorOptions.ts index be4de9dc..0c099aa0 100644 --- a/patched-vscode/src/vs/editor/common/config/editorOptions.ts +++ b/patched-vscode/src/vs/editor/common/config/editorOptions.ts @@ -692,6 +692,13 @@ export interface IEditorOptions { * Defaults to false. */ peekWidgetDefaultFocus?: 'tree' | 'editor'; + + /** + * Sets a placeholder for the editor. + * If set, the placeholder is shown if the editor is empty. + */ + placeholder?: string | undefined; + /** * Controls whether the definition link opens element in the peek widget. * Defaults to false. @@ -764,75 +771,96 @@ export interface IDiffEditorBaseOptions { * Defaults to true. */ enableSplitViewResizing?: boolean; + /** * The default ratio when rendering side-by-side editors. * Must be a number between 0 and 1, min sizes apply. * Defaults to 0.5 */ splitViewDefaultRatio?: number; + /** * Render the differences in two side-by-side editors. * Defaults to true. */ renderSideBySide?: boolean; + /** * When `renderSideBySide` is enabled, `useInlineViewWhenSpaceIsLimited` is set, * and the diff editor has a width less than `renderSideBySideInlineBreakpoint`, the inline view is used. */ renderSideBySideInlineBreakpoint?: number | undefined; + /** * When `renderSideBySide` is enabled, `useInlineViewWhenSpaceIsLimited` is set, * and the diff editor has a width less than `renderSideBySideInlineBreakpoint`, the inline view is used. */ useInlineViewWhenSpaceIsLimited?: boolean; + + /** + * If set, the diff editor is optimized for small views. + * Defaults to `false`. + */ + compactMode?: boolean; + /** * Timeout in milliseconds after which diff computation is cancelled. * Defaults to 5000. */ maxComputationTime?: number; + /** * Maximum supported file size in MB. * Defaults to 50. */ maxFileSize?: number; + /** * Compute the diff by ignoring leading/trailing whitespace * Defaults to true. */ ignoreTrimWhitespace?: boolean; + /** * Render +/- indicators for added/deleted changes. * Defaults to true. */ renderIndicators?: boolean; + /** * Shows icons in the glyph margin to revert changes. * Default to true. */ renderMarginRevertIcon?: boolean; + /** * Indicates if the gutter menu should be rendered. */ renderGutterMenu?: boolean; + /** * Original model should be editable? * Defaults to false. */ originalEditable?: boolean; + /** * Should the diff editor enable code lens? * Defaults to false. */ diffCodeLens?: boolean; + /** * Is the diff editor should render overview ruler * Defaults to true */ renderOverviewRuler?: boolean; + /** * Control the wrapping of the diff editor. */ diffWordWrap?: 'off' | 'on' | 'inherit'; + /** * Diff Algorithm */ @@ -850,6 +878,11 @@ export interface IDiffEditorBaseOptions { showMoves?: boolean; showEmptyDecorations?: boolean; + + /** + * Only applies when `renderSideBySide` is set to false. + */ + useTrueInlineView?: boolean; }; /** @@ -1909,12 +1942,14 @@ export interface IGotoLocationOptions { multipleDeclarations?: GoToLocationValues; multipleImplementations?: GoToLocationValues; multipleReferences?: GoToLocationValues; + multipleTests?: GoToLocationValues; alternativeDefinitionCommand?: string; alternativeTypeDefinitionCommand?: string; alternativeDeclarationCommand?: string; alternativeImplementationCommand?: string; alternativeReferenceCommand?: string; + alternativeTestsCommand?: string; } /** @@ -1932,11 +1967,13 @@ class EditorGoToLocation extends BaseEditorOption(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleImplementations: input.multipleImplementations ?? stringSet(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleReferences: input.multipleReferences ?? stringSet(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']), + multipleTests: input.multipleTests ?? stringSet(input.multipleTests, 'peek', ['peek', 'gotoAndPeek', 'goto']), alternativeDefinitionCommand: EditorStringOption.string(input.alternativeDefinitionCommand, this.defaultValue.alternativeDefinitionCommand), alternativeTypeDefinitionCommand: EditorStringOption.string(input.alternativeTypeDefinitionCommand, this.defaultValue.alternativeTypeDefinitionCommand), alternativeDeclarationCommand: EditorStringOption.string(input.alternativeDeclarationCommand, this.defaultValue.alternativeDeclarationCommand), alternativeImplementationCommand: EditorStringOption.string(input.alternativeImplementationCommand, this.defaultValue.alternativeImplementationCommand), alternativeReferenceCommand: EditorStringOption.string(input.alternativeReferenceCommand, this.defaultValue.alternativeReferenceCommand), + alternativeTestsCommand: EditorStringOption.string(input.alternativeTestsCommand, this.defaultValue.alternativeTestsCommand), }; } } @@ -2760,7 +2799,7 @@ export type EditorLightbulbOptions = Readonly> class EditorLightbulb extends BaseEditorOption { constructor() { - const defaults: EditorLightbulbOptions = { enabled: ShowLightbulbIconMode.On }; + const defaults: EditorLightbulbOptions = { enabled: ShowLightbulbIconMode.OnCode }; super( EditorOption.lightbulb, 'lightbulb', defaults, { @@ -3347,6 +3386,25 @@ class EditorPixelRatio extends ComputedEditorOption { + constructor() { + super(EditorOption.placeholder, 'placeholder', undefined); + } + + public validate(input: any): string | undefined { + if (typeof input === 'undefined') { + return this.defaultValue; + } + if (typeof input === 'string') { + return input; + } + return this.defaultValue; + } +} +//#endregion + //#region quickSuggestions export type QuickSuggestionsValue = 'on' | 'inline' | 'off'; @@ -3405,7 +3463,7 @@ class EditorQuickSuggestions extends BaseEditorOption lines[lineNumber - 1], + lines.length + ); + } +} + export class StringText extends AbstractText { private readonly _t = new PositionOffsetTransformer(this.value); diff --git a/patched-vscode/src/vs/editor/common/cursor/cursor.ts b/patched-vscode/src/vs/editor/common/cursor/cursor.ts index 4df16ec8..242283e7 100644 --- a/patched-vscode/src/vs/editor/common/cursor/cursor.ts +++ b/patched-vscode/src/vs/editor/common/cursor/cursor.ts @@ -10,7 +10,8 @@ import { CursorConfiguration, CursorState, EditOperationResult, EditOperationTyp import { CursorContext } from 'vs/editor/common/cursor/cursorContext'; import { DeleteOperations } from 'vs/editor/common/cursor/cursorDeleteOperations'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; -import { CompositionOutcome, TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/cursor/cursorTypeOperations'; +import { CompositionOutcome, TypeOperations } from 'vs/editor/common/cursor/cursorTypeOperations'; +import { BaseTypeWithAutoClosingCommand } from 'vs/editor/common/cursor/cursorTypeEditOperations'; import { Position } from 'vs/editor/common/core/position'; import { Range, IRange } from 'vs/editor/common/core/range'; import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; @@ -367,7 +368,7 @@ export class CursorsController extends Disposable { for (let i = 0; i < opResult.commands.length; i++) { const command = opResult.commands[i]; - if (command instanceof TypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) { + if (command instanceof BaseTypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) { autoClosedCharactersRanges.push(command.closeCharacterRange); autoClosedEnclosingRanges.push(command.enclosingRange); } @@ -739,7 +740,7 @@ interface ICommandsData { hadTrackedEditOperation: boolean; } -class CommandExecutor { +export class CommandExecutor { public static executeCommands(model: ITextModel, selectionsBefore: Selection[], commands: (editorCommon.ICommand | null)[]): Selection[] | null { diff --git a/patched-vscode/src/vs/editor/common/cursor/cursorTypeEditOperations.ts b/patched-vscode/src/vs/editor/common/cursor/cursorTypeEditOperations.ts new file mode 100644 index 00000000..df17d2f3 --- /dev/null +++ b/patched-vscode/src/vs/editor/common/cursor/cursorTypeEditOperations.ts @@ -0,0 +1,1030 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CharCode } from 'vs/base/common/charCode'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import * as strings from 'vs/base/common/strings'; +import { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand'; +import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand'; +import { SurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand'; +import { CursorConfiguration, EditOperationResult, EditOperationType, ICursorSimpleModel, isQuote } from 'vs/editor/common/cursorCommon'; +import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/core/wordCharacterClassifier'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { Position } from 'vs/editor/common/core/position'; +import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; +import { ITextModel } from 'vs/editor/common/model'; +import { EnterAction, IndentAction, StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; +import { getIndentationAtPosition } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { IElectricAction } from 'vs/editor/common/languages/supports/electricCharacter'; +import { EditorAutoClosingStrategy, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; +import { createScopedLineTokens } from 'vs/editor/common/languages/supports'; +import { getIndentActionForType, getIndentForEnter, getInheritIndentForLine } from 'vs/editor/common/languages/autoIndent'; +import { getEnterAction } from 'vs/editor/common/languages/enterAction'; + +export class AutoIndentOperation { + + public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, isDoingComposition: boolean): EditOperationResult | undefined { + if (!isDoingComposition && this._isAutoIndentType(config, model, selections)) { + const indentationForSelections: { selection: Selection; indentation: string }[] = []; + for (const selection of selections) { + const indentation = this._findActualIndentationForSelection(config, model, selection, ch); + if (indentation === null) { + // Auto indentation failed + return; + } + indentationForSelections.push({ selection, indentation }); + } + const autoClosingPairClose = AutoClosingOpenCharTypeOperation.getAutoClosingPairClose(config, model, selections, ch, false); + return this._getIndentationAndAutoClosingPairEdits(config, model, indentationForSelections, ch, autoClosingPairClose); + } + return; + } + + private static _isAutoIndentType(config: CursorConfiguration, model: ITextModel, selections: Selection[]): boolean { + if (config.autoIndent < EditorAutoIndentStrategy.Full) { + return false; + } + for (let i = 0, len = selections.length; i < len; i++) { + if (!model.tokenization.isCheapToTokenize(selections[i].getEndPosition().lineNumber)) { + return false; + } + } + return true; + } + + private static _findActualIndentationForSelection(config: CursorConfiguration, model: ITextModel, selection: Selection, ch: string): string | null { + const actualIndentation = getIndentActionForType(config, model, selection, ch, { + shiftIndent: (indentation) => { + return shiftIndent(config, indentation); + }, + unshiftIndent: (indentation) => { + return unshiftIndent(config, indentation); + }, + }, config.languageConfigurationService); + + if (actualIndentation === null) { + return null; + } + + const currentIndentation = getIndentationAtPosition(model, selection.startLineNumber, selection.startColumn); + if (actualIndentation === config.normalizeIndentation(currentIndentation)) { + return null; + } + return actualIndentation; + } + + private static _getIndentationAndAutoClosingPairEdits(config: CursorConfiguration, model: ITextModel, indentationForSelections: { selection: Selection; indentation: string }[], ch: string, autoClosingPairClose: string | null): EditOperationResult { + const commands: ICommand[] = indentationForSelections.map(({ selection, indentation }) => { + if (autoClosingPairClose !== null) { + // Apply both auto closing pair edits and auto indentation edits + const indentationEdit = this._getEditFromIndentationAndSelection(config, model, indentation, selection, ch, false); + return new TypeWithIndentationAndAutoClosingCommand(indentationEdit, selection, ch, autoClosingPairClose); + } else { + // Apply only auto indentation edits + const indentationEdit = this._getEditFromIndentationAndSelection(config, model, indentation, selection, ch, true); + return typeCommand(indentationEdit.range, indentationEdit.text, false); + } + }); + const editOptions = { shouldPushStackElementBefore: true, shouldPushStackElementAfter: false }; + return new EditOperationResult(EditOperationType.TypingOther, commands, editOptions); + } + + private static _getEditFromIndentationAndSelection(config: CursorConfiguration, model: ITextModel, indentation: string, selection: Selection, ch: string, includeChInEdit: boolean = true): { range: Range; text: string } { + const startLineNumber = selection.startLineNumber; + const firstNonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(startLineNumber); + let text: string = config.normalizeIndentation(indentation); + if (firstNonWhitespaceColumn !== 0) { + const startLine = model.getLineContent(startLineNumber); + text += startLine.substring(firstNonWhitespaceColumn - 1, selection.startColumn - 1); + } + text += includeChInEdit ? ch : ''; + const range = new Range(startLineNumber, 1, selection.endLineNumber, selection.endColumn); + return { range, text }; + } +} + +export class AutoClosingOvertypeOperation { + + public static getEdits(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult | undefined { + if (isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) { + return this._runAutoClosingOvertype(prevEditOperationType, selections, ch); + } + return; + } + + private static _runAutoClosingOvertype(prevEditOperationType: EditOperationType, selections: Selection[], ch: string): EditOperationResult { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + const selection = selections[i]; + const position = selection.getPosition(); + const typeSelection = new Range(position.lineNumber, position.column, position.lineNumber, position.column + 1); + commands[i] = new ReplaceCommand(typeSelection, ch); + } + return new EditOperationResult(EditOperationType.TypingOther, commands, { + shouldPushStackElementBefore: shouldPushStackElementBetween(prevEditOperationType, EditOperationType.TypingOther), + shouldPushStackElementAfter: false + }); + } +} + +export class AutoClosingOvertypeWithInterceptorsOperation { + + public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult | undefined { + if (isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) { + // Unfortunately, the close character is at this point "doubled", so we need to delete it... + const commands = selections.map(s => new ReplaceCommand(new Range(s.positionLineNumber, s.positionColumn, s.positionLineNumber, s.positionColumn + 1), '', false)); + return new EditOperationResult(EditOperationType.TypingOther, commands, { + shouldPushStackElementBefore: true, + shouldPushStackElementAfter: false + }); + } + return; + } +} + +export class AutoClosingOpenCharTypeOperation { + + public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean, isDoingComposition: boolean): EditOperationResult | undefined { + if (!isDoingComposition) { + const autoClosingPairClose = this.getAutoClosingPairClose(config, model, selections, ch, chIsAlreadyTyped); + if (autoClosingPairClose !== null) { + return this._runAutoClosingOpenCharType(selections, ch, chIsAlreadyTyped, autoClosingPairClose); + } + } + return; + } + + private static _runAutoClosingOpenCharType(selections: Selection[], ch: string, chIsAlreadyTyped: boolean, autoClosingPairClose: string): EditOperationResult { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + const selection = selections[i]; + commands[i] = new TypeWithAutoClosingCommand(selection, ch, !chIsAlreadyTyped, autoClosingPairClose); + } + return new EditOperationResult(EditOperationType.TypingOther, commands, { + shouldPushStackElementBefore: true, + shouldPushStackElementAfter: false + }); + } + + public static getAutoClosingPairClose(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean): string | null { + for (const selection of selections) { + if (!selection.isEmpty()) { + return null; + } + } + // This method is called both when typing (regularly) and when composition ends + // This means that we need to work with a text buffer where sometimes `ch` is not + // there (it is being typed right now) or with a text buffer where `ch` has already been typed + // + // In order to avoid adding checks for `chIsAlreadyTyped` in all places, we will work + // with two conceptual positions, the position before `ch` and the position after `ch` + // + const positions: { lineNumber: number; beforeColumn: number; afterColumn: number }[] = selections.map((s) => { + const position = s.getPosition(); + if (chIsAlreadyTyped) { + return { lineNumber: position.lineNumber, beforeColumn: position.column - ch.length, afterColumn: position.column }; + } else { + return { lineNumber: position.lineNumber, beforeColumn: position.column, afterColumn: position.column }; + } + }); + // Find the longest auto-closing open pair in case of multiple ending in `ch` + // e.g. when having [f","] and [","], it picks [f","] if the character before is f + const pair = this._findAutoClosingPairOpen(config, model, positions.map(p => new Position(p.lineNumber, p.beforeColumn)), ch); + if (!pair) { + return null; + } + let autoCloseConfig: EditorAutoClosingStrategy; + let shouldAutoCloseBefore: (ch: string) => boolean; + + const chIsQuote = isQuote(ch); + if (chIsQuote) { + autoCloseConfig = config.autoClosingQuotes; + shouldAutoCloseBefore = config.shouldAutoCloseBefore.quote; + } else { + const pairIsForComments = config.blockCommentStartToken ? pair.open.includes(config.blockCommentStartToken) : false; + if (pairIsForComments) { + autoCloseConfig = config.autoClosingComments; + shouldAutoCloseBefore = config.shouldAutoCloseBefore.comment; + } else { + autoCloseConfig = config.autoClosingBrackets; + shouldAutoCloseBefore = config.shouldAutoCloseBefore.bracket; + } + } + if (autoCloseConfig === 'never') { + return null; + } + // Sometimes, it is possible to have two auto-closing pairs that have a containment relationship + // e.g. when having [(,)] and [(*,*)] + // - when typing (, the resulting state is (|) + // - when typing *, the desired resulting state is (*|*), not (*|*)) + const containedPair = this._findContainedAutoClosingPair(config, pair); + const containedPairClose = containedPair ? containedPair.close : ''; + let isContainedPairPresent = true; + + for (const position of positions) { + const { lineNumber, beforeColumn, afterColumn } = position; + const lineText = model.getLineContent(lineNumber); + const lineBefore = lineText.substring(0, beforeColumn - 1); + const lineAfter = lineText.substring(afterColumn - 1); + + if (!lineAfter.startsWith(containedPairClose)) { + isContainedPairPresent = false; + } + // Only consider auto closing the pair if an allowed character follows or if another autoclosed pair closing brace follows + if (lineAfter.length > 0) { + const characterAfter = lineAfter.charAt(0); + const isBeforeCloseBrace = this._isBeforeClosingBrace(config, lineAfter); + if (!isBeforeCloseBrace && !shouldAutoCloseBefore(characterAfter)) { + return null; + } + } + // Do not auto-close ' or " after a word character + if (pair.open.length === 1 && (ch === '\'' || ch === '"') && autoCloseConfig !== 'always') { + const wordSeparators = getMapForWordSeparators(config.wordSeparators, []); + if (lineBefore.length > 0) { + const characterBefore = lineBefore.charCodeAt(lineBefore.length - 1); + if (wordSeparators.get(characterBefore) === WordCharacterClass.Regular) { + return null; + } + } + } + if (!model.tokenization.isCheapToTokenize(lineNumber)) { + // Do not force tokenization + return null; + } + model.tokenization.forceTokenization(lineNumber); + const lineTokens = model.tokenization.getLineTokens(lineNumber); + const scopedLineTokens = createScopedLineTokens(lineTokens, beforeColumn - 1); + if (!pair.shouldAutoClose(scopedLineTokens, beforeColumn - scopedLineTokens.firstCharOffset)) { + return null; + } + // Typing for example a quote could either start a new string, in which case auto-closing is desirable + // or it could end a previously started string, in which case auto-closing is not desirable + // + // In certain cases, it is really not possible to look at the previous token to determine + // what would happen. That's why we do something really unusual, we pretend to type a different + // character and ask the tokenizer what the outcome of doing that is: after typing a neutral + // character, are we in a string (i.e. the quote would most likely end a string) or not? + // + const neutralCharacter = pair.findNeutralCharacter(); + if (neutralCharacter) { + const tokenType = model.tokenization.getTokenTypeIfInsertingCharacter(lineNumber, beforeColumn, neutralCharacter); + if (!pair.isOK(tokenType)) { + return null; + } + } + } + if (isContainedPairPresent) { + return pair.close.substring(0, pair.close.length - containedPairClose.length); + } else { + return pair.close; + } + } + + /** + * Find another auto-closing pair that is contained by the one passed in. + * + * e.g. when having [(,)] and [(*,*)] as auto-closing pairs + * this method will find [(,)] as a containment pair for [(*,*)] + */ + private static _findContainedAutoClosingPair(config: CursorConfiguration, pair: StandardAutoClosingPairConditional): StandardAutoClosingPairConditional | null { + if (pair.open.length <= 1) { + return null; + } + const lastChar = pair.close.charAt(pair.close.length - 1); + // get candidates with the same last character as close + const candidates = config.autoClosingPairs.autoClosingPairsCloseByEnd.get(lastChar) || []; + let result: StandardAutoClosingPairConditional | null = null; + for (const candidate of candidates) { + if (candidate.open !== pair.open && pair.open.includes(candidate.open) && pair.close.endsWith(candidate.close)) { + if (!result || candidate.open.length > result.open.length) { + result = candidate; + } + } + } + return result; + } + + /** + * Determine if typing `ch` at all `positions` in the `model` results in an + * auto closing open sequence being typed. + * + * Auto closing open sequences can consist of multiple characters, which + * can lead to ambiguities. In such a case, the longest auto-closing open + * sequence is returned. + */ + private static _findAutoClosingPairOpen(config: CursorConfiguration, model: ITextModel, positions: Position[], ch: string): StandardAutoClosingPairConditional | null { + const candidates = config.autoClosingPairs.autoClosingPairsOpenByEnd.get(ch); + if (!candidates) { + return null; + } + // Determine which auto-closing pair it is + let result: StandardAutoClosingPairConditional | null = null; + for (const candidate of candidates) { + if (result === null || candidate.open.length > result.open.length) { + let candidateIsMatch = true; + for (const position of positions) { + const relevantText = model.getValueInRange(new Range(position.lineNumber, position.column - candidate.open.length + 1, position.lineNumber, position.column)); + if (relevantText + ch !== candidate.open) { + candidateIsMatch = false; + break; + } + } + if (candidateIsMatch) { + result = candidate; + } + } + } + return result; + } + + private static _isBeforeClosingBrace(config: CursorConfiguration, lineAfter: string) { + // If the start of lineAfter can be interpretted as both a starting or ending brace, default to returning false + const nextChar = lineAfter.charAt(0); + const potentialStartingBraces = config.autoClosingPairs.autoClosingPairsOpenByStart.get(nextChar) || []; + const potentialClosingBraces = config.autoClosingPairs.autoClosingPairsCloseByStart.get(nextChar) || []; + + const isBeforeStartingBrace = potentialStartingBraces.some(x => lineAfter.startsWith(x.open)); + const isBeforeClosingBrace = potentialClosingBraces.some(x => lineAfter.startsWith(x.close)); + + return !isBeforeStartingBrace && isBeforeClosingBrace; + } +} + +export class SurroundSelectionOperation { + + public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, isDoingComposition: boolean): EditOperationResult | undefined { + if (!isDoingComposition && this._isSurroundSelectionType(config, model, selections, ch)) { + return this._runSurroundSelectionType(config, selections, ch); + } + return; + } + + private static _runSurroundSelectionType(config: CursorConfiguration, selections: Selection[], ch: string): EditOperationResult { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + const selection = selections[i]; + const closeCharacter = config.surroundingPairs[ch]; + commands[i] = new SurroundSelectionCommand(selection, ch, closeCharacter); + } + return new EditOperationResult(EditOperationType.Other, commands, { + shouldPushStackElementBefore: true, + shouldPushStackElementAfter: true + }); + } + + private static _isSurroundSelectionType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): boolean { + if (!shouldSurroundChar(config, ch) || !config.surroundingPairs.hasOwnProperty(ch)) { + return false; + } + const isTypingAQuoteCharacter = isQuote(ch); + for (const selection of selections) { + if (selection.isEmpty()) { + return false; + } + let selectionContainsOnlyWhitespace = true; + for (let lineNumber = selection.startLineNumber; lineNumber <= selection.endLineNumber; lineNumber++) { + const lineText = model.getLineContent(lineNumber); + const startIndex = (lineNumber === selection.startLineNumber ? selection.startColumn - 1 : 0); + const endIndex = (lineNumber === selection.endLineNumber ? selection.endColumn - 1 : lineText.length); + const selectedText = lineText.substring(startIndex, endIndex); + if (/[^ \t]/.test(selectedText)) { + // this selected text contains something other than whitespace + selectionContainsOnlyWhitespace = false; + break; + } + } + if (selectionContainsOnlyWhitespace) { + return false; + } + if (isTypingAQuoteCharacter && selection.startLineNumber === selection.endLineNumber && selection.startColumn + 1 === selection.endColumn) { + const selectionText = model.getValueInRange(selection); + if (isQuote(selectionText)) { + // Typing a quote character on top of another quote character + // => disable surround selection type + return false; + } + } + } + return true; + } +} + +export class InterceptorElectricCharOperation { + + public static getEdits(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, isDoingComposition: boolean): EditOperationResult | undefined { + // Electric characters make sense only when dealing with a single cursor, + // as multiple cursors typing brackets for example would interfer with bracket matching + if (!isDoingComposition && this._isTypeInterceptorElectricChar(config, model, selections)) { + const r = this._typeInterceptorElectricChar(prevEditOperationType, config, model, selections[0], ch); + if (r) { + return r; + } + } + return; + } + + private static _isTypeInterceptorElectricChar(config: CursorConfiguration, model: ITextModel, selections: Selection[]) { + if (selections.length === 1 && model.tokenization.isCheapToTokenize(selections[0].getEndPosition().lineNumber)) { + return true; + } + return false; + } + + private static _typeInterceptorElectricChar(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selection: Selection, ch: string): EditOperationResult | null { + if (!config.electricChars.hasOwnProperty(ch) || !selection.isEmpty()) { + return null; + } + const position = selection.getPosition(); + model.tokenization.forceTokenization(position.lineNumber); + const lineTokens = model.tokenization.getLineTokens(position.lineNumber); + let electricAction: IElectricAction | null; + try { + electricAction = config.onElectricCharacter(ch, lineTokens, position.column); + } catch (e) { + onUnexpectedError(e); + return null; + } + if (!electricAction) { + return null; + } + if (electricAction.matchOpenBracket) { + const endColumn = (lineTokens.getLineContent() + ch).lastIndexOf(electricAction.matchOpenBracket) + 1; + const match = model.bracketPairs.findMatchingBracketUp(electricAction.matchOpenBracket, { + lineNumber: position.lineNumber, + column: endColumn + }, 500 /* give at most 500ms to compute */); + if (match) { + if (match.startLineNumber === position.lineNumber) { + // matched something on the same line => no change in indentation + return null; + } + const matchLine = model.getLineContent(match.startLineNumber); + const matchLineIndentation = strings.getLeadingWhitespace(matchLine); + const newIndentation = config.normalizeIndentation(matchLineIndentation); + const lineText = model.getLineContent(position.lineNumber); + const lineFirstNonBlankColumn = model.getLineFirstNonWhitespaceColumn(position.lineNumber) || position.column; + const prefix = lineText.substring(lineFirstNonBlankColumn - 1, position.column - 1); + const typeText = newIndentation + prefix + ch; + const typeSelection = new Range(position.lineNumber, 1, position.lineNumber, position.column); + const command = new ReplaceCommand(typeSelection, typeText); + return new EditOperationResult(getTypingOperation(typeText, prevEditOperationType), [command], { + shouldPushStackElementBefore: false, + shouldPushStackElementAfter: true + }); + } + } + return null; + } +} + +export class SimpleCharacterTypeOperation { + + public static getEdits(prevEditOperationType: EditOperationType, selections: Selection[], ch: string): EditOperationResult { + // A simple character type + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + commands[i] = new ReplaceCommand(selections[i], ch); + } + + const opType = getTypingOperation(ch, prevEditOperationType); + return new EditOperationResult(opType, commands, { + shouldPushStackElementBefore: shouldPushStackElementBetween(prevEditOperationType, opType), + shouldPushStackElementAfter: false + }); + } +} + +export class EnterOperation { + + public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, isDoingComposition: boolean): EditOperationResult | undefined { + if (!isDoingComposition && ch === '\n') { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + commands[i] = this._enter(config, model, false, selections[i]); + } + return new EditOperationResult(EditOperationType.TypingOther, commands, { + shouldPushStackElementBefore: true, + shouldPushStackElementAfter: false, + }); + } + return; + } + + private static _enter(config: CursorConfiguration, model: ITextModel, keepPosition: boolean, range: Range): ICommand { + if (config.autoIndent === EditorAutoIndentStrategy.None) { + return typeCommand(range, '\n', keepPosition); + } + if (!model.tokenization.isCheapToTokenize(range.getStartPosition().lineNumber) || config.autoIndent === EditorAutoIndentStrategy.Keep) { + const lineText = model.getLineContent(range.startLineNumber); + const indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); + return typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); + } + const r = getEnterAction(config.autoIndent, model, range, config.languageConfigurationService); + if (r) { + if (r.indentAction === IndentAction.None) { + // Nothing special + return typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition); + + } else if (r.indentAction === IndentAction.Indent) { + // Indent once + return typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition); + + } else if (r.indentAction === IndentAction.IndentOutdent) { + // Ultra special + const normalIndent = config.normalizeIndentation(r.indentation); + const increasedIndent = config.normalizeIndentation(r.indentation + r.appendText); + const typeText = '\n' + increasedIndent + '\n' + normalIndent; + if (keepPosition) { + return new ReplaceCommandWithoutChangingPosition(range, typeText, true); + } else { + return new ReplaceCommandWithOffsetCursorState(range, typeText, -1, increasedIndent.length - normalIndent.length, true); + } + } else if (r.indentAction === IndentAction.Outdent) { + const actualIndentation = unshiftIndent(config, r.indentation); + return typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + r.appendText), keepPosition); + } + } + + const lineText = model.getLineContent(range.startLineNumber); + const indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); + + if (config.autoIndent >= EditorAutoIndentStrategy.Full) { + const ir = getIndentForEnter(config.autoIndent, model, range, { + unshiftIndent: (indent) => { + return unshiftIndent(config, indent); + }, + shiftIndent: (indent) => { + return shiftIndent(config, indent); + }, + normalizeIndentation: (indent) => { + return config.normalizeIndentation(indent); + } + }, config.languageConfigurationService); + + if (ir) { + let oldEndViewColumn = config.visibleColumnFromColumn(model, range.getEndPosition()); + const oldEndColumn = range.endColumn; + const newLineContent = model.getLineContent(range.endLineNumber); + const firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent); + if (firstNonWhitespace >= 0) { + range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1)); + } else { + range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber)); + } + if (keepPosition) { + return new ReplaceCommandWithoutChangingPosition(range, '\n' + config.normalizeIndentation(ir.afterEnter), true); + } else { + let offset = 0; + if (oldEndColumn <= firstNonWhitespace + 1) { + if (!config.insertSpaces) { + oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize); + } + offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); + } + return new ReplaceCommandWithOffsetCursorState(range, '\n' + config.normalizeIndentation(ir.afterEnter), 0, offset, true); + } + } + } + return typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); + } + + + public static lineInsertBefore(config: CursorConfiguration, model: ITextModel | null, selections: Selection[] | null): ICommand[] { + if (model === null || selections === null) { + return []; + } + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + let lineNumber = selections[i].positionLineNumber; + if (lineNumber === 1) { + commands[i] = new ReplaceCommandWithoutChangingPosition(new Range(1, 1, 1, 1), '\n'); + } else { + lineNumber--; + const column = model.getLineMaxColumn(lineNumber); + + commands[i] = this._enter(config, model, false, new Range(lineNumber, column, lineNumber, column)); + } + } + return commands; + } + + public static lineInsertAfter(config: CursorConfiguration, model: ITextModel | null, selections: Selection[] | null): ICommand[] { + if (model === null || selections === null) { + return []; + } + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + const lineNumber = selections[i].positionLineNumber; + const column = model.getLineMaxColumn(lineNumber); + commands[i] = this._enter(config, model, false, new Range(lineNumber, column, lineNumber, column)); + } + return commands; + } + + public static lineBreakInsert(config: CursorConfiguration, model: ITextModel, selections: Selection[]): ICommand[] { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + commands[i] = this._enter(config, model, true, selections[i]); + } + return commands; + } +} + +export class PasteOperation { + + public static getEdits(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string, pasteOnNewLine: boolean, multicursorText: string[]) { + const distributedPaste = this._distributePasteToCursors(config, selections, text, pasteOnNewLine, multicursorText); + if (distributedPaste) { + selections = selections.sort(Range.compareRangesUsingStarts); + return this._distributedPaste(config, model, selections, distributedPaste); + } else { + return this._simplePaste(config, model, selections, text, pasteOnNewLine); + } + } + + private static _distributePasteToCursors(config: CursorConfiguration, selections: Selection[], text: string, pasteOnNewLine: boolean, multicursorText: string[]): string[] | null { + if (pasteOnNewLine) { + return null; + } + if (selections.length === 1) { + return null; + } + if (multicursorText && multicursorText.length === selections.length) { + return multicursorText; + } + if (config.multiCursorPaste === 'spread') { + // Try to spread the pasted text in case the line count matches the cursor count + // Remove trailing \n if present + if (text.charCodeAt(text.length - 1) === CharCode.LineFeed) { + text = text.substring(0, text.length - 1); + } + // Remove trailing \r if present + if (text.charCodeAt(text.length - 1) === CharCode.CarriageReturn) { + text = text.substring(0, text.length - 1); + } + const lines = strings.splitLines(text); + if (lines.length === selections.length) { + return lines; + } + } + return null; + } + + private static _distributedPaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string[]): EditOperationResult { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + commands[i] = new ReplaceCommand(selections[i], text[i]); + } + return new EditOperationResult(EditOperationType.Other, commands, { + shouldPushStackElementBefore: true, + shouldPushStackElementAfter: true + }); + } + + private static _simplePaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string, pasteOnNewLine: boolean): EditOperationResult { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + const selection = selections[i]; + const position = selection.getPosition(); + if (pasteOnNewLine && !selection.isEmpty()) { + pasteOnNewLine = false; + } + if (pasteOnNewLine && text.indexOf('\n') !== text.length - 1) { + pasteOnNewLine = false; + } + if (pasteOnNewLine) { + // Paste entire line at the beginning of line + const typeSelection = new Range(position.lineNumber, 1, position.lineNumber, 1); + commands[i] = new ReplaceCommandThatPreservesSelection(typeSelection, text, selection, true); + } else { + commands[i] = new ReplaceCommand(selection, text); + } + } + return new EditOperationResult(EditOperationType.Other, commands, { + shouldPushStackElementBefore: true, + shouldPushStackElementAfter: true + }); + } +} + +export class CompositionOperation { + + public static getEdits(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number) { + const commands = selections.map(selection => this._compositionType(model, selection, text, replacePrevCharCnt, replaceNextCharCnt, positionDelta)); + return new EditOperationResult(EditOperationType.TypingOther, commands, { + shouldPushStackElementBefore: shouldPushStackElementBetween(prevEditOperationType, EditOperationType.TypingOther), + shouldPushStackElementAfter: false + }); + } + + private static _compositionType(model: ITextModel, selection: Selection, text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number): ICommand | null { + if (!selection.isEmpty()) { + // looks like https://github.com/microsoft/vscode/issues/2773 + // where a cursor operation occurred before a canceled composition + // => ignore composition + return null; + } + const pos = selection.getPosition(); + const startColumn = Math.max(1, pos.column - replacePrevCharCnt); + const endColumn = Math.min(model.getLineMaxColumn(pos.lineNumber), pos.column + replaceNextCharCnt); + const range = new Range(pos.lineNumber, startColumn, pos.lineNumber, endColumn); + const oldText = model.getValueInRange(range); + if (oldText === text && positionDelta === 0) { + // => ignore composition that doesn't do anything + return null; + } + return new ReplaceCommandWithOffsetCursorState(range, text, 0, positionDelta); + } +} + +export class TypeWithoutInterceptorsOperation { + + public static getEdits(prevEditOperationType: EditOperationType, selections: Selection[], str: string): EditOperationResult { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + commands[i] = new ReplaceCommand(selections[i], str); + } + const opType = getTypingOperation(str, prevEditOperationType); + return new EditOperationResult(opType, commands, { + shouldPushStackElementBefore: shouldPushStackElementBetween(prevEditOperationType, opType), + shouldPushStackElementAfter: false + }); + } +} + +export class TabOperation { + + public static getCommands(config: CursorConfiguration, model: ITextModel, selections: Selection[]) { + const commands: ICommand[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + const selection = selections[i]; + if (selection.isEmpty()) { + const lineText = model.getLineContent(selection.startLineNumber); + if (/^\s*$/.test(lineText) && model.tokenization.isCheapToTokenize(selection.startLineNumber)) { + let goodIndent = this._goodIndentForLine(config, model, selection.startLineNumber); + goodIndent = goodIndent || '\t'; + const possibleTypeText = config.normalizeIndentation(goodIndent); + if (!lineText.startsWith(possibleTypeText)) { + commands[i] = new ReplaceCommand(new Range(selection.startLineNumber, 1, selection.startLineNumber, lineText.length + 1), possibleTypeText, true); + continue; + } + } + commands[i] = this._replaceJumpToNextIndent(config, model, selection, true); + } else { + if (selection.startLineNumber === selection.endLineNumber) { + const lineMaxColumn = model.getLineMaxColumn(selection.startLineNumber); + if (selection.startColumn !== 1 || selection.endColumn !== lineMaxColumn) { + // This is a single line selection that is not the entire line + commands[i] = this._replaceJumpToNextIndent(config, model, selection, false); + continue; + } + } + commands[i] = new ShiftCommand(selection, { + isUnshift: false, + tabSize: config.tabSize, + indentSize: config.indentSize, + insertSpaces: config.insertSpaces, + useTabStops: config.useTabStops, + autoIndent: config.autoIndent + }, config.languageConfigurationService); + } + } + return commands; + } + + private static _goodIndentForLine(config: CursorConfiguration, model: ITextModel, lineNumber: number): string | null { + let action: IndentAction | EnterAction | null = null; + let indentation: string = ''; + const expectedIndentAction = getInheritIndentForLine(config.autoIndent, model, lineNumber, false, config.languageConfigurationService); + if (expectedIndentAction) { + action = expectedIndentAction.action; + indentation = expectedIndentAction.indentation; + } else if (lineNumber > 1) { + let lastLineNumber: number; + for (lastLineNumber = lineNumber - 1; lastLineNumber >= 1; lastLineNumber--) { + const lineText = model.getLineContent(lastLineNumber); + const nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText); + if (nonWhitespaceIdx >= 0) { + break; + } + } + if (lastLineNumber < 1) { + // No previous line with content found + return null; + } + const maxColumn = model.getLineMaxColumn(lastLineNumber); + const expectedEnterAction = getEnterAction(config.autoIndent, model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn), config.languageConfigurationService); + if (expectedEnterAction) { + indentation = expectedEnterAction.indentation + expectedEnterAction.appendText; + } + } + if (action) { + if (action === IndentAction.Indent) { + indentation = shiftIndent(config, indentation); + } + if (action === IndentAction.Outdent) { + indentation = unshiftIndent(config, indentation); + } + indentation = config.normalizeIndentation(indentation); + } + if (!indentation) { + return null; + } + return indentation; + } + + private static _replaceJumpToNextIndent(config: CursorConfiguration, model: ICursorSimpleModel, selection: Selection, insertsAutoWhitespace: boolean): ReplaceCommand { + let typeText = ''; + const position = selection.getStartPosition(); + if (config.insertSpaces) { + const visibleColumnFromColumn = config.visibleColumnFromColumn(model, position); + const indentSize = config.indentSize; + const spacesCnt = indentSize - (visibleColumnFromColumn % indentSize); + for (let i = 0; i < spacesCnt; i++) { + typeText += ' '; + } + } else { + typeText = '\t'; + } + return new ReplaceCommand(selection, typeText, insertsAutoWhitespace); + } +} + +export class BaseTypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorState { + + private readonly _openCharacter: string; + private readonly _closeCharacter: string; + public closeCharacterRange: Range | null; + public enclosingRange: Range | null; + + constructor(selection: Selection, text: string, lineNumberDeltaOffset: number, columnDeltaOffset: number, openCharacter: string, closeCharacter: string) { + super(selection, text, lineNumberDeltaOffset, columnDeltaOffset); + this._openCharacter = openCharacter; + this._closeCharacter = closeCharacter; + this.closeCharacterRange = null; + this.enclosingRange = null; + } + + protected _computeCursorStateWithRange(model: ITextModel, range: Range, helper: ICursorStateComputerData): Selection { + this.closeCharacterRange = new Range(range.startLineNumber, range.endColumn - this._closeCharacter.length, range.endLineNumber, range.endColumn); + this.enclosingRange = new Range(range.startLineNumber, range.endColumn - this._openCharacter.length - this._closeCharacter.length, range.endLineNumber, range.endColumn); + return super.computeCursorState(model, helper); + } +} + +class TypeWithAutoClosingCommand extends BaseTypeWithAutoClosingCommand { + + constructor(selection: Selection, openCharacter: string, insertOpenCharacter: boolean, closeCharacter: string) { + const text = (insertOpenCharacter ? openCharacter : '') + closeCharacter; + const lineNumberDeltaOffset = 0; + const columnDeltaOffset = -closeCharacter.length; + super(selection, text, lineNumberDeltaOffset, columnDeltaOffset, openCharacter, closeCharacter); + } + + public override computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { + const inverseEditOperations = helper.getInverseEditOperations(); + const range = inverseEditOperations[0].range; + return this._computeCursorStateWithRange(model, range, helper); + } +} + +class TypeWithIndentationAndAutoClosingCommand extends BaseTypeWithAutoClosingCommand { + + private readonly _autoIndentationEdit: { range: Range; text: string }; + private readonly _autoClosingEdit: { range: Range; text: string }; + + constructor(autoIndentationEdit: { range: Range; text: string }, selection: Selection, openCharacter: string, closeCharacter: string) { + const text = openCharacter + closeCharacter; + const lineNumberDeltaOffset = 0; + const columnDeltaOffset = openCharacter.length; + super(selection, text, lineNumberDeltaOffset, columnDeltaOffset, openCharacter, closeCharacter); + this._autoIndentationEdit = autoIndentationEdit; + this._autoClosingEdit = { range: selection, text }; + } + + public override getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { + builder.addTrackedEditOperation(this._autoIndentationEdit.range, this._autoIndentationEdit.text); + builder.addTrackedEditOperation(this._autoClosingEdit.range, this._autoClosingEdit.text); + } + + public override computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { + const inverseEditOperations = helper.getInverseEditOperations(); + if (inverseEditOperations.length !== 2) { + throw new Error('There should be two inverse edit operations!'); + } + const range1 = inverseEditOperations[0].range; + const range2 = inverseEditOperations[1].range; + const range = range1.plusRange(range2); + return this._computeCursorStateWithRange(model, range, helper); + } +} + +function getTypingOperation(typedText: string, previousTypingOperation: EditOperationType): EditOperationType { + if (typedText === ' ') { + return previousTypingOperation === EditOperationType.TypingFirstSpace + || previousTypingOperation === EditOperationType.TypingConsecutiveSpace + ? EditOperationType.TypingConsecutiveSpace + : EditOperationType.TypingFirstSpace; + } + + return EditOperationType.TypingOther; +} + +function shouldPushStackElementBetween(previousTypingOperation: EditOperationType, typingOperation: EditOperationType): boolean { + if (isTypingOperation(previousTypingOperation) && !isTypingOperation(typingOperation)) { + // Always set an undo stop before non-type operations + return true; + } + if (previousTypingOperation === EditOperationType.TypingFirstSpace) { + // `abc |d`: No undo stop + // `abc |d`: Undo stop + return false; + } + // Insert undo stop between different operation types + return normalizeOperationType(previousTypingOperation) !== normalizeOperationType(typingOperation); +} + +function normalizeOperationType(type: EditOperationType): EditOperationType | 'space' { + return (type === EditOperationType.TypingConsecutiveSpace || type === EditOperationType.TypingFirstSpace) + ? 'space' + : type; +} + +function isTypingOperation(type: EditOperationType): boolean { + return type === EditOperationType.TypingOther + || type === EditOperationType.TypingFirstSpace + || type === EditOperationType.TypingConsecutiveSpace; +} + +function isAutoClosingOvertype(config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): boolean { + if (config.autoClosingOvertype === 'never') { + return false; + } + if (!config.autoClosingPairs.autoClosingPairsCloseSingleChar.has(ch)) { + return false; + } + for (let i = 0, len = selections.length; i < len; i++) { + const selection = selections[i]; + if (!selection.isEmpty()) { + return false; + } + const position = selection.getPosition(); + const lineText = model.getLineContent(position.lineNumber); + const afterCharacter = lineText.charAt(position.column - 1); + if (afterCharacter !== ch) { + return false; + } + // Do not over-type quotes after a backslash + const chIsQuote = isQuote(ch); + const beforeCharacter = position.column > 2 ? lineText.charCodeAt(position.column - 2) : CharCode.Null; + if (beforeCharacter === CharCode.Backslash && chIsQuote) { + return false; + } + // Must over-type a closing character typed by the editor + if (config.autoClosingOvertype === 'auto') { + let found = false; + for (let j = 0, lenJ = autoClosedCharacters.length; j < lenJ; j++) { + const autoClosedCharacter = autoClosedCharacters[j]; + if (position.lineNumber === autoClosedCharacter.startLineNumber && position.column === autoClosedCharacter.startColumn) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + } + return true; +} + +function typeCommand(range: Range, text: string, keepPosition: boolean): ICommand { + if (keepPosition) { + return new ReplaceCommandWithoutChangingPosition(range, text, true); + } else { + return new ReplaceCommand(range, text, true); + } +} + +export function shiftIndent(config: CursorConfiguration, indentation: string, count?: number): string { + count = count || 1; + return ShiftCommand.shiftIndent(indentation, indentation.length + count, config.tabSize, config.indentSize, config.insertSpaces); +} + +export function unshiftIndent(config: CursorConfiguration, indentation: string, count?: number): string { + count = count || 1; + return ShiftCommand.unshiftIndent(indentation, indentation.length + count, config.tabSize, config.indentSize, config.insertSpaces); +} + +export function shouldSurroundChar(config: CursorConfiguration, ch: string): boolean { + if (isQuote(ch)) { + return (config.autoSurround === 'quotes' || config.autoSurround === 'languageDefined'); + } else { + // Character is a bracket + return (config.autoSurround === 'brackets' || config.autoSurround === 'languageDefined'); + } +} diff --git a/patched-vscode/src/vs/editor/common/cursor/cursorTypeOperations.ts b/patched-vscode/src/vs/editor/common/cursor/cursorTypeOperations.ts index ffa80cbb..b4c65156 100644 --- a/patched-vscode/src/vs/editor/common/cursor/cursorTypeOperations.ts +++ b/patched-vscode/src/vs/editor/common/cursor/cursorTypeOperations.ts @@ -3,26 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CharCode } from 'vs/base/common/charCode'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import * as strings from 'vs/base/common/strings'; -import { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand'; import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand'; -import { CompositionSurroundSelectionCommand, SurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand'; +import { CompositionSurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand'; import { CursorConfiguration, EditOperationResult, EditOperationType, ICursorSimpleModel, isQuote } from 'vs/editor/common/cursorCommon'; -import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/core/wordCharacterClassifier'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { Position } from 'vs/editor/common/core/position'; -import { ICommand, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; +import { ICommand } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { EnterAction, IndentAction, StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; -import { getIndentationAtPosition } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { IElectricAction } from 'vs/editor/common/languages/supports/electricCharacter'; -import { EditorAutoClosingStrategy, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; -import { createScopedLineTokens } from 'vs/editor/common/languages/supports'; -import { getIndentActionForType, getIndentForEnter, getInheritIndentForLine } from 'vs/editor/common/languages/autoIndent'; -import { getEnterAction } from 'vs/editor/common/languages/enterAction'; +import { AutoClosingOpenCharTypeOperation, AutoClosingOvertypeOperation, AutoClosingOvertypeWithInterceptorsOperation, AutoIndentOperation, CompositionOperation, EnterOperation, InterceptorElectricCharOperation, PasteOperation, shiftIndent, shouldSurroundChar, SimpleCharacterTypeOperation, SurroundSelectionOperation, TabOperation, TypeWithoutInterceptorsOperation, unshiftIndent } from 'vs/editor/common/cursor/cursorTypeEditOperations'; export class TypeOperations { @@ -61,777 +50,23 @@ export class TypeOperations { } public static shiftIndent(config: CursorConfiguration, indentation: string, count?: number): string { - count = count || 1; - return ShiftCommand.shiftIndent(indentation, indentation.length + count, config.tabSize, config.indentSize, config.insertSpaces); + return shiftIndent(config, indentation, count); } public static unshiftIndent(config: CursorConfiguration, indentation: string, count?: number): string { - count = count || 1; - return ShiftCommand.unshiftIndent(indentation, indentation.length + count, config.tabSize, config.indentSize, config.insertSpaces); - } - - private static _distributedPaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string[]): EditOperationResult { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - commands[i] = new ReplaceCommand(selections[i], text[i]); - } - return new EditOperationResult(EditOperationType.Other, commands, { - shouldPushStackElementBefore: true, - shouldPushStackElementAfter: true - }); - } - - private static _simplePaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string, pasteOnNewLine: boolean): EditOperationResult { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - const selection = selections[i]; - const position = selection.getPosition(); - - if (pasteOnNewLine && !selection.isEmpty()) { - pasteOnNewLine = false; - } - if (pasteOnNewLine && text.indexOf('\n') !== text.length - 1) { - pasteOnNewLine = false; - } - - if (pasteOnNewLine) { - // Paste entire line at the beginning of line - const typeSelection = new Range(position.lineNumber, 1, position.lineNumber, 1); - commands[i] = new ReplaceCommandThatPreservesSelection(typeSelection, text, selection, true); - } else { - commands[i] = new ReplaceCommand(selection, text); - } - } - return new EditOperationResult(EditOperationType.Other, commands, { - shouldPushStackElementBefore: true, - shouldPushStackElementAfter: true - }); - } - - private static _distributePasteToCursors(config: CursorConfiguration, selections: Selection[], text: string, pasteOnNewLine: boolean, multicursorText: string[]): string[] | null { - if (pasteOnNewLine) { - return null; - } - - if (selections.length === 1) { - return null; - } - - if (multicursorText && multicursorText.length === selections.length) { - return multicursorText; - } - - if (config.multiCursorPaste === 'spread') { - // Try to spread the pasted text in case the line count matches the cursor count - // Remove trailing \n if present - if (text.charCodeAt(text.length - 1) === CharCode.LineFeed) { - text = text.substr(0, text.length - 1); - } - // Remove trailing \r if present - if (text.charCodeAt(text.length - 1) === CharCode.CarriageReturn) { - text = text.substr(0, text.length - 1); - } - const lines = strings.splitLines(text); - if (lines.length === selections.length) { - return lines; - } - } - - return null; + return unshiftIndent(config, indentation, count); } public static paste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string, pasteOnNewLine: boolean, multicursorText: string[]): EditOperationResult { - const distributedPaste = this._distributePasteToCursors(config, selections, text, pasteOnNewLine, multicursorText); - - if (distributedPaste) { - selections = selections.sort(Range.compareRangesUsingStarts); - return this._distributedPaste(config, model, selections, distributedPaste); - } else { - return this._simplePaste(config, model, selections, text, pasteOnNewLine); - } - } - - private static _goodIndentForLine(config: CursorConfiguration, model: ITextModel, lineNumber: number): string | null { - let action: IndentAction | EnterAction | null = null; - let indentation: string = ''; - - const expectedIndentAction = getInheritIndentForLine(config.autoIndent, model, lineNumber, false, config.languageConfigurationService); - if (expectedIndentAction) { - action = expectedIndentAction.action; - indentation = expectedIndentAction.indentation; - } else if (lineNumber > 1) { - let lastLineNumber: number; - for (lastLineNumber = lineNumber - 1; lastLineNumber >= 1; lastLineNumber--) { - const lineText = model.getLineContent(lastLineNumber); - const nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText); - if (nonWhitespaceIdx >= 0) { - break; - } - } - - if (lastLineNumber < 1) { - // No previous line with content found - return null; - } - - const maxColumn = model.getLineMaxColumn(lastLineNumber); - const expectedEnterAction = getEnterAction(config.autoIndent, model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn), config.languageConfigurationService); - if (expectedEnterAction) { - indentation = expectedEnterAction.indentation + expectedEnterAction.appendText; - } - } - - if (action) { - if (action === IndentAction.Indent) { - indentation = TypeOperations.shiftIndent(config, indentation); - } - - if (action === IndentAction.Outdent) { - indentation = TypeOperations.unshiftIndent(config, indentation); - } - - indentation = config.normalizeIndentation(indentation); - } - - if (!indentation) { - return null; - } - - return indentation; - } - - private static _replaceJumpToNextIndent(config: CursorConfiguration, model: ICursorSimpleModel, selection: Selection, insertsAutoWhitespace: boolean): ReplaceCommand { - let typeText = ''; - - const position = selection.getStartPosition(); - if (config.insertSpaces) { - const visibleColumnFromColumn = config.visibleColumnFromColumn(model, position); - const indentSize = config.indentSize; - const spacesCnt = indentSize - (visibleColumnFromColumn % indentSize); - for (let i = 0; i < spacesCnt; i++) { - typeText += ' '; - } - } else { - typeText = '\t'; - } - - return new ReplaceCommand(selection, typeText, insertsAutoWhitespace); + return PasteOperation.getEdits(config, model, selections, text, pasteOnNewLine, multicursorText); } public static tab(config: CursorConfiguration, model: ITextModel, selections: Selection[]): ICommand[] { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - const selection = selections[i]; - - if (selection.isEmpty()) { - - const lineText = model.getLineContent(selection.startLineNumber); - - if (/^\s*$/.test(lineText) && model.tokenization.isCheapToTokenize(selection.startLineNumber)) { - let goodIndent = this._goodIndentForLine(config, model, selection.startLineNumber); - goodIndent = goodIndent || '\t'; - const possibleTypeText = config.normalizeIndentation(goodIndent); - if (!lineText.startsWith(possibleTypeText)) { - commands[i] = new ReplaceCommand(new Range(selection.startLineNumber, 1, selection.startLineNumber, lineText.length + 1), possibleTypeText, true); - continue; - } - } - - commands[i] = this._replaceJumpToNextIndent(config, model, selection, true); - } else { - if (selection.startLineNumber === selection.endLineNumber) { - const lineMaxColumn = model.getLineMaxColumn(selection.startLineNumber); - if (selection.startColumn !== 1 || selection.endColumn !== lineMaxColumn) { - // This is a single line selection that is not the entire line - commands[i] = this._replaceJumpToNextIndent(config, model, selection, false); - continue; - } - } - - commands[i] = new ShiftCommand(selection, { - isUnshift: false, - tabSize: config.tabSize, - indentSize: config.indentSize, - insertSpaces: config.insertSpaces, - useTabStops: config.useTabStops, - autoIndent: config.autoIndent - }, config.languageConfigurationService); - } - } - return commands; + return TabOperation.getCommands(config, model, selections); } public static compositionType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number): EditOperationResult { - const commands = selections.map(selection => this._compositionType(model, selection, text, replacePrevCharCnt, replaceNextCharCnt, positionDelta)); - return new EditOperationResult(EditOperationType.TypingOther, commands, { - shouldPushStackElementBefore: shouldPushStackElementBetween(prevEditOperationType, EditOperationType.TypingOther), - shouldPushStackElementAfter: false - }); - } - - private static _compositionType(model: ITextModel, selection: Selection, text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number): ICommand | null { - if (!selection.isEmpty()) { - // looks like https://github.com/microsoft/vscode/issues/2773 - // where a cursor operation occurred before a canceled composition - // => ignore composition - return null; - } - const pos = selection.getPosition(); - const startColumn = Math.max(1, pos.column - replacePrevCharCnt); - const endColumn = Math.min(model.getLineMaxColumn(pos.lineNumber), pos.column + replaceNextCharCnt); - const range = new Range(pos.lineNumber, startColumn, pos.lineNumber, endColumn); - const oldText = model.getValueInRange(range); - if (oldText === text && positionDelta === 0) { - // => ignore composition that doesn't do anything - return null; - } - return new ReplaceCommandWithOffsetCursorState(range, text, 0, positionDelta); - } - - private static _typeCommand(range: Range, text: string, keepPosition: boolean): ICommand { - if (keepPosition) { - return new ReplaceCommandWithoutChangingPosition(range, text, true); - } else { - return new ReplaceCommand(range, text, true); - } - } - - private static _enter(config: CursorConfiguration, model: ITextModel, keepPosition: boolean, range: Range): ICommand { - if (config.autoIndent === EditorAutoIndentStrategy.None) { - return TypeOperations._typeCommand(range, '\n', keepPosition); - } - if (!model.tokenization.isCheapToTokenize(range.getStartPosition().lineNumber) || config.autoIndent === EditorAutoIndentStrategy.Keep) { - const lineText = model.getLineContent(range.startLineNumber); - const indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); - } - - const r = getEnterAction(config.autoIndent, model, range, config.languageConfigurationService); - if (r) { - if (r.indentAction === IndentAction.None) { - // Nothing special - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition); - - } else if (r.indentAction === IndentAction.Indent) { - // Indent once - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition); - - } else if (r.indentAction === IndentAction.IndentOutdent) { - // Ultra special - const normalIndent = config.normalizeIndentation(r.indentation); - const increasedIndent = config.normalizeIndentation(r.indentation + r.appendText); - - const typeText = '\n' + increasedIndent + '\n' + normalIndent; - - if (keepPosition) { - return new ReplaceCommandWithoutChangingPosition(range, typeText, true); - } else { - return new ReplaceCommandWithOffsetCursorState(range, typeText, -1, increasedIndent.length - normalIndent.length, true); - } - } else if (r.indentAction === IndentAction.Outdent) { - const actualIndentation = TypeOperations.unshiftIndent(config, r.indentation); - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + r.appendText), keepPosition); - } - } - - const lineText = model.getLineContent(range.startLineNumber); - const indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1); - - if (config.autoIndent >= EditorAutoIndentStrategy.Full) { - const ir = getIndentForEnter(config.autoIndent, model, range, { - unshiftIndent: (indent) => { - return TypeOperations.unshiftIndent(config, indent); - }, - shiftIndent: (indent) => { - return TypeOperations.shiftIndent(config, indent); - }, - normalizeIndentation: (indent) => { - return config.normalizeIndentation(indent); - } - }, config.languageConfigurationService); - - if (ir) { - let oldEndViewColumn = config.visibleColumnFromColumn(model, range.getEndPosition()); - const oldEndColumn = range.endColumn; - const newLineContent = model.getLineContent(range.endLineNumber); - const firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent); - if (firstNonWhitespace >= 0) { - range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1)); - } else { - range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber)); - } - - if (keepPosition) { - return new ReplaceCommandWithoutChangingPosition(range, '\n' + config.normalizeIndentation(ir.afterEnter), true); - } else { - let offset = 0; - if (oldEndColumn <= firstNonWhitespace + 1) { - if (!config.insertSpaces) { - oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize); - } - offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); - } - return new ReplaceCommandWithOffsetCursorState(range, '\n' + config.normalizeIndentation(ir.afterEnter), 0, offset, true); - } - } - } - - return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition); - } - - private static _isAutoIndentType(config: CursorConfiguration, model: ITextModel, selections: Selection[]): boolean { - if (config.autoIndent < EditorAutoIndentStrategy.Full) { - return false; - } - - for (let i = 0, len = selections.length; i < len; i++) { - if (!model.tokenization.isCheapToTokenize(selections[i].getEndPosition().lineNumber)) { - return false; - } - } - - return true; - } - - private static _runAutoIndentType(config: CursorConfiguration, model: ITextModel, range: Range, ch: string): ICommand | null { - const currentIndentation = getIndentationAtPosition(model, range.startLineNumber, range.startColumn); - const actualIndentation = getIndentActionForType(config.autoIndent, model, range, ch, { - shiftIndent: (indentation) => { - return TypeOperations.shiftIndent(config, indentation); - }, - unshiftIndent: (indentation) => { - return TypeOperations.unshiftIndent(config, indentation); - }, - }, config.languageConfigurationService); - - if (actualIndentation === null) { - return null; - } - - if (actualIndentation !== config.normalizeIndentation(currentIndentation)) { - const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber); - if (firstNonWhitespace === 0) { - return TypeOperations._typeCommand( - new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn), - config.normalizeIndentation(actualIndentation) + ch, - false - ); - } else { - return TypeOperations._typeCommand( - new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn), - config.normalizeIndentation(actualIndentation) + - model.getLineContent(range.startLineNumber).substring(firstNonWhitespace - 1, range.startColumn - 1) + ch, - false - ); - } - } - - return null; - } - - private static _isAutoClosingOvertype(config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): boolean { - if (config.autoClosingOvertype === 'never') { - return false; - } - - if (!config.autoClosingPairs.autoClosingPairsCloseSingleChar.has(ch)) { - return false; - } - - for (let i = 0, len = selections.length; i < len; i++) { - const selection = selections[i]; - - if (!selection.isEmpty()) { - return false; - } - - const position = selection.getPosition(); - const lineText = model.getLineContent(position.lineNumber); - const afterCharacter = lineText.charAt(position.column - 1); - - if (afterCharacter !== ch) { - return false; - } - - // Do not over-type quotes after a backslash - const chIsQuote = isQuote(ch); - const beforeCharacter = position.column > 2 ? lineText.charCodeAt(position.column - 2) : CharCode.Null; - if (beforeCharacter === CharCode.Backslash && chIsQuote) { - return false; - } - - // Must over-type a closing character typed by the editor - if (config.autoClosingOvertype === 'auto') { - let found = false; - for (let j = 0, lenJ = autoClosedCharacters.length; j < lenJ; j++) { - const autoClosedCharacter = autoClosedCharacters[j]; - if (position.lineNumber === autoClosedCharacter.startLineNumber && position.column === autoClosedCharacter.startColumn) { - found = true; - break; - } - } - if (!found) { - return false; - } - } - } - - return true; - } - - private static _runAutoClosingOvertype(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - const selection = selections[i]; - const position = selection.getPosition(); - const typeSelection = new Range(position.lineNumber, position.column, position.lineNumber, position.column + 1); - commands[i] = new ReplaceCommand(typeSelection, ch); - } - return new EditOperationResult(EditOperationType.TypingOther, commands, { - shouldPushStackElementBefore: shouldPushStackElementBetween(prevEditOperationType, EditOperationType.TypingOther), - shouldPushStackElementAfter: false - }); - } - - private static _isBeforeClosingBrace(config: CursorConfiguration, lineAfter: string) { - // If the start of lineAfter can be interpretted as both a starting or ending brace, default to returning false - const nextChar = lineAfter.charAt(0); - const potentialStartingBraces = config.autoClosingPairs.autoClosingPairsOpenByStart.get(nextChar) || []; - const potentialClosingBraces = config.autoClosingPairs.autoClosingPairsCloseByStart.get(nextChar) || []; - - const isBeforeStartingBrace = potentialStartingBraces.some(x => lineAfter.startsWith(x.open)); - const isBeforeClosingBrace = potentialClosingBraces.some(x => lineAfter.startsWith(x.close)); - - return !isBeforeStartingBrace && isBeforeClosingBrace; - } - - /** - * Determine if typing `ch` at all `positions` in the `model` results in an - * auto closing open sequence being typed. - * - * Auto closing open sequences can consist of multiple characters, which - * can lead to ambiguities. In such a case, the longest auto-closing open - * sequence is returned. - */ - private static _findAutoClosingPairOpen(config: CursorConfiguration, model: ITextModel, positions: Position[], ch: string): StandardAutoClosingPairConditional | null { - const candidates = config.autoClosingPairs.autoClosingPairsOpenByEnd.get(ch); - if (!candidates) { - return null; - } - - // Determine which auto-closing pair it is - let result: StandardAutoClosingPairConditional | null = null; - for (const candidate of candidates) { - if (result === null || candidate.open.length > result.open.length) { - let candidateIsMatch = true; - for (const position of positions) { - const relevantText = model.getValueInRange(new Range(position.lineNumber, position.column - candidate.open.length + 1, position.lineNumber, position.column)); - if (relevantText + ch !== candidate.open) { - candidateIsMatch = false; - break; - } - } - - if (candidateIsMatch) { - result = candidate; - } - } - } - return result; - } - - /** - * Find another auto-closing pair that is contained by the one passed in. - * - * e.g. when having [(,)] and [(*,*)] as auto-closing pairs - * this method will find [(,)] as a containment pair for [(*,*)] - */ - private static _findContainedAutoClosingPair(config: CursorConfiguration, pair: StandardAutoClosingPairConditional): StandardAutoClosingPairConditional | null { - if (pair.open.length <= 1) { - return null; - } - const lastChar = pair.close.charAt(pair.close.length - 1); - // get candidates with the same last character as close - const candidates = config.autoClosingPairs.autoClosingPairsCloseByEnd.get(lastChar) || []; - let result: StandardAutoClosingPairConditional | null = null; - for (const candidate of candidates) { - if (candidate.open !== pair.open && pair.open.includes(candidate.open) && pair.close.endsWith(candidate.close)) { - if (!result || candidate.open.length > result.open.length) { - result = candidate; - } - } - } - return result; - } - - private static _getAutoClosingPairClose(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean): string | null { - - for (const selection of selections) { - if (!selection.isEmpty()) { - return null; - } - } - - // This method is called both when typing (regularly) and when composition ends - // This means that we need to work with a text buffer where sometimes `ch` is not - // there (it is being typed right now) or with a text buffer where `ch` has already been typed - // - // In order to avoid adding checks for `chIsAlreadyTyped` in all places, we will work - // with two conceptual positions, the position before `ch` and the position after `ch` - // - const positions: { lineNumber: number; beforeColumn: number; afterColumn: number }[] = selections.map((s) => { - const position = s.getPosition(); - if (chIsAlreadyTyped) { - return { lineNumber: position.lineNumber, beforeColumn: position.column - ch.length, afterColumn: position.column }; - } else { - return { lineNumber: position.lineNumber, beforeColumn: position.column, afterColumn: position.column }; - } - }); - - - // Find the longest auto-closing open pair in case of multiple ending in `ch` - // e.g. when having [f","] and [","], it picks [f","] if the character before is f - const pair = this._findAutoClosingPairOpen(config, model, positions.map(p => new Position(p.lineNumber, p.beforeColumn)), ch); - if (!pair) { - return null; - } - - let autoCloseConfig: EditorAutoClosingStrategy; - let shouldAutoCloseBefore: (ch: string) => boolean; - - const chIsQuote = isQuote(ch); - if (chIsQuote) { - autoCloseConfig = config.autoClosingQuotes; - shouldAutoCloseBefore = config.shouldAutoCloseBefore.quote; - } else { - const pairIsForComments = config.blockCommentStartToken ? pair.open.includes(config.blockCommentStartToken) : false; - if (pairIsForComments) { - autoCloseConfig = config.autoClosingComments; - shouldAutoCloseBefore = config.shouldAutoCloseBefore.comment; - } else { - autoCloseConfig = config.autoClosingBrackets; - shouldAutoCloseBefore = config.shouldAutoCloseBefore.bracket; - } - } - - if (autoCloseConfig === 'never') { - return null; - } - - // Sometimes, it is possible to have two auto-closing pairs that have a containment relationship - // e.g. when having [(,)] and [(*,*)] - // - when typing (, the resulting state is (|) - // - when typing *, the desired resulting state is (*|*), not (*|*)) - const containedPair = this._findContainedAutoClosingPair(config, pair); - const containedPairClose = containedPair ? containedPair.close : ''; - let isContainedPairPresent = true; - - for (const position of positions) { - const { lineNumber, beforeColumn, afterColumn } = position; - const lineText = model.getLineContent(lineNumber); - const lineBefore = lineText.substring(0, beforeColumn - 1); - const lineAfter = lineText.substring(afterColumn - 1); - - if (!lineAfter.startsWith(containedPairClose)) { - isContainedPairPresent = false; - } - - // Only consider auto closing the pair if an allowed character follows or if another autoclosed pair closing brace follows - if (lineAfter.length > 0) { - const characterAfter = lineAfter.charAt(0); - const isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, lineAfter); - - if (!isBeforeCloseBrace && !shouldAutoCloseBefore(characterAfter)) { - return null; - } - } - - // Do not auto-close ' or " after a word character - if (pair.open.length === 1 && (ch === '\'' || ch === '"') && autoCloseConfig !== 'always') { - const wordSeparators = getMapForWordSeparators(config.wordSeparators, []); - if (lineBefore.length > 0) { - const characterBefore = lineBefore.charCodeAt(lineBefore.length - 1); - if (wordSeparators.get(characterBefore) === WordCharacterClass.Regular) { - return null; - } - } - } - - if (!model.tokenization.isCheapToTokenize(lineNumber)) { - // Do not force tokenization - return null; - } - - model.tokenization.forceTokenization(lineNumber); - const lineTokens = model.tokenization.getLineTokens(lineNumber); - const scopedLineTokens = createScopedLineTokens(lineTokens, beforeColumn - 1); - if (!pair.shouldAutoClose(scopedLineTokens, beforeColumn - scopedLineTokens.firstCharOffset)) { - return null; - } - - // Typing for example a quote could either start a new string, in which case auto-closing is desirable - // or it could end a previously started string, in which case auto-closing is not desirable - // - // In certain cases, it is really not possible to look at the previous token to determine - // what would happen. That's why we do something really unusual, we pretend to type a different - // character and ask the tokenizer what the outcome of doing that is: after typing a neutral - // character, are we in a string (i.e. the quote would most likely end a string) or not? - // - const neutralCharacter = pair.findNeutralCharacter(); - if (neutralCharacter) { - const tokenType = model.tokenization.getTokenTypeIfInsertingCharacter(lineNumber, beforeColumn, neutralCharacter); - if (!pair.isOK(tokenType)) { - return null; - } - } - } - - if (isContainedPairPresent) { - return pair.close.substring(0, pair.close.length - containedPairClose.length); - } else { - return pair.close; - } - } - - private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean, autoClosingPairClose: string): EditOperationResult { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - const selection = selections[i]; - commands[i] = new TypeWithAutoClosingCommand(selection, ch, !chIsAlreadyTyped, autoClosingPairClose); - } - return new EditOperationResult(EditOperationType.TypingOther, commands, { - shouldPushStackElementBefore: true, - shouldPushStackElementAfter: false - }); - } - - private static _shouldSurroundChar(config: CursorConfiguration, ch: string): boolean { - if (isQuote(ch)) { - return (config.autoSurround === 'quotes' || config.autoSurround === 'languageDefined'); - } else { - // Character is a bracket - return (config.autoSurround === 'brackets' || config.autoSurround === 'languageDefined'); - } - } - - private static _isSurroundSelectionType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): boolean { - if (!TypeOperations._shouldSurroundChar(config, ch) || !config.surroundingPairs.hasOwnProperty(ch)) { - return false; - } - - const isTypingAQuoteCharacter = isQuote(ch); - - for (const selection of selections) { - - if (selection.isEmpty()) { - return false; - } - - let selectionContainsOnlyWhitespace = true; - - for (let lineNumber = selection.startLineNumber; lineNumber <= selection.endLineNumber; lineNumber++) { - const lineText = model.getLineContent(lineNumber); - const startIndex = (lineNumber === selection.startLineNumber ? selection.startColumn - 1 : 0); - const endIndex = (lineNumber === selection.endLineNumber ? selection.endColumn - 1 : lineText.length); - const selectedText = lineText.substring(startIndex, endIndex); - if (/[^ \t]/.test(selectedText)) { - // this selected text contains something other than whitespace - selectionContainsOnlyWhitespace = false; - break; - } - } - - if (selectionContainsOnlyWhitespace) { - return false; - } - - if (isTypingAQuoteCharacter && selection.startLineNumber === selection.endLineNumber && selection.startColumn + 1 === selection.endColumn) { - const selectionText = model.getValueInRange(selection); - if (isQuote(selectionText)) { - // Typing a quote character on top of another quote character - // => disable surround selection type - return false; - } - } - } - - return true; - } - - private static _runSurroundSelectionType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - const selection = selections[i]; - const closeCharacter = config.surroundingPairs[ch]; - commands[i] = new SurroundSelectionCommand(selection, ch, closeCharacter); - } - return new EditOperationResult(EditOperationType.Other, commands, { - shouldPushStackElementBefore: true, - shouldPushStackElementAfter: true - }); - } - - private static _isTypeInterceptorElectricChar(config: CursorConfiguration, model: ITextModel, selections: Selection[]) { - if (selections.length === 1 && model.tokenization.isCheapToTokenize(selections[0].getEndPosition().lineNumber)) { - return true; - } - return false; - } - - private static _typeInterceptorElectricChar(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selection: Selection, ch: string): EditOperationResult | null { - if (!config.electricChars.hasOwnProperty(ch) || !selection.isEmpty()) { - return null; - } - - const position = selection.getPosition(); - model.tokenization.forceTokenization(position.lineNumber); - const lineTokens = model.tokenization.getLineTokens(position.lineNumber); - - let electricAction: IElectricAction | null; - try { - electricAction = config.onElectricCharacter(ch, lineTokens, position.column); - } catch (e) { - onUnexpectedError(e); - return null; - } - - if (!electricAction) { - return null; - } - - if (electricAction.matchOpenBracket) { - const endColumn = (lineTokens.getLineContent() + ch).lastIndexOf(electricAction.matchOpenBracket) + 1; - const match = model.bracketPairs.findMatchingBracketUp(electricAction.matchOpenBracket, { - lineNumber: position.lineNumber, - column: endColumn - }, 500 /* give at most 500ms to compute */); - - if (match) { - if (match.startLineNumber === position.lineNumber) { - // matched something on the same line => no change in indentation - return null; - } - const matchLine = model.getLineContent(match.startLineNumber); - const matchLineIndentation = strings.getLeadingWhitespace(matchLine); - const newIndentation = config.normalizeIndentation(matchLineIndentation); - - const lineText = model.getLineContent(position.lineNumber); - const lineFirstNonBlankColumn = model.getLineFirstNonWhitespaceColumn(position.lineNumber) || position.column; - - const prefix = lineText.substring(lineFirstNonBlankColumn - 1, position.column - 1); - const typeText = newIndentation + prefix + ch; - - const typeSelection = new Range(position.lineNumber, 1, position.lineNumber, position.column); - - const command = new ReplaceCommand(typeSelection, typeText); - return new EditOperationResult(getTypingOperation(typeText, prevEditOperationType), [command], { - shouldPushStackElementBefore: false, - shouldPushStackElementAfter: true - }); - } - } - - return null; + return CompositionOperation.getEdits(prevEditOperationType, config, model, selections, text, replacePrevCharCnt, replaceNextCharCnt, positionDelta); } /** @@ -871,7 +106,7 @@ export class TypeOperations { if (hasDeletion) { // Check if this could have been a surround selection - if (!TypeOperations._shouldSurroundChar(config, ch) || !config.surroundingPairs.hasOwnProperty(ch)) { + if (!shouldSurroundChar(config, ch) || !config.surroundingPairs.hasOwnProperty(ch)) { return null; } @@ -914,18 +149,14 @@ export class TypeOperations { }); } - if (this._isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) { - // Unfortunately, the close character is at this point "doubled", so we need to delete it... - const commands = selections.map(s => new ReplaceCommand(new Range(s.positionLineNumber, s.positionColumn, s.positionLineNumber, s.positionColumn + 1), '', false)); - return new EditOperationResult(EditOperationType.TypingOther, commands, { - shouldPushStackElementBefore: true, - shouldPushStackElementAfter: false - }); + const autoClosingOvertypeEdits = AutoClosingOvertypeWithInterceptorsOperation.getEdits(config, model, selections, autoClosedCharacters, ch); + if (autoClosingOvertypeEdits !== undefined) { + return autoClosingOvertypeEdits; } - const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, true); - if (autoClosingPairClose !== null) { - return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairClose); + const autoClosingOpenCharEdits = AutoClosingOpenCharTypeOperation.getEdits(config, model, selections, ch, true, false); + if (autoClosingOpenCharEdits !== undefined) { + return autoClosingOpenCharEdits; } return null; @@ -933,149 +164,41 @@ export class TypeOperations { public static typeWithInterceptors(isDoingComposition: boolean, prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult { - if (!isDoingComposition && ch === '\n') { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - commands[i] = TypeOperations._enter(config, model, false, selections[i]); - } - return new EditOperationResult(EditOperationType.TypingOther, commands, { - shouldPushStackElementBefore: true, - shouldPushStackElementAfter: false, - }); + const enterEdits = EnterOperation.getEdits(config, model, selections, ch, isDoingComposition); + if (enterEdits !== undefined) { + return enterEdits; } - if (!isDoingComposition && this._isAutoIndentType(config, model, selections)) { - const commands: Array = []; - let autoIndentFails = false; - for (let i = 0, len = selections.length; i < len; i++) { - commands[i] = this._runAutoIndentType(config, model, selections[i], ch); - if (!commands[i]) { - autoIndentFails = true; - break; - } - } - if (!autoIndentFails) { - return new EditOperationResult(EditOperationType.TypingOther, commands, { - shouldPushStackElementBefore: true, - shouldPushStackElementAfter: false, - }); - } + const autoIndentEdits = AutoIndentOperation.getEdits(config, model, selections, ch, isDoingComposition); + if (autoIndentEdits !== undefined) { + return autoIndentEdits; } - if (this._isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) { - return this._runAutoClosingOvertype(prevEditOperationType, config, model, selections, ch); + const autoClosingOverTypeEdits = AutoClosingOvertypeOperation.getEdits(prevEditOperationType, config, model, selections, autoClosedCharacters, ch); + if (autoClosingOverTypeEdits !== undefined) { + return autoClosingOverTypeEdits; } - if (!isDoingComposition) { - const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, false); - if (autoClosingPairClose) { - return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, false, autoClosingPairClose); - } + const autoClosingOpenCharEdits = AutoClosingOpenCharTypeOperation.getEdits(config, model, selections, ch, false, isDoingComposition); + if (autoClosingOpenCharEdits !== undefined) { + return autoClosingOpenCharEdits; } - if (!isDoingComposition && this._isSurroundSelectionType(config, model, selections, ch)) { - return this._runSurroundSelectionType(prevEditOperationType, config, model, selections, ch); + const surroundSelectionEdits = SurroundSelectionOperation.getEdits(config, model, selections, ch, isDoingComposition); + if (surroundSelectionEdits !== undefined) { + return surroundSelectionEdits; } - // Electric characters make sense only when dealing with a single cursor, - // as multiple cursors typing brackets for example would interfer with bracket matching - if (!isDoingComposition && this._isTypeInterceptorElectricChar(config, model, selections)) { - const r = this._typeInterceptorElectricChar(prevEditOperationType, config, model, selections[0], ch); - if (r) { - return r; - } + const interceptorElectricCharOperation = InterceptorElectricCharOperation.getEdits(prevEditOperationType, config, model, selections, ch, isDoingComposition); + if (interceptorElectricCharOperation !== undefined) { + return interceptorElectricCharOperation; } - // A simple character type - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - commands[i] = new ReplaceCommand(selections[i], ch); - } - - const opType = getTypingOperation(ch, prevEditOperationType); - return new EditOperationResult(opType, commands, { - shouldPushStackElementBefore: shouldPushStackElementBetween(prevEditOperationType, opType), - shouldPushStackElementAfter: false - }); + return SimpleCharacterTypeOperation.getEdits(prevEditOperationType, selections, ch); } public static typeWithoutInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], str: string): EditOperationResult { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - commands[i] = new ReplaceCommand(selections[i], str); - } - const opType = getTypingOperation(str, prevEditOperationType); - return new EditOperationResult(opType, commands, { - shouldPushStackElementBefore: shouldPushStackElementBetween(prevEditOperationType, opType), - shouldPushStackElementAfter: false - }); - } - - public static lineInsertBefore(config: CursorConfiguration, model: ITextModel | null, selections: Selection[] | null): ICommand[] { - if (model === null || selections === null) { - return []; - } - - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - let lineNumber = selections[i].positionLineNumber; - - if (lineNumber === 1) { - commands[i] = new ReplaceCommandWithoutChangingPosition(new Range(1, 1, 1, 1), '\n'); - } else { - lineNumber--; - const column = model.getLineMaxColumn(lineNumber); - - commands[i] = this._enter(config, model, false, new Range(lineNumber, column, lineNumber, column)); - } - } - return commands; - } - - public static lineInsertAfter(config: CursorConfiguration, model: ITextModel | null, selections: Selection[] | null): ICommand[] { - if (model === null || selections === null) { - return []; - } - - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - const lineNumber = selections[i].positionLineNumber; - const column = model.getLineMaxColumn(lineNumber); - commands[i] = this._enter(config, model, false, new Range(lineNumber, column, lineNumber, column)); - } - return commands; - } - - public static lineBreakInsert(config: CursorConfiguration, model: ITextModel, selections: Selection[]): ICommand[] { - const commands: ICommand[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - commands[i] = this._enter(config, model, true, selections[i]); - } - return commands; - } -} - -export class TypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorState { - - private readonly _openCharacter: string; - private readonly _closeCharacter: string; - public closeCharacterRange: Range | null; - public enclosingRange: Range | null; - - constructor(selection: Selection, openCharacter: string, insertOpenCharacter: boolean, closeCharacter: string) { - super(selection, (insertOpenCharacter ? openCharacter : '') + closeCharacter, 0, -closeCharacter.length); - this._openCharacter = openCharacter; - this._closeCharacter = closeCharacter; - this.closeCharacterRange = null; - this.enclosingRange = null; - } - - public override computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - const inverseEditOperations = helper.getInverseEditOperations(); - const range = inverseEditOperations[0].range; - this.closeCharacterRange = new Range(range.startLineNumber, range.endColumn - this._closeCharacter.length, range.endLineNumber, range.endColumn); - this.enclosingRange = new Range(range.startLineNumber, range.endColumn - this._openCharacter.length - this._closeCharacter.length, range.endLineNumber, range.endColumn); - return super.computeCursorState(model, helper); + return TypeWithoutInterceptorsOperation.getEdits(prevEditOperationType, selections, str); } } @@ -1089,40 +212,3 @@ export class CompositionOutcome { public readonly insertedSelectionEnd: number, ) { } } - -function getTypingOperation(typedText: string, previousTypingOperation: EditOperationType): EditOperationType { - if (typedText === ' ') { - return previousTypingOperation === EditOperationType.TypingFirstSpace - || previousTypingOperation === EditOperationType.TypingConsecutiveSpace - ? EditOperationType.TypingConsecutiveSpace - : EditOperationType.TypingFirstSpace; - } - - return EditOperationType.TypingOther; -} - -function shouldPushStackElementBetween(previousTypingOperation: EditOperationType, typingOperation: EditOperationType): boolean { - if (isTypingOperation(previousTypingOperation) && !isTypingOperation(typingOperation)) { - // Always set an undo stop before non-type operations - return true; - } - if (previousTypingOperation === EditOperationType.TypingFirstSpace) { - // `abc |d`: No undo stop - // `abc |d`: Undo stop - return false; - } - // Insert undo stop between different operation types - return normalizeOperationType(previousTypingOperation) !== normalizeOperationType(typingOperation); -} - -function normalizeOperationType(type: EditOperationType): EditOperationType | 'space' { - return (type === EditOperationType.TypingConsecutiveSpace || type === EditOperationType.TypingFirstSpace) - ? 'space' - : type; -} - -function isTypingOperation(type: EditOperationType): boolean { - return type === EditOperationType.TypingOther - || type === EditOperationType.TypingFirstSpace - || type === EditOperationType.TypingConsecutiveSpace; -} diff --git a/patched-vscode/src/vs/editor/common/cursor/cursorWordOperations.ts b/patched-vscode/src/vs/editor/common/cursor/cursorWordOperations.ts index b16172cc..a4353821 100644 --- a/patched-vscode/src/vs/editor/common/cursor/cursorWordOperations.ts +++ b/patched-vscode/src/vs/editor/common/cursor/cursorWordOperations.ts @@ -208,7 +208,7 @@ export class WordOperations { return 0; } - public static moveWordLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position { + public static moveWordLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { let lineNumber = position.lineNumber; let column = position.column; @@ -227,7 +227,8 @@ export class WordOperations { if (wordNavigationType === WordNavigationType.WordStartFast) { if ( - prevWordOnLine + !hasMulticursor // avoid having multiple cursors stop at different locations when doing word start + && prevWordOnLine && prevWordOnLine.wordType === WordType.Separator && prevWordOnLine.end - prevWordOnLine.start === 1 && prevWordOnLine.nextCharClass === WordCharacterClass.Regular @@ -830,10 +831,10 @@ export class WordPartOperations extends WordOperations { return candidates[0]; } - public static moveWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position): Position { + public static moveWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, hasMulticursor: boolean): Position { const candidates = enforceDefined([ - WordOperations.moveWordLeft(wordSeparators, model, position, WordNavigationType.WordStart), - WordOperations.moveWordLeft(wordSeparators, model, position, WordNavigationType.WordEnd), + WordOperations.moveWordLeft(wordSeparators, model, position, WordNavigationType.WordStart, hasMulticursor), + WordOperations.moveWordLeft(wordSeparators, model, position, WordNavigationType.WordEnd, hasMulticursor), WordOperations._moveWordPartLeft(model, position) ]); candidates.sort(Position.compare); diff --git a/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts b/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts index 50fbf66d..2de46350 100644 --- a/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts +++ b/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts @@ -52,6 +52,18 @@ export class SequenceDiff { ); } + public static assertSorted(sequenceDiffs: SequenceDiff[]): void { + let last: SequenceDiff | undefined = undefined; + for (const cur of sequenceDiffs) { + if (last) { + if (!(last.seq1Range.endExclusive <= cur.seq1Range.start && last.seq2Range.endExclusive <= cur.seq2Range.start)) { + throw new BugIndicatingError('Sequence diffs must be sorted'); + } + } + last = cur; + } + } + constructor( public readonly seq1Range: OffsetRange, public readonly seq2Range: OffsetRange, diff --git a/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts b/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts index 8f718321..6a46557a 100644 --- a/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts +++ b/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts @@ -9,10 +9,10 @@ import { pushMany, compareBy, numberComparator, reverseOrder } from 'vs/base/com import { MonotonousArray, findLastMonotonous } from 'vs/base/common/arraysFind'; import { SetMap } from 'vs/base/common/map'; import { LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange'; -import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence'; import { LineRangeFragment, isSpace } from 'vs/editor/common/diff/defaultLinesDiffComputer/utils'; import { MyersDiffAlgorithm } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/myersDiffAlgorithm'; +import { Range } from 'vs/editor/common/core/range'; export function computeMovedLines( changes: DetailedLineRangeMapping[], @@ -260,8 +260,8 @@ function areLinesSimilar(line1: string, line2: string, timeout: ITimeout): boole const myersDiffingAlgorithm = new MyersDiffAlgorithm(); const result = myersDiffingAlgorithm.compute( - new LinesSliceCharSequence([line1], new OffsetRange(0, 1), false), - new LinesSliceCharSequence([line2], new OffsetRange(0, 1), false), + new LinesSliceCharSequence([line1], new Range(1, 1, 1, line1.length), false), + new LinesSliceCharSequence([line2], new Range(1, 1, 1, line2.length), false), timeout ); let commonNonSpaceCharCount = 0; diff --git a/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts b/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts index 2c3ee2fe..5573f684 100644 --- a/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts +++ b/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts @@ -17,7 +17,7 @@ import { extendDiffsToEntireWordIfAppropriate, optimizeSequenceDiffs, removeShor import { LineSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/lineSequence'; import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence'; import { ILinesDiffComputer, ILinesDiffComputerOptions, LinesDiff, MovedText } from 'vs/editor/common/diff/linesDiffComputer'; -import { DetailedLineRangeMapping, RangeMapping } from '../rangeMapping'; +import { DetailedLineRangeMapping, LineRangeMapping, RangeMapping } from '../rangeMapping'; export class DefaultLinesDiffComputer implements ILinesDiffComputer { private readonly dynamicProgrammingDiffing = new DynamicProgrammingDiffing(); @@ -167,7 +167,9 @@ export class DefaultLinesDiffComputer implements ILinesDiffComputer { for (const ic of c.innerChanges) { const valid = validatePosition(ic.modifiedRange.getStartPosition(), modifiedLines) && validatePosition(ic.modifiedRange.getEndPosition(), modifiedLines) && validatePosition(ic.originalRange.getStartPosition(), originalLines) && validatePosition(ic.originalRange.getEndPosition(), originalLines); - if (!valid) { return false; } + if (!valid) { + return false; + } } if (!validateRange(c.modified, modifiedLines) || !validateRange(c.original, originalLines)) { return false; @@ -208,18 +210,28 @@ export class DefaultLinesDiffComputer implements ILinesDiffComputer { } private refineDiff(originalLines: string[], modifiedLines: string[], diff: SequenceDiff, timeout: ITimeout, considerWhitespaceChanges: boolean): { mappings: RangeMapping[]; hitTimeout: boolean } { - const slice1 = new LinesSliceCharSequence(originalLines, diff.seq1Range, considerWhitespaceChanges); - const slice2 = new LinesSliceCharSequence(modifiedLines, diff.seq2Range, considerWhitespaceChanges); + const lineRangeMapping = toLineRangeMapping(diff); + const rangeMapping = lineRangeMapping.toRangeMapping2(originalLines, modifiedLines); + + const slice1 = new LinesSliceCharSequence(originalLines, rangeMapping.originalRange, considerWhitespaceChanges); + const slice2 = new LinesSliceCharSequence(modifiedLines, rangeMapping.modifiedRange, considerWhitespaceChanges); const diffResult = slice1.length + slice2.length < 500 ? this.dynamicProgrammingDiffing.compute(slice1, slice2, timeout) : this.myersDiffingAlgorithm.compute(slice1, slice2, timeout); + const check = false; + let diffs = diffResult.diffs; + if (check) { SequenceDiff.assertSorted(diffs); } diffs = optimizeSequenceDiffs(slice1, slice2, diffs); + if (check) { SequenceDiff.assertSorted(diffs); } diffs = extendDiffsToEntireWordIfAppropriate(slice1, slice2, diffs); + if (check) { SequenceDiff.assertSorted(diffs); } diffs = removeShortMatches(slice1, slice2, diffs); + if (check) { SequenceDiff.assertSorted(diffs); } diffs = removeVeryShortMatchingTextBetweenLongDiffs(slice1, slice2, diffs); + if (check) { SequenceDiff.assertSorted(diffs); } const result = diffs.map( (d) => @@ -229,6 +241,8 @@ export class DefaultLinesDiffComputer implements ILinesDiffComputer { ) ); + if (check) { RangeMapping.assertSorted(result); } + // Assert: result applied on original should be the same as diff applied to original return { @@ -312,3 +326,10 @@ export function getLineRangeMapping(rangeMapping: RangeMapping, originalLines: s return new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, [rangeMapping]); } + +function toLineRangeMapping(sequenceDiff: SequenceDiff) { + return new LineRangeMapping( + new LineRange(sequenceDiff.seq1Range.start + 1, sequenceDiff.seq1Range.endExclusive + 1), + new LineRange(sequenceDiff.seq2Range.start + 1, sequenceDiff.seq2Range.endExclusive + 1), + ); +} diff --git a/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts b/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts index 9edc63d3..77615994 100644 --- a/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts +++ b/patched-vscode/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts @@ -13,52 +13,39 @@ import { isSpace } from 'vs/editor/common/diff/defaultLinesDiffComputer/utils'; export class LinesSliceCharSequence implements ISequence { private readonly elements: number[] = []; - private readonly firstCharOffsetByLine: number[] = []; - public readonly lineRange: OffsetRange; - // To account for trimming - private readonly additionalOffsetByLine: number[] = []; - - constructor(public readonly lines: string[], lineRange: OffsetRange, public readonly considerWhitespaceChanges: boolean) { - // This slice has to have lineRange.length many \n! (otherwise diffing against an empty slice will be problematic) - // (Unless it covers the entire document, in that case the other slice also has to cover the entire document ands it's okay) - - // If the slice covers the end, but does not start at the beginning, we include just the \n of the previous line. - let trimFirstLineFully = false; - if (lineRange.start > 0 && lineRange.endExclusive >= lines.length) { - lineRange = new OffsetRange(lineRange.start - 1, lineRange.endExclusive); - trimFirstLineFully = true; - } + private readonly firstElementOffsetByLineIdx: number[] = []; + private readonly lineStartOffsets: number[] = []; + private readonly trimmedWsLengthsByLineIdx: number[] = []; + + constructor(public readonly lines: string[], private readonly range: Range, public readonly considerWhitespaceChanges: boolean) { + this.firstElementOffsetByLineIdx.push(0); + for (let lineNumber = this.range.startLineNumber; lineNumber <= this.range.endLineNumber; lineNumber++) { + let line = lines[lineNumber - 1]; + let lineStartOffset = 0; + if (lineNumber === this.range.startLineNumber && this.range.startColumn > 1) { + lineStartOffset = this.range.startColumn - 1; + line = line.substring(lineStartOffset); + } + this.lineStartOffsets.push(lineStartOffset); - this.lineRange = lineRange; - - this.firstCharOffsetByLine[0] = 0; - for (let i = this.lineRange.start; i < this.lineRange.endExclusive; i++) { - let line = lines[i]; - let offset = 0; - if (trimFirstLineFully) { - offset = line.length; - line = ''; - trimFirstLineFully = false; - } else if (!considerWhitespaceChanges) { + let trimmedWsLength = 0; + if (!considerWhitespaceChanges) { const trimmedStartLine = line.trimStart(); - offset = line.length - trimmedStartLine.length; + trimmedWsLength = line.length - trimmedStartLine.length; line = trimmedStartLine.trimEnd(); } + this.trimmedWsLengthsByLineIdx.push(trimmedWsLength); - this.additionalOffsetByLine.push(offset); - - for (let i = 0; i < line.length; i++) { + const lineLength = lineNumber === this.range.endLineNumber ? Math.min(this.range.endColumn - 1 - lineStartOffset - trimmedWsLength, line.length) : line.length; + for (let i = 0; i < lineLength; i++) { this.elements.push(line.charCodeAt(i)); } - // Don't add an \n that does not exist in the document. - if (i < lines.length - 1) { + if (lineNumber < this.range.endLineNumber) { this.elements.push('\n'.charCodeAt(0)); - this.firstCharOffsetByLine[i - this.lineRange.start + 1] = this.elements.length; + this.firstElementOffsetByLineIdx.push(this.elements.length); } } - // To account for the last line - this.additionalOffsetByLine.push(0); } toString() { @@ -111,18 +98,23 @@ export class LinesSliceCharSequence implements ISequence { return score; } - public translateOffset(offset: number): Position { + public translateOffset(offset: number, preference: 'left' | 'right' = 'right'): Position { // find smallest i, so that lineBreakOffsets[i] <= offset using binary search - if (this.lineRange.isEmpty) { - return new Position(this.lineRange.start + 1, 1); - } - - const i = findLastIdxMonotonous(this.firstCharOffsetByLine, (value) => value <= offset); - return new Position(this.lineRange.start + i + 1, offset - this.firstCharOffsetByLine[i] + this.additionalOffsetByLine[i] + 1); + const i = findLastIdxMonotonous(this.firstElementOffsetByLineIdx, (value) => value <= offset); + const lineOffset = offset - this.firstElementOffsetByLineIdx[i]; + return new Position( + this.range.startLineNumber + i, + 1 + this.lineStartOffsets[i] + lineOffset + ((lineOffset === 0 && preference === 'left') ? 0 : this.trimmedWsLengthsByLineIdx[i]) + ); } public translateRange(range: OffsetRange): Range { - return Range.fromPositions(this.translateOffset(range.start), this.translateOffset(range.endExclusive)); + const pos1 = this.translateOffset(range.start, 'right'); + const pos2 = this.translateOffset(range.endExclusive, 'left'); + if (pos2.isBefore(pos1)) { + return Range.fromPositions(pos2, pos2); + } + return Range.fromPositions(pos1, pos2); } /** @@ -161,8 +153,8 @@ export class LinesSliceCharSequence implements ISequence { } public extendToFullLines(range: OffsetRange): OffsetRange { - const start = findLastMonotonous(this.firstCharOffsetByLine, x => x <= range.start) ?? 0; - const end = findFirstMonotonous(this.firstCharOffsetByLine, x => range.endExclusive <= x) ?? this.elements.length; + const start = findLastMonotonous(this.firstElementOffsetByLineIdx, x => x <= range.start) ?? 0; + const end = findFirstMonotonous(this.firstElementOffsetByLineIdx, x => range.endExclusive <= x) ?? this.elements.length; return new OffsetRange(start, end); } } diff --git a/patched-vscode/src/vs/editor/common/diff/rangeMapping.ts b/patched-vscode/src/vs/editor/common/diff/rangeMapping.ts index f15b2a05..da9c3a49 100644 --- a/patched-vscode/src/vs/editor/common/diff/rangeMapping.ts +++ b/patched-vscode/src/vs/editor/common/diff/rangeMapping.ts @@ -5,6 +5,7 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { LineRange } from 'vs/editor/common/core/lineRange'; +import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { AbstractText, SingleTextEdit } from 'vs/editor/common/core/textEdit'; @@ -118,6 +119,70 @@ export class LineRangeMapping { ); } } + + /** + * This method assumes that the LineRangeMapping describes a valid diff! + * I.e. if one range is empty, the other range cannot be the entire document. + * It avoids various problems when the line range points to non-existing line-numbers. + */ + public toRangeMapping2(original: string[], modified: string[]): RangeMapping { + if (isValidLineNumber(this.original.endLineNumberExclusive, original) + && isValidLineNumber(this.modified.endLineNumberExclusive, modified)) { + return new RangeMapping( + new Range(this.original.startLineNumber, 1, this.original.endLineNumberExclusive, 1), + new Range(this.modified.startLineNumber, 1, this.modified.endLineNumberExclusive, 1), + ); + } + + if (!this.original.isEmpty && !this.modified.isEmpty) { + return new RangeMapping( + Range.fromPositions( + new Position(this.original.startLineNumber, 1), + normalizePosition(new Position(this.original.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER), original) + ), + Range.fromPositions( + new Position(this.modified.startLineNumber, 1), + normalizePosition(new Position(this.modified.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER), modified) + ), + ); + } + + if (this.original.startLineNumber > 1 && this.modified.startLineNumber > 1) { + return new RangeMapping( + Range.fromPositions( + normalizePosition(new Position(this.original.startLineNumber - 1, Number.MAX_SAFE_INTEGER), original), + normalizePosition(new Position(this.original.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER), original) + ), + Range.fromPositions( + normalizePosition(new Position(this.modified.startLineNumber - 1, Number.MAX_SAFE_INTEGER), modified), + normalizePosition(new Position(this.modified.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER), modified) + ), + ); + } + + // Situation now: one range is empty and one range touches the last line and one range starts at line 1. + // I don't think this can happen. + + throw new BugIndicatingError(); + } +} + +function normalizePosition(position: Position, content: string[]): Position { + if (position.lineNumber < 1) { + return new Position(1, 1); + } + if (position.lineNumber > content.length) { + return new Position(content.length, content[content.length - 1].length + 1); + } + const line = content[position.lineNumber - 1]; + if (position.column > line.length + 1) { + return new Position(position.lineNumber, line.length + 1); + } + return position; +} + +function isValidLineNumber(lineNumber: number, lines: string[]): boolean { + return lineNumber >= 1 && lineNumber <= lines.length; } /** @@ -161,6 +226,19 @@ export class DetailedLineRangeMapping extends LineRangeMapping { * Maps a range in the original text model to a range in the modified text model. */ export class RangeMapping { + public static assertSorted(rangeMappings: RangeMapping[]): void { + for (let i = 1; i < rangeMappings.length; i++) { + const previous = rangeMappings[i - 1]; + const current = rangeMappings[i]; + if (!( + previous.originalRange.getEndPosition().isBeforeOrEqual(current.originalRange.getStartPosition()) + && previous.modifiedRange.getEndPosition().isBeforeOrEqual(current.modifiedRange.getStartPosition()) + )) { + throw new BugIndicatingError('Range mappings must be sorted'); + } + } + } + /** * The original range. */ diff --git a/patched-vscode/src/vs/editor/common/languageFeatureRegistry.ts b/patched-vscode/src/vs/editor/common/languageFeatureRegistry.ts index 53c14ac5..32489739 100644 --- a/patched-vscode/src/vs/editor/common/languageFeatureRegistry.ts +++ b/patched-vscode/src/vs/editor/common/languageFeatureRegistry.ts @@ -10,10 +10,10 @@ import { LanguageFilter, LanguageSelector, score } from 'vs/editor/common/langua import { URI } from 'vs/base/common/uri'; interface Entry { - selector: LanguageSelector; - provider: T; + readonly selector: LanguageSelector; + readonly provider: T; _score: number; - _time: number; + readonly _time: number; } function isExclusive(selector: LanguageSelector): boolean { @@ -40,14 +40,16 @@ class MatchCandidate { readonly uri: URI, readonly languageId: string, readonly notebookUri: URI | undefined, - readonly notebookType: string | undefined + readonly notebookType: string | undefined, + readonly recursive: boolean, ) { } equals(other: MatchCandidate): boolean { return this.notebookType === other.notebookType && this.languageId === other.languageId && this.uri.toString() === other.uri.toString() - && this.notebookUri?.toString() === other.notebookUri?.toString(); + && this.notebookUri?.toString() === other.notebookUri?.toString() + && this.recursive === other.recursive; } } @@ -96,7 +98,7 @@ export class LanguageFeatureRegistry { return []; } - this._updateScores(model); + this._updateScores(model, false); const result: T[] = []; // from registry @@ -109,9 +111,13 @@ export class LanguageFeatureRegistry { return result; } - ordered(model: ITextModel): T[] { + allNoModel(): T[] { + return this._entries.map(entry => entry.provider); + } + + ordered(model: ITextModel, recursive = false): T[] { const result: T[] = []; - this._orderedForEach(model, entry => result.push(entry.provider)); + this._orderedForEach(model, recursive, entry => result.push(entry.provider)); return result; } @@ -120,7 +126,7 @@ export class LanguageFeatureRegistry { let lastBucket: T[]; let lastBucketScore: number; - this._orderedForEach(model, entry => { + this._orderedForEach(model, false, entry => { if (lastBucket && lastBucketScore === entry._score) { lastBucket.push(entry.provider); } else { @@ -133,9 +139,9 @@ export class LanguageFeatureRegistry { return result; } - private _orderedForEach(model: ITextModel, callback: (provider: Entry) => any): void { + private _orderedForEach(model: ITextModel, recursive: boolean, callback: (provider: Entry) => any): void { - this._updateScores(model); + this._updateScores(model, recursive); for (const entry of this._entries) { if (entry._score > 0) { @@ -146,15 +152,15 @@ export class LanguageFeatureRegistry { private _lastCandidate: MatchCandidate | undefined; - private _updateScores(model: ITextModel): void { + private _updateScores(model: ITextModel, recursive: boolean): void { const notebookInfo = this._notebookInfoResolver?.(model.uri); // use the uri (scheme, pattern) of the notebook info iff we have one // otherwise it's the model's/document's uri const candidate = notebookInfo - ? new MatchCandidate(model.uri, model.getLanguageId(), notebookInfo.uri, notebookInfo.type) - : new MatchCandidate(model.uri, model.getLanguageId(), undefined, undefined); + ? new MatchCandidate(model.uri, model.getLanguageId(), notebookInfo.uri, notebookInfo.type, recursive) + : new MatchCandidate(model.uri, model.getLanguageId(), undefined, undefined, recursive); if (this._lastCandidate?.equals(candidate)) { // nothing has changed @@ -167,13 +173,17 @@ export class LanguageFeatureRegistry { entry._score = score(entry.selector, candidate.uri, candidate.languageId, shouldSynchronizeModel(model), candidate.notebookUri, candidate.notebookType); if (isExclusive(entry.selector) && entry._score > 0) { - // support for one exclusive selector that overwrites - // any other selector - for (const entry of this._entries) { + if (recursive) { entry._score = 0; + } else { + // support for one exclusive selector that overwrites + // any other selector + for (const entry of this._entries) { + entry._score = 0; + } + entry._score = 1000; + break; } - entry._score = 1000; - break; } } diff --git a/patched-vscode/src/vs/editor/common/languages.ts b/patched-vscode/src/vs/editor/common/languages.ts index 68fbf632..fbb24ec6 100644 --- a/patched-vscode/src/vs/editor/common/languages.ts +++ b/patched-vscode/src/vs/editor/common/languages.ts @@ -82,6 +82,15 @@ export class EncodedTokenizationResult { } } +/** + * An intermediate interface for scaffolding the new tree sitter tokenization support. Not final. + * @internal + */ +export interface ITreeSitterTokenizationSupport { + tokenizeEncoded(lineNumber: number, textModel: model.ITextModel): Uint32Array | undefined; + captureAtPosition(lineNumber: number, column: number, textModel: model.ITextModel): any; +} + /** * @internal */ @@ -687,6 +696,11 @@ export interface InlineCompletionContext { */ readonly triggerKind: InlineCompletionTriggerKind; readonly selectedSuggestionInfo: SelectedSuggestionInfo | undefined; + /** + * @experimental + * @internal + */ + readonly userPrompt?: string | undefined; } export class SelectedSuggestionInfo { @@ -765,6 +779,12 @@ export type InlineCompletionProviderGroupId = string; export interface InlineCompletionsProvider { provideInlineCompletions(model: model.ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; + /** + * @experimental + * @internal + */ + provideInlineEdits?(model: model.ITextModel, range: Range, context: InlineCompletionContext, token: CancellationToken): ProviderResult; + /** * Will be called when an item is shown. * @param updatedInsertText Is useful to understand bracket completion. @@ -1847,6 +1867,11 @@ export interface CommentInput { uri: URI; } +export interface CommentThreadRevealOptions { + preserveFocus: boolean; + focusReply: boolean; +} + /** * @internal */ @@ -1860,7 +1885,7 @@ export interface CommentThread { range: T | undefined; label: string | undefined; contextValue: string | undefined; - comments: Comment[] | undefined; + comments: ReadonlyArray | undefined; onDidChangeComments: Event; collapsibleState?: CommentThreadCollapsibleState; initialCollapsibleState?: CommentThreadCollapsibleState; @@ -2090,17 +2115,17 @@ export interface ITokenizationSupportChangedEvent { /** * @internal */ -export interface ILazyTokenizationSupport { - get tokenizationSupport(): Promise; +export interface ILazyTokenizationSupport { + get tokenizationSupport(): Promise; } /** * @internal */ -export class LazyTokenizationSupport implements IDisposable, ILazyTokenizationSupport { - private _tokenizationSupport: Promise | null = null; +export class LazyTokenizationSupport implements IDisposable, ILazyTokenizationSupport { + private _tokenizationSupport: Promise | null = null; - constructor(private readonly createSupport: () => Promise) { + constructor(private readonly createSupport: () => Promise) { } dispose(): void { @@ -2113,7 +2138,7 @@ export class LazyTokenizationSupport implements IDisposable, ILazyTokenizationSu } } - get tokenizationSupport(): Promise { + get tokenizationSupport(): Promise { if (!this._tokenizationSupport) { this._tokenizationSupport = this.createSupport(); } @@ -2124,7 +2149,7 @@ export class LazyTokenizationSupport implements IDisposable, ILazyTokenizationSu /** * @internal */ -export interface ITokenizationRegistry { +export interface ITokenizationRegistry { /** * An event triggered when: @@ -2142,24 +2167,24 @@ export interface ITokenizationRegistry { /** * Register a tokenization support. */ - register(languageId: string, support: ITokenizationSupport): IDisposable; + register(languageId: string, support: TSupport): IDisposable; /** * Register a tokenization support factory. */ - registerFactory(languageId: string, factory: ILazyTokenizationSupport): IDisposable; + registerFactory(languageId: string, factory: ILazyTokenizationSupport): IDisposable; /** * Get or create the tokenization support for a language. * Returns `null` if not found. */ - getOrCreate(languageId: string): Promise; + getOrCreate(languageId: string): Promise; /** * Get the tokenization support for a language. * Returns `null` if not found. */ - get(languageId: string): ITokenizationSupport | null; + get(languageId: string): TSupport | null; /** * Returns false if a factory is still pending. @@ -2179,8 +2204,12 @@ export interface ITokenizationRegistry { /** * @internal */ -export const TokenizationRegistry: ITokenizationRegistry = new TokenizationRegistryImpl(); +export const TokenizationRegistry: ITokenizationRegistry = new TokenizationRegistryImpl(); +/** + * @internal + */ +export const TreeSitterTokenizationRegistry: ITokenizationRegistry = new TokenizationRegistryImpl(); /** * @internal @@ -2209,6 +2238,14 @@ export interface DocumentDropEdit { additionalEdit?: WorkspaceEdit; } +/** + * @internal + */ +export interface DocumentDropEditsSession { + edits: readonly DocumentDropEdit[]; + dispose(): void; +} + /** * @internal */ @@ -2216,7 +2253,7 @@ export interface DocumentDropEditProvider { readonly id?: string; readonly dropMimeTypes?: readonly string[]; - provideDocumentDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): ProviderResult; + provideDocumentDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): ProviderResult; resolveDocumentDropEdit?(edit: DocumentDropEdit, token: CancellationToken): Promise; } @@ -2232,13 +2269,17 @@ export interface MappedEditsContext { } export interface MappedEditsProvider { + /** + * @internal + */ + readonly displayName: string; // internal /** * Provider maps code blocks from the chat into a workspace edit. * * @param document The document to provide mapped edits for. * @param codeBlocks Code blocks that come from an LLM's reply. - * "Insert at cursor" in the panel chat only sends one edit that the user clicks on, but inline chat can send multiple blocks and let the lang server decide what to do with them. + * "Apply in Editor" in the panel chat only sends one edit that the user clicks on, but inline chat can send multiple blocks and let the lang server decide what to do with them. * @param context The context for providing mapped edits. * @param token A cancellation token. * @returns A provider result of text edits. diff --git a/patched-vscode/src/vs/editor/common/languages/autoIndent.ts b/patched-vscode/src/vs/editor/common/languages/autoIndent.ts index 680d3a7d..5c643b4f 100644 --- a/patched-vscode/src/vs/editor/common/languages/autoIndent.ts +++ b/patched-vscode/src/vs/editor/common/languages/autoIndent.ts @@ -12,6 +12,7 @@ import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions' import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; import { IndentationContextProcessor, isLanguageDifferentFromLineStart, ProcessedIndentRulesSupport } from 'vs/editor/common/languages/supports/indentationLineProcessor'; +import { CursorConfiguration } from 'vs/editor/common/cursorCommon'; export interface IVirtualModel { tokenization: { @@ -357,13 +358,14 @@ export function getIndentForEnter( * this line doesn't match decreaseIndentPattern, we should not adjust the indentation. */ export function getIndentActionForType( - autoIndent: EditorAutoIndentStrategy, + cursorConfig: CursorConfiguration, model: ITextModel, range: Range, ch: string, indentConverter: IIndentConverter, languageConfigurationService: ILanguageConfigurationService ): string | null { + const autoIndent = cursorConfig.autoIndent; if (autoIndent < EditorAutoIndentStrategy.Full) { return null; } @@ -404,6 +406,29 @@ export function getIndentActionForType( return indentation; } + const previousLineNumber = range.startLineNumber - 1; + if (previousLineNumber > 0) { + const previousLine = model.getLineContent(previousLineNumber); + if (indentRulesSupport.shouldIndentNextLine(previousLine) && indentRulesSupport.shouldIncrease(textAroundRangeWithCharacter)) { + const inheritedIndentationData = getInheritIndentForLine(autoIndent, model, range.startLineNumber, false, languageConfigurationService); + const inheritedIndentation = inheritedIndentationData?.indentation; + if (inheritedIndentation !== undefined) { + const currentLine = model.getLineContent(range.startLineNumber); + const actualCurrentIndentation = strings.getLeadingWhitespace(currentLine); + const inferredCurrentIndentation = indentConverter.shiftIndent(inheritedIndentation); + // If the inferred current indentation is not equal to the actual current indentation, then the indentation has been intentionally changed, in that case keep it + const inferredIndentationEqualsActual = inferredCurrentIndentation === actualCurrentIndentation; + const textAroundRangeContainsOnlyWhitespace = /^\s*$/.test(textAroundRange); + const autoClosingPairs = cursorConfig.autoClosingPairs.autoClosingPairsOpenByEnd.get(ch); + const autoClosingPairExists = autoClosingPairs && autoClosingPairs.length > 0; + const isChFirstNonWhitespaceCharacterAndInAutoClosingPair = autoClosingPairExists && textAroundRangeContainsOnlyWhitespace; + if (inferredIndentationEqualsActual && isChFirstNonWhitespaceCharacterAndInAutoClosingPair) { + return inheritedIndentation; + } + } + } + } + return null; } diff --git a/patched-vscode/src/vs/editor/common/languages/highlights/typescript.scm b/patched-vscode/src/vs/editor/common/languages/highlights/typescript.scm new file mode 100644 index 00000000..e50bc9de --- /dev/null +++ b/patched-vscode/src/vs/editor/common/languages/highlights/typescript.scm @@ -0,0 +1,271 @@ +; Order matters! Place higher precedence first. +; Adapted from https://github.com/zed-industries/zed/blob/main/crates/languages/src/typescript/highlights.scm + +; Language constants + +[ + (true) + (false) + (null) + (undefined) +] @constant.language + +(namespace_import + "*" @constant.language) + +; Keywords + +[ + "delete" + "in" + "infer" + "instanceof" + "keyof" + "of" + "typeof" +] @keyword.operator.expression + +[ + "as" + "await" + "break" + "case" + "catch" + "continue" + "default" + "do" + "else" + "export" + "finally" + "for" + "from" + "if" + "import" + "require" + "return" + "satisfies" + "switch" + "throw" + "try" + "type" + "while" + "yield" +] @keyword.control + +[ + "abstract" + "async" + "declare" + "extends" + "implements" + "override" + "private" + "protected" + "public" + "readonly" + "static" +] @storage.modifier + +[ + "=>" + "class" + "const" + "enum" + "function" + "get" + "interface" + "let" + "namespace" + "set" + "var" +] @storage.type + +[ + "debugger" + "target" + "with" +] @keyword + +; TODO: works in the playground but not here +(regex_flags) @keyword + +[ + "void" +] @support.type + +[ + "new" +] @keyword.operator.new + +; Tokens + +[ + ";" + "?." + "." + "," + ":" + "?" +] @punctuation.delimiter + +[ + "-" + "--" + "-=" + "+" + "++" + "+=" + "*" + "*=" + "**" + "**=" + "/" + "/=" + "%" + "%=" + "<" + "<=" + "<<" + "<<=" + "=" + "==" + "===" + "!" + "!=" + "!==" + "=>" + ">" + ">=" + ">>" + ">>=" + ">>>" + ">>>=" + "~" + "^" + "&" + "|" + "^=" + "&=" + "|=" + "&&" + "||" + "??" + "&&=" + "||=" + "??=" +] @keyword.operator + +; Special identifiers + +(type_identifier) @entity.name.type +(predefined_type) @support.type + +(("const") + (variable_declarator + name: (identifier) @variable.other.constant)) + +([ + (identifier) + (shorthand_property_identifier) + (shorthand_property_identifier_pattern)] @variable.other.constant + (#match? @variable.other.constant "^[A-Z][A-Z_]+$")) + +(extends_clause + value: (identifier) @entity.other.inherited-class) + +; Function and method calls + +(call_expression + function: (identifier) @entity.name.function) + +(call_expression + function: (member_expression + property: (property_identifier) @entity.name.function)) + +(new_expression + constructor: (identifier) @entity.name.function) + +; Function and method definitions + +(function_expression + name: (identifier) @entity.name.function) +(function_declaration + name: (identifier) @entity.name.function) +(method_definition + name: (property_identifier) @storage.type + (#eq? @storage.type "constructor")) +(method_definition + name: (property_identifier) @entity.name.function) +(method_signature + name: (property_identifier) @entity.name.function) + +(pair + key: (property_identifier) @entity.name.function + value: [(function_expression) (arrow_function)]) + +(assignment_expression + left: (member_expression + property: (property_identifier) @entity.name.function) + right: [(function_expression) (arrow_function)]) + +(variable_declarator + name: (identifier) @entity.name.function + value: [(function_expression) (arrow_function)]) + +(assignment_expression + left: (identifier) @entity.name.function + right: [(function_expression) (arrow_function)]) + +; Properties + +(member_expression + object: (this) + property: (property_identifier) @variable) + +(member_expression + property: (property_identifier) @variable.other.constant + (#match? @variable.other.constant "^[A-Z][A-Z_]+$")) + +[ + (property_identifier) + (shorthand_property_identifier) + (shorthand_property_identifier_pattern)] @variable + +; Variables + +(identifier) @variable + +; Template TODO: These don't seem to be working + +(template_substitution + "${" @punctuation.definition.template-expression.begin + "}" @punctuation.definition.template-expression.end) + +(template_type + "${" @punctuation.definition.template-expression.begin + "}" @punctuation.definition.template-expression.end) + +(type_arguments + "<" @punctuation.bracket + ">" @punctuation.bracket) + +; Literals + +(this) @variable.language +(super) @variable.language + +(comment) @comment + +; TODO: This doesn't seem to be working +(escape_sequence) @constant.character.escape + +[ + (string) + (template_string) + (template_literal_type) +] @string + +; NOTE: the typescript grammar doesn't break regex into nice parts so as to capture parts of it separately +(regex) @string.regexp +(number) @constant.numeric + diff --git a/patched-vscode/src/vs/editor/common/languages/supports/richEditBrackets.ts b/patched-vscode/src/vs/editor/common/languages/supports/richEditBrackets.ts index 7733719f..abb30850 100644 --- a/patched-vscode/src/vs/editor/common/languages/supports/richEditBrackets.ts +++ b/patched-vscode/src/vs/editor/common/languages/supports/richEditBrackets.ts @@ -7,7 +7,6 @@ import * as strings from 'vs/base/common/strings'; import * as stringBuilder from 'vs/editor/common/core/stringBuilder'; import { Range } from 'vs/editor/common/core/range'; import { CharacterPair } from 'vs/editor/common/languages/languageConfiguration'; -import { RegExpOptions } from 'vs/base/common/strings'; interface InternalBracket { open: string[]; @@ -409,7 +408,7 @@ function prepareBracketForRegExp(str: string): string { return (insertWordBoundaries ? `\\b${str}\\b` : str); } -export function createBracketOrRegExp(pieces: string[], options?: RegExpOptions): RegExp { +export function createBracketOrRegExp(pieces: string[], options?: strings.RegExpOptions): RegExp { const regexStr = `(${pieces.map(prepareBracketForRegExp).join(')|(')})`; return strings.createRegExp(regexStr, true, options); } diff --git a/patched-vscode/src/vs/editor/common/model.ts b/patched-vscode/src/vs/editor/common/model.ts index e21aa7d6..fb48c38b 100644 --- a/patched-vscode/src/vs/editor/common/model.ts +++ b/patched-vscode/src/vs/editor/common/model.ts @@ -1428,9 +1428,16 @@ export interface IReadonlyTextBuffer { getLineCharCode(lineNumber: number, index: number): number; getCharCode(offset: number): number; getLineLength(lineNumber: number): number; + getLineMinColumn(lineNumber: number): number; + getLineMaxColumn(lineNumber: number): number; getLineFirstNonWhitespaceColumn(lineNumber: number): number; getLineLastNonWhitespaceColumn(lineNumber: number): number; findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[]; + + /** + * Get nearest chunk of text after `offset` in the text buffer. + */ + getNearestChunk(offset: number): string; } /** diff --git a/patched-vscode/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts b/patched-vscode/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts index 470a16f0..3d3fe2e3 100644 --- a/patched-vscode/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts +++ b/patched-vscode/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts @@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { ILanguageConfigurationService, LanguageConfigurationServiceChangeEvent } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ignoreBracketsInToken } from 'vs/editor/common/languages/supports'; import { LanguageBracketsConfiguration } from 'vs/editor/common/languages/supports/languageBracketsConfiguration'; import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/common/languages/supports/richEditBrackets'; @@ -36,19 +36,17 @@ export class BracketPairsTextModelPart extends Disposable implements IBracketPai private readonly languageConfigurationService: ILanguageConfigurationService ) { super(); - - this._register( - this.languageConfigurationService.onDidChange(e => { - if (!e.languageId || this.bracketPairsTree.value?.object.didLanguageChange(e.languageId)) { - this.bracketPairsTree.clear(); - this.updateBracketPairsTree(); - } - }) - ); } //#region TextModel events + public handleLanguageConfigurationServiceChange(e: LanguageConfigurationServiceChangeEvent): void { + if (!e.languageId || this.bracketPairsTree.value?.object.didLanguageChange(e.languageId)) { + this.bracketPairsTree.clear(); + this.updateBracketPairsTree(); + } + } + public handleDidChangeOptions(e: IModelOptionsChangedEvent): void { this.bracketPairsTree.clear(); this.updateBracketPairsTree(); diff --git a/patched-vscode/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/patched-vscode/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index b75d0d75..24f90651 100644 --- a/patched-vscode/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/patched-vscode/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -666,6 +666,27 @@ export class PieceTreeBase { return this._getCharCode(nodePos); } + public getNearestChunk(offset: number): string { + const nodePos = this.nodeAt(offset); + if (nodePos.remainder === nodePos.node.piece.length) { + // the offset is at the head of next node. + const matchingNode = nodePos.node.next(); + if (!matchingNode || matchingNode === SENTINEL) { + return ''; + } + + const buffer = this._buffers[matchingNode.piece.bufferIndex]; + const startOffset = this.offsetInBuffer(matchingNode.piece.bufferIndex, matchingNode.piece.start); + return buffer.buffer.substring(startOffset, startOffset + matchingNode.piece.length); + } else { + const buffer = this._buffers[nodePos.node.piece.bufferIndex]; + const startOffset = this.offsetInBuffer(nodePos.node.piece.bufferIndex, nodePos.node.piece.start); + const targetOffset = startOffset + nodePos.remainder; + const targetEnd = startOffset + nodePos.node.piece.length; + return buffer.buffer.substring(targetOffset, targetEnd); + } + } + public findMatchesInNode(node: TreeNode, searcher: Searcher, startLineNumber: number, startColumn: number, startCursor: BufferCursor, endCursor: BufferCursor, searchData: SearchData, captureMatches: boolean, limitResultCount: number, resultLen: number, result: FindMatch[]) { const buffer = this._buffers[node.piece.bufferIndex]; const startOffsetInBuffer = this.offsetInBuffer(node.piece.bufferIndex, node.piece.start); diff --git a/patched-vscode/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/patched-vscode/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index 12d7e0b0..a369298c 100644 --- a/patched-vscode/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/patched-vscode/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -167,6 +167,10 @@ export class PieceTreeTextBuffer extends Disposable implements ITextBuffer { return this.getValueLengthInRange(range, eol); } + public getNearestChunk(offset: number): string { + return this._pieceTree.getNearestChunk(offset); + } + public getLength(): number { return this._pieceTree.getLength(); } diff --git a/patched-vscode/src/vs/editor/common/model/textModel.ts b/patched-vscode/src/vs/editor/common/model/textModel.ts index 626217e8..c5556c15 100644 --- a/patched-vscode/src/vs/editor/common/model/textModel.ts +++ b/patched-vscode/src/vs/editor/common/model/textModel.ts @@ -18,7 +18,6 @@ import { URI } from 'vs/base/common/uri'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { countEOL } from 'vs/editor/common/core/eolCounter'; import { normalizeIndentation } from 'vs/editor/common/core/indentation'; -import { LineRange } from 'vs/editor/common/core/lineRange'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -39,10 +38,12 @@ import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/ import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { SearchParams, TextModelSearch } from 'vs/editor/common/model/textModelSearch'; import { TokenizationTextModelPart } from 'vs/editor/common/model/tokenizationTextModelPart'; +import { AttachedViews } from 'vs/editor/common/model/tokens'; import { IBracketPairsTextModelPart } from 'vs/editor/common/textModelBracketPairs'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelOptionsChangedEvent, InternalModelContentChangeEvent, LineInjectedText, ModelInjectedTextChangedEvent, ModelRawChange, ModelRawContentChangedEvent, ModelRawEOLChanged, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/textModelEvents'; import { IGuidesTextModelPart } from 'vs/editor/common/textModelGuides'; import { ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextModelPart'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; @@ -299,6 +300,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, @ILanguageService private readonly _languageService: ILanguageService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -327,13 +329,11 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati this._bracketPairs = this._register(new BracketPairsTextModelPart(this, this._languageConfigurationService)); this._guidesTextModelPart = this._register(new GuidesTextModelPart(this, this._languageConfigurationService)); this._decorationProvider = this._register(new ColorizedBracketPairsDecorationProvider(this)); - this._tokenizationTextModelPart = new TokenizationTextModelPart( - this._languageService, - this._languageConfigurationService, + this._tokenizationTextModelPart = this.instantiationService.createInstance(TokenizationTextModelPart, this, this._bracketPairs, languageId, - this._attachedViews, + this._attachedViews ); const bufferLineCount = this._buffer.getLineCount(); @@ -381,6 +381,11 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati })); this._languageService.requestRichLanguageFeatures(languageId); + + this._register(this._languageConfigurationService.onDidChange(e => { + this._bracketPairs.handleLanguageConfigurationServiceChange(e); + this._tokenizationTextModelPart.handleLanguageConfigurationServiceChange(e); + })); } public override dispose(): void { @@ -413,7 +418,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati private _assertNotDisposed(): void { if (this._isDisposed) { - throw new Error('Model is disposed!'); + throw new BugIndicatingError('Model is disposed!'); } } @@ -1984,7 +1989,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati } } -function indentOfLine(line: string): number { +export function indentOfLine(line: string): number { let indent = 0; for (const c of line) { if (c === ' ' || c === '\t') { @@ -2523,43 +2528,3 @@ class DidChangeContentEmitter extends Disposable { this._slowEmitter.fire(e); } } - -/** - * @internal - */ -export class AttachedViews { - private readonly _onDidChangeVisibleRanges = new Emitter<{ view: model.IAttachedView; state: IAttachedViewState | undefined }>(); - public readonly onDidChangeVisibleRanges = this._onDidChangeVisibleRanges.event; - - private readonly _views = new Set(); - - public attachView(): model.IAttachedView { - const view = new AttachedViewImpl((state) => { - this._onDidChangeVisibleRanges.fire({ view, state }); - }); - this._views.add(view); - return view; - } - - public detachView(view: model.IAttachedView): void { - this._views.delete(view as AttachedViewImpl); - this._onDidChangeVisibleRanges.fire({ view, state: undefined }); - } -} - -/** - * @internal - */ -export interface IAttachedViewState { - readonly visibleLineRanges: readonly LineRange[]; - readonly stabilized: boolean; -} - -class AttachedViewImpl implements model.IAttachedView { - constructor(private readonly handleStateChange: (state: IAttachedViewState) => void) { } - - setVisibleLines(visibleLines: { startLineNumber: number; endLineNumber: number }[], stabilized: boolean): void { - const visibleLineRanges = visibleLines.map((line) => new LineRange(line.startLineNumber, line.endLineNumber + 1)); - this.handleStateChange({ visibleLineRanges, stabilized }); - } -} diff --git a/patched-vscode/src/vs/editor/common/model/tokenizationTextModelPart.ts b/patched-vscode/src/vs/editor/common/model/tokenizationTextModelPart.ts index 804f63c6..47a66565 100644 --- a/patched-vscode/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/patched-vscode/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -3,26 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { equals } from 'vs/base/common/arrays'; -import { RunOnceScheduler } from 'vs/base/common/async'; import { CharCode } from 'vs/base/common/charCode'; import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableMap, MutableDisposable } from 'vs/base/common/lifecycle'; +import { DisposableMap, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { countEOL } from 'vs/editor/common/core/eolCounter'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IWordAtPosition, getWordAtText } from 'vs/editor/common/core/wordHelper'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; -import { IBackgroundTokenizationStore, IBackgroundTokenizer, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; +import { IBackgroundTokenizationStore, IBackgroundTokenizer, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry, TreeSitterTokenizationRegistry } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ILanguageConfigurationService, ResolvedLanguageConfiguration } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { ILanguageConfigurationService, LanguageConfigurationServiceChangeEvent, ResolvedLanguageConfiguration } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IAttachedView } from 'vs/editor/common/model'; import { BracketPairsTextModelPart } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl'; -import { AttachedViews, IAttachedViewState, TextModel } from 'vs/editor/common/model/textModel'; +import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModelPart } from 'vs/editor/common/model/textModelPart'; import { DefaultBackgroundTokenizer, TokenizerWithStateStoreAndTextModel, TrackingTokenizationStateStore } from 'vs/editor/common/model/textModelTokens'; +import { AbstractTokens, AttachedViewHandler, AttachedViews } from 'vs/editor/common/model/tokens'; +import { TreeSitterTokens } from 'vs/editor/common/model/treeSitterTokens'; +import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; import { BackgroundTokenizationState, ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextModelPart'; import { ContiguousMultilineTokens } from 'vs/editor/common/tokens/contiguousMultilineTokens'; @@ -44,15 +45,17 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz private readonly _onDidChangeTokens: Emitter = this._register(new Emitter()); public readonly onDidChangeTokens: Event = this._onDidChangeTokens.event; - private readonly grammarTokens = this._register(new GrammarTokens(this._languageService.languageIdCodec, this._textModel, () => this._languageId, this._attachedViews)); + private _tokens!: AbstractTokens; + private readonly _tokensDisposables: DisposableStore = this._register(new DisposableStore()); constructor( - private readonly _languageService: ILanguageService, - private readonly _languageConfigurationService: ILanguageConfigurationService, private readonly _textModel: TextModel, private readonly _bracketPairsTextModelPart: BracketPairsTextModelPart, private _languageId: string, private readonly _attachedViews: AttachedViews, + @ILanguageService private readonly _languageService: ILanguageService, + @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, + @ITreeSitterParserService private readonly _treeSitterService: ITreeSitterParserService, ) { super(); @@ -62,13 +65,51 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz } })); - this._register(this.grammarTokens.onDidChangeTokens(e => { + // We just look at registry changes to determine whether to use tree sitter. + // This means that removing a language from the setting will not cause a switch to textmate and will require a reload. + // Adding a language to the setting will not need a reload, however. + this._register(Event.filter(TreeSitterTokenizationRegistry.onDidChange, (e) => e.changedLanguages.includes(this._languageId))(() => { + this.createPreferredTokenProvider(); + })); + this.createPreferredTokenProvider(); + } + + private createGrammarTokens() { + return this._register(new GrammarTokens(this._languageService.languageIdCodec, this._textModel, () => this._languageId, this._attachedViews)); + } + + private createTreeSitterTokens(): AbstractTokens { + return this._register(new TreeSitterTokens(this._treeSitterService, this._languageService.languageIdCodec, this._textModel, () => this._languageId)); + } + + private createTokens(useTreeSitter: boolean): void { + const needsReset = this._tokens !== undefined; + this._tokens?.dispose(); + this._tokens = useTreeSitter ? this.createTreeSitterTokens() : this.createGrammarTokens(); + this._tokensDisposables.clear(); + this._tokensDisposables.add(this._tokens.onDidChangeTokens(e => { this._emitModelTokensChangedEvent(e); })); - this._register(this.grammarTokens.onDidChangeBackgroundTokenizationState(e => { + this._tokensDisposables.add(this._tokens.onDidChangeBackgroundTokenizationState(e => { this._bracketPairsTextModelPart.handleDidChangeBackgroundTokenizationState(); })); + if (needsReset) { + // We need to reset the tokenization, as the new token provider otherwise won't have a chance to provide tokens until some action happens in the editor. + this._tokens.resetTokenization(); + } + } + + private createPreferredTokenProvider() { + if (TreeSitterTokenizationRegistry.get(this._languageId)) { + if (!(this._tokens instanceof TreeSitterTokens)) { + this.createTokens(true); + } + } else { + if (!(this._tokens instanceof GrammarTokens)) { + this.createTokens(false); + } + } } _hasListeners(): boolean { @@ -77,6 +118,12 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz || this._onDidChangeTokens.hasListeners()); } + public handleLanguageConfigurationServiceChange(e: LanguageConfigurationServiceChangeEvent): void { + if (e.affects(this._languageId)) { + this._onDidChangeLanguageConfiguration.fire({}); + } + } + public handleDidChangeContent(e: IModelContentChangedEvent): void { if (e.isFlush) { this._semanticTokens.flush(); @@ -94,11 +141,11 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz } } - this.grammarTokens.handleDidChangeContent(e); + this._tokens.handleDidChangeContent(e); } public handleDidChangeAttached(): void { - this.grammarTokens.handleDidChangeAttached(); + this._tokens.handleDidChangeAttached(); } /** @@ -106,7 +153,7 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz */ public getLineTokens(lineNumber: number): LineTokens { this.validateLineNumber(lineNumber); - const syntacticTokens = this.grammarTokens.getLineTokens(lineNumber); + const syntacticTokens = this._tokens.getLineTokens(lineNumber); return this._semanticTokens.addSparseTokens(lineNumber, syntacticTokens); } @@ -126,43 +173,43 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz } public get hasTokens(): boolean { - return this.grammarTokens.hasTokens; + return this._tokens.hasTokens; } public resetTokenization() { - this.grammarTokens.resetTokenization(); + this._tokens.resetTokenization(); } public get backgroundTokenizationState() { - return this.grammarTokens.backgroundTokenizationState; + return this._tokens.backgroundTokenizationState; } public forceTokenization(lineNumber: number): void { this.validateLineNumber(lineNumber); - this.grammarTokens.forceTokenization(lineNumber); + this._tokens.forceTokenization(lineNumber); } public hasAccurateTokensForLine(lineNumber: number): boolean { this.validateLineNumber(lineNumber); - return this.grammarTokens.hasAccurateTokensForLine(lineNumber); + return this._tokens.hasAccurateTokensForLine(lineNumber); } public isCheapToTokenize(lineNumber: number): boolean { this.validateLineNumber(lineNumber); - return this.grammarTokens.isCheapToTokenize(lineNumber); + return this._tokens.isCheapToTokenize(lineNumber); } public tokenizeIfCheap(lineNumber: number): void { this.validateLineNumber(lineNumber); - this.grammarTokens.tokenizeIfCheap(lineNumber); + this._tokens.tokenizeIfCheap(lineNumber); } public getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType { - return this.grammarTokens.getTokenTypeIfInsertingCharacter(lineNumber, column, character); + return this._tokens.getTokenTypeIfInsertingCharacter(lineNumber, column, character); } public tokenizeLineWithEdit(position: IPosition, length: number, newText: string): LineTokens | null { - return this.grammarTokens.tokenizeLineWithEdit(position, length, newText); + return this._tokens.tokenizeLineWithEdit(position, length, newText); } // #endregion @@ -327,7 +374,8 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz this._languageId = languageId; this._bracketPairsTextModelPart.handleDidChangeLanguage(e); - this.grammarTokens.resetTokenization(); + this._tokens.resetTokenization(); + this.createPreferredTokenProvider(); this._onDidChangeLanguage.fire(e); this._onDidChangeLanguageConfiguration.fire({}); } @@ -335,7 +383,7 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz // #endregion } -class GrammarTokens extends Disposable { +class GrammarTokens extends AbstractTokens { private _tokenizer: TokenizerWithStateStoreAndTextModel | null = null; private _defaultBackgroundTokenizer: DefaultBackgroundTokenizer | null = null; private readonly _backgroundTokenizer = this._register(new MutableDisposable()); @@ -346,28 +394,15 @@ class GrammarTokens extends Disposable { private readonly _debugBackgroundTokenizer = this._register(new MutableDisposable()); - private _backgroundTokenizationState = BackgroundTokenizationState.InProgress; - public get backgroundTokenizationState(): BackgroundTokenizationState { - return this._backgroundTokenizationState; - } - - private readonly _onDidChangeBackgroundTokenizationState = this._register(new Emitter()); - /** @internal, should not be exposed by the text model! */ - public readonly onDidChangeBackgroundTokenizationState: Event = this._onDidChangeBackgroundTokenizationState.event; - - private readonly _onDidChangeTokens = this._register(new Emitter()); - /** @internal, should not be exposed by the text model! */ - public readonly onDidChangeTokens: Event = this._onDidChangeTokens.event; - private readonly _attachedViewStates = this._register(new DisposableMap()); constructor( - private readonly _languageIdCodec: ILanguageIdCodec, - private readonly _textModel: TextModel, - private getLanguageId: () => string, + languageIdCodec: ILanguageIdCodec, + textModel: TextModel, + getLanguageId: () => string, attachedViews: AttachedViews, ) { - super(); + super(languageIdCodec, textModel, getLanguageId); this._register(TokenizationRegistry.onDidChange((e) => { const languageId = this.getLanguageId(); @@ -587,12 +622,6 @@ class GrammarTokens extends Disposable { return this._tokenizer.isCheapToTokenize(lineNumber); } - public tokenizeIfCheap(lineNumber: number): void { - if (this.isCheapToTokenize(lineNumber)) { - this.forceTokenization(lineNumber); - } - } - public getLineTokens(lineNumber: number): LineTokens { const lineText = this._textModel.getLineContent(lineNumber); const result = this._tokens.getTokens( @@ -639,33 +668,3 @@ class GrammarTokens extends Disposable { return this._tokens.hasTokens; } } - -class AttachedViewHandler extends Disposable { - private readonly runner = this._register(new RunOnceScheduler(() => this.update(), 50)); - - private _computedLineRanges: readonly LineRange[] = []; - private _lineRanges: readonly LineRange[] = []; - public get lineRanges(): readonly LineRange[] { return this._lineRanges; } - - constructor(private readonly _refreshTokens: () => void) { - super(); - } - - private update(): void { - if (equals(this._computedLineRanges, this._lineRanges, (a, b) => a.equals(b))) { - return; - } - this._computedLineRanges = this._lineRanges; - this._refreshTokens(); - } - - public handleStateChange(state: IAttachedViewState): void { - this._lineRanges = state.visibleLineRanges; - if (state.stabilized) { - this.runner.cancel(); - this.update(); - } else { - this.runner.schedule(); - } - } -} diff --git a/patched-vscode/src/vs/editor/common/model/tokens.ts b/patched-vscode/src/vs/editor/common/model/tokens.ts new file mode 100644 index 00000000..da46f267 --- /dev/null +++ b/patched-vscode/src/vs/editor/common/model/tokens.ts @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { equals } from 'vs/base/common/arrays'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { IPosition } from 'vs/editor/common/core/position'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; +import { ILanguageIdCodec } from 'vs/editor/common/languages'; +import { IAttachedView } from 'vs/editor/common/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { IModelContentChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; +import { BackgroundTokenizationState } from 'vs/editor/common/tokenizationTextModelPart'; +import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; + +/** + * @internal + */ +export class AttachedViews { + private readonly _onDidChangeVisibleRanges = new Emitter<{ view: IAttachedView; state: IAttachedViewState | undefined }>(); + public readonly onDidChangeVisibleRanges = this._onDidChangeVisibleRanges.event; + + private readonly _views = new Set(); + + public attachView(): IAttachedView { + const view = new AttachedViewImpl((state) => { + this._onDidChangeVisibleRanges.fire({ view, state }); + }); + this._views.add(view); + return view; + } + + public detachView(view: IAttachedView): void { + this._views.delete(view as AttachedViewImpl); + this._onDidChangeVisibleRanges.fire({ view, state: undefined }); + } +} + +/** + * @internal + */ +export interface IAttachedViewState { + readonly visibleLineRanges: readonly LineRange[]; + readonly stabilized: boolean; +} + +class AttachedViewImpl implements IAttachedView { + constructor(private readonly handleStateChange: (state: IAttachedViewState) => void) { } + + setVisibleLines(visibleLines: { startLineNumber: number; endLineNumber: number }[], stabilized: boolean): void { + const visibleLineRanges = visibleLines.map((line) => new LineRange(line.startLineNumber, line.endLineNumber + 1)); + this.handleStateChange({ visibleLineRanges, stabilized }); + } +} + + +export class AttachedViewHandler extends Disposable { + private readonly runner = this._register(new RunOnceScheduler(() => this.update(), 50)); + + private _computedLineRanges: readonly LineRange[] = []; + private _lineRanges: readonly LineRange[] = []; + public get lineRanges(): readonly LineRange[] { return this._lineRanges; } + + constructor(private readonly _refreshTokens: () => void) { + super(); + } + + private update(): void { + if (equals(this._computedLineRanges, this._lineRanges, (a, b) => a.equals(b))) { + return; + } + this._computedLineRanges = this._lineRanges; + this._refreshTokens(); + } + + public handleStateChange(state: IAttachedViewState): void { + this._lineRanges = state.visibleLineRanges; + if (state.stabilized) { + this.runner.cancel(); + this.update(); + } else { + this.runner.schedule(); + } + } +} + +export abstract class AbstractTokens extends Disposable { + protected _backgroundTokenizationState = BackgroundTokenizationState.InProgress; + public get backgroundTokenizationState(): BackgroundTokenizationState { + return this._backgroundTokenizationState; + } + + protected readonly _onDidChangeBackgroundTokenizationState = this._register(new Emitter()); + /** @internal, should not be exposed by the text model! */ + public readonly onDidChangeBackgroundTokenizationState: Event = this._onDidChangeBackgroundTokenizationState.event; + + protected readonly _onDidChangeTokens = this._register(new Emitter()); + /** @internal, should not be exposed by the text model! */ + public readonly onDidChangeTokens: Event = this._onDidChangeTokens.event; + + constructor( + protected readonly _languageIdCodec: ILanguageIdCodec, + protected readonly _textModel: TextModel, + protected getLanguageId: () => string, + ) { + super(); + } + + public abstract resetTokenization(fireTokenChangeEvent?: boolean): void; + + public abstract handleDidChangeAttached(): void; + + public abstract handleDidChangeContent(e: IModelContentChangedEvent): void; + + public abstract forceTokenization(lineNumber: number): void; + + public abstract hasAccurateTokensForLine(lineNumber: number): boolean; + + public abstract isCheapToTokenize(lineNumber: number): boolean; + + public tokenizeIfCheap(lineNumber: number): void { + if (this.isCheapToTokenize(lineNumber)) { + this.forceTokenization(lineNumber); + } + } + + public abstract getLineTokens(lineNumber: number): LineTokens; + + public abstract getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType; + + public abstract tokenizeLineWithEdit(position: IPosition, length: number, newText: string): LineTokens | null; + + public abstract get hasTokens(): boolean; +} diff --git a/patched-vscode/src/vs/editor/common/model/treeSitterTokens.ts b/patched-vscode/src/vs/editor/common/model/treeSitterTokens.ts new file mode 100644 index 00000000..2da4bdc8 --- /dev/null +++ b/patched-vscode/src/vs/editor/common/model/treeSitterTokens.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILanguageIdCodec, ITreeSitterTokenizationSupport, TreeSitterTokenizationRegistry } from 'vs/editor/common/languages'; +import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; +import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; +import { AbstractTokens } from 'vs/editor/common/model/tokens'; +import { IPosition } from 'vs/editor/common/core/position'; + +export class TreeSitterTokens extends AbstractTokens { + private _tokenizationSupport: ITreeSitterTokenizationSupport | null = null; + private _lastLanguageId: string | undefined; + + constructor(private readonly _treeSitterService: ITreeSitterParserService, + languageIdCodec: ILanguageIdCodec, + textModel: TextModel, + languageId: () => string) { + super(languageIdCodec, textModel, languageId); + + this._initialize(); + } + + private _initialize() { + const newLanguage = this.getLanguageId(); + if (!this._tokenizationSupport || this._lastLanguageId !== newLanguage) { + this._lastLanguageId = newLanguage; + this._tokenizationSupport = TreeSitterTokenizationRegistry.get(newLanguage); + } + } + + public getLineTokens(lineNumber: number): LineTokens { + const content = this._textModel.getLineContent(lineNumber); + if (this._tokenizationSupport) { + const rawTokens = this._tokenizationSupport.tokenizeEncoded(lineNumber, this._textModel); + if (rawTokens) { + return new LineTokens(rawTokens, content, this._languageIdCodec); + } + } + return LineTokens.createEmpty(content, this._languageIdCodec); + } + + public resetTokenization(fireTokenChangeEvent: boolean = true): void { + if (fireTokenChangeEvent) { + this._onDidChangeTokens.fire({ + semanticTokensApplied: false, + ranges: [ + { + fromLineNumber: 1, + toLineNumber: this._textModel.getLineCount(), + }, + ], + }); + } + this._initialize(); + } + + public override handleDidChangeAttached(): void { + // TODO @alexr00 implement for background tokenization + } + + public override handleDidChangeContent(e: IModelContentChangedEvent): void { + if (e.isFlush) { + // Don't fire the event, as the view might not have got the text change event yet + this.resetTokenization(false); + } + } + + public override forceTokenization(lineNumber: number): void { + // TODO @alexr00 implement + } + + public override hasAccurateTokensForLine(lineNumber: number): boolean { + // TODO @alexr00 update for background tokenization + return true; + } + + public override isCheapToTokenize(lineNumber: number): boolean { + // TODO @alexr00 update for background tokenization + return true; + } + + public override getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType { + // TODO @alexr00 implement once we have custom parsing and don't just feed in the whole text model value + return StandardTokenType.Other; + } + public override tokenizeLineWithEdit(position: IPosition, length: number, newText: string): LineTokens | null { + // TODO @alexr00 understand what this is for and implement + return null; + } + public override get hasTokens(): boolean { + // TODO @alexr00 once we have a token store, implement properly + const hasTree = this._treeSitterService.getParseResult(this._textModel) !== undefined; + return hasTree; + } +} diff --git a/patched-vscode/src/vs/editor/common/services/editorSimpleWorker.esm.ts b/patched-vscode/src/vs/editor/common/services/editorSimpleWorker.esm.ts new file mode 100644 index 00000000..5f659c76 --- /dev/null +++ b/patched-vscode/src/vs/editor/common/services/editorSimpleWorker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { create } from 'vs/editor/common/services/editorSimpleWorker'; +import { bootstrapSimpleEditorWorker } from './editorWorkerBootstrap'; + +bootstrapSimpleEditorWorker(create); diff --git a/patched-vscode/src/vs/editor/common/services/editorSimpleWorker.ts b/patched-vscode/src/vs/editor/common/services/editorSimpleWorker.ts index 195a870b..4bf19b0d 100644 --- a/patched-vscode/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/patched-vscode/src/vs/editor/common/services/editorSimpleWorker.ts @@ -6,18 +6,17 @@ import { stringDiff } from 'vs/base/common/diff/diff'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; -import { IPosition, Position } from 'vs/editor/common/core/position'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; -import { IMirrorTextModel, IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; -import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper'; +import { IMirrorTextModel, IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import { IColorInformation, IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/languages'; -import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/languages/linkComputer'; +import { computeLinks } from 'vs/editor/common/languages/linkComputer'; import { BasicInplaceReplace } from 'vs/editor/common/languages/supports/inplaceReplaceSupport'; import { DiffAlgorithmName, IDiffComputationResult, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { createMonacoBaseAPI } from 'vs/editor/common/services/editorBaseApi'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; +import { EditorWorkerHost } from './editorWorkerHost'; import { StopWatch } from 'vs/base/common/stopwatch'; import { UnicodeTextModelHighlighter, UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; import { DiffComputer, IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; @@ -26,9 +25,19 @@ import { DetailedLineRangeMapping } from '../diff/rangeMapping'; import { linesDiffComputers } from 'vs/editor/common/diff/linesDiffComputers'; import { createProxyObject, getAllMethodNames } from 'vs/base/common/objects'; import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { AppResourcePath, FileAccess } from 'vs/base/common/network'; import { BugIndicatingError } from 'vs/base/common/errors'; -import { IDocumentColorComputerTarget, computeDefaultDocumentColors } from 'vs/editor/common/languages/defaultDocumentColorsComputer'; +import { computeDefaultDocumentColors } from 'vs/editor/common/languages/defaultDocumentColorsComputer'; import { FindSectionHeaderOptions, SectionHeader, findSectionHeaders } from 'vs/editor/common/services/findSectionHeaders'; +import { IRawModelData, IWorkerTextModelSyncChannelServer } from './textModelSync/textModelSync.protocol'; +import { ICommonModel, WorkerTextModelSyncServer } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; + +// ESM-comment-begin +const isESM = false; +// ESM-comment-end +// ESM-uncomment-begin +// const isESM = true; +// ESM-uncomment-end export interface IMirrorModel extends IMirrorTextModel { readonly uri: URI; @@ -47,43 +56,11 @@ export interface IWorkerContext { getMirrorModels(): IMirrorModel[]; } -/** - * @internal - */ -export interface IRawModelData { - url: string; - versionId: number; - lines: string[]; - EOL: string; -} - -/** - * @internal - */ -export interface ICommonModel extends ILinkComputerTarget, IDocumentColorComputerTarget, IMirrorModel { - uri: URI; - version: number; - eol: string; - getValue(): string; - - getLinesContent(): string[]; - getLineCount(): number; - getLineContent(lineNumber: number): string; - getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[]; - words(wordDefinition: RegExp): Iterable; - getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition; - getValueInRange(range: IRange): string; - getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null; - offsetAt(position: IPosition): number; - positionAt(offset: number): IPosition; - findMatches(regex: RegExp): RegExpMatchArray[]; -} - /** * Range of a word inside a model. * @internal */ -interface IWordRange { +export interface IWordRange { /** * The index where the word starts. */ @@ -94,246 +71,6 @@ interface IWordRange { readonly end: number; } -/** - * @internal - */ -class MirrorModel extends BaseMirrorModel implements ICommonModel { - - public get uri(): URI { - return this._uri; - } - - public get eol(): string { - return this._eol; - } - - public getValue(): string { - return this.getText(); - } - - public findMatches(regex: RegExp): RegExpMatchArray[] { - const matches = []; - for (let i = 0; i < this._lines.length; i++) { - const line = this._lines[i]; - const offsetToAdd = this.offsetAt(new Position(i + 1, 1)); - const iteratorOverMatches = line.matchAll(regex); - for (const match of iteratorOverMatches) { - if (match.index || match.index === 0) { - match.index = match.index + offsetToAdd; - } - matches.push(match); - } - } - return matches; - } - - public getLinesContent(): string[] { - return this._lines.slice(0); - } - - public getLineCount(): number { - return this._lines.length; - } - - public getLineContent(lineNumber: number): string { - return this._lines[lineNumber - 1]; - } - - public getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null { - - const wordAtText = getWordAtText( - position.column, - ensureValidWordDefinition(wordDefinition), - this._lines[position.lineNumber - 1], - 0 - ); - - if (wordAtText) { - return new Range(position.lineNumber, wordAtText.startColumn, position.lineNumber, wordAtText.endColumn); - } - - return null; - } - - public getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition { - const wordAtPosition = this.getWordAtPosition(position, wordDefinition); - if (!wordAtPosition) { - return { - word: '', - startColumn: position.column, - endColumn: position.column - }; - } - return { - word: this._lines[position.lineNumber - 1].substring(wordAtPosition.startColumn - 1, position.column - 1), - startColumn: wordAtPosition.startColumn, - endColumn: position.column - }; - } - - - public words(wordDefinition: RegExp): Iterable { - - const lines = this._lines; - const wordenize = this._wordenize.bind(this); - - let lineNumber = 0; - let lineText = ''; - let wordRangesIdx = 0; - let wordRanges: IWordRange[] = []; - - return { - *[Symbol.iterator]() { - while (true) { - if (wordRangesIdx < wordRanges.length) { - const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); - wordRangesIdx += 1; - yield value; - } else { - if (lineNumber < lines.length) { - lineText = lines[lineNumber]; - wordRanges = wordenize(lineText, wordDefinition); - wordRangesIdx = 0; - lineNumber += 1; - } else { - break; - } - } - } - } - }; - } - - public getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[] { - const content = this._lines[lineNumber - 1]; - const ranges = this._wordenize(content, wordDefinition); - const words: IWordAtPosition[] = []; - for (const range of ranges) { - words.push({ - word: content.substring(range.start, range.end), - startColumn: range.start + 1, - endColumn: range.end + 1 - }); - } - return words; - } - - private _wordenize(content: string, wordDefinition: RegExp): IWordRange[] { - const result: IWordRange[] = []; - let match: RegExpExecArray | null; - - wordDefinition.lastIndex = 0; // reset lastIndex just to be sure - - while (match = wordDefinition.exec(content)) { - if (match[0].length === 0) { - // it did match the empty string - break; - } - result.push({ start: match.index, end: match.index + match[0].length }); - } - return result; - } - - public getValueInRange(range: IRange): string { - range = this._validateRange(range); - - if (range.startLineNumber === range.endLineNumber) { - return this._lines[range.startLineNumber - 1].substring(range.startColumn - 1, range.endColumn - 1); - } - - const lineEnding = this._eol; - const startLineIndex = range.startLineNumber - 1; - const endLineIndex = range.endLineNumber - 1; - const resultLines: string[] = []; - - resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1)); - for (let i = startLineIndex + 1; i < endLineIndex; i++) { - resultLines.push(this._lines[i]); - } - resultLines.push(this._lines[endLineIndex].substring(0, range.endColumn - 1)); - - return resultLines.join(lineEnding); - } - - public offsetAt(position: IPosition): number { - position = this._validatePosition(position); - this._ensureLineStarts(); - return this._lineStarts!.getPrefixSum(position.lineNumber - 2) + (position.column - 1); - } - - public positionAt(offset: number): IPosition { - offset = Math.floor(offset); - offset = Math.max(0, offset); - - this._ensureLineStarts(); - const out = this._lineStarts!.getIndexOf(offset); - const lineLength = this._lines[out.index].length; - - // Ensure we return a valid position - return { - lineNumber: 1 + out.index, - column: 1 + Math.min(out.remainder, lineLength) - }; - } - - private _validateRange(range: IRange): IRange { - - const start = this._validatePosition({ lineNumber: range.startLineNumber, column: range.startColumn }); - const end = this._validatePosition({ lineNumber: range.endLineNumber, column: range.endColumn }); - - if (start.lineNumber !== range.startLineNumber - || start.column !== range.startColumn - || end.lineNumber !== range.endLineNumber - || end.column !== range.endColumn) { - - return { - startLineNumber: start.lineNumber, - startColumn: start.column, - endLineNumber: end.lineNumber, - endColumn: end.column - }; - } - - return range; - } - - private _validatePosition(position: IPosition): IPosition { - if (!Position.isIPosition(position)) { - throw new Error('bad position'); - } - let { lineNumber, column } = position; - let hasChanged = false; - - if (lineNumber < 1) { - lineNumber = 1; - column = 1; - hasChanged = true; - - } else if (lineNumber > this._lines.length) { - lineNumber = this._lines.length; - column = this._lines[lineNumber - 1].length + 1; - hasChanged = true; - - } else { - const maxCharacter = this._lines[lineNumber - 1].length + 1; - if (column < 1) { - column = 1; - hasChanged = true; - } - else if (column > maxCharacter) { - column = maxCharacter; - hasChanged = true; - } - } - - if (!hasChanged) { - return position; - } else { - return { lineNumber, column }; - } - } -} - /** * @internal */ @@ -346,55 +83,38 @@ declare const require: any; /** * @internal */ -export class EditorSimpleWorker implements IRequestHandler, IDisposable { +export class BaseEditorSimpleWorker implements IDisposable, IWorkerTextModelSyncChannelServer, IRequestHandler { _requestHandlerBrand: any; - protected readonly _host: IEditorWorkerHost; - private _models: { [uri: string]: MirrorModel }; - private readonly _foreignModuleFactory: IForeignModuleFactory | null; - private _foreignModule: any; + private readonly _workerTextModelSyncServer = new WorkerTextModelSyncServer(); - constructor(host: IEditorWorkerHost, foreignModuleFactory: IForeignModuleFactory | null) { - this._host = host; - this._models = Object.create(null); - this._foreignModuleFactory = foreignModuleFactory; - this._foreignModule = null; + constructor() { } - public dispose(): void { - this._models = Object.create(null); + dispose(): void { } - protected _getModel(uri: string): ICommonModel { - return this._models[uri]; + protected _getModel(uri: string): ICommonModel | undefined { + return this._workerTextModelSyncServer.getModel(uri); } - private _getModels(): ICommonModel[] { - const all: MirrorModel[] = []; - Object.keys(this._models).forEach((key) => all.push(this._models[key])); - return all; + protected _getModels(): ICommonModel[] { + return this._workerTextModelSyncServer.getModels(); } - public acceptNewModel(data: IRawModelData): void { - this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId); + public $acceptNewModel(data: IRawModelData): void { + this._workerTextModelSyncServer.$acceptNewModel(data); } - public acceptModelChanged(strURL: string, e: IModelChangedEvent): void { - if (!this._models[strURL]) { - return; - } - const model = this._models[strURL]; - model.onEvents(e); + public $acceptModelChanged(uri: string, e: IModelChangedEvent): void { + this._workerTextModelSyncServer.$acceptModelChanged(uri, e); } - public acceptRemovedModel(strURL: string): void { - if (!this._models[strURL]) { - return; - } - delete this._models[strURL]; + public $acceptRemovedModel(uri: string): void { + this._workerTextModelSyncServer.$acceptRemovedModel(uri); } - public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise { + public async $computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise { const model = this._getModel(url); if (!model) { return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; @@ -402,7 +122,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range); } - public async findSectionHeaders(url: string, options: FindSectionHeaderOptions): Promise { + public async $findSectionHeaders(url: string, options: FindSectionHeaderOptions): Promise { const model = this._getModel(url); if (!model) { return []; @@ -412,7 +132,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // ---- BEGIN diff -------------------------------------------------------------------------- - public async computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { + public async $computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); if (!original || !modified) { @@ -476,7 +196,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return true; } - public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { + public async $computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); if (!original || !modified) { @@ -502,7 +222,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { private static readonly _diffLimit = 100000; - public async computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise { + public async $computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise { const model = this._getModel(modelUrl); if (!model) { return edits; @@ -584,7 +304,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return result; } - public computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): TextEdit[] { + public $computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): TextEdit[] { const model = this._getModel(modelUrl); if (!model) { return edits; @@ -684,7 +404,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // ---- END minimal edits --------------------------------------------------------------- - public async computeLinks(modelUrl: string): Promise { + public async $computeLinks(modelUrl: string): Promise { const model = this._getModel(modelUrl); if (!model) { return null; @@ -695,7 +415,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // --- BEGIN default document colors ----------------------------------------------------------- - public async computeDefaultDocumentColors(modelUrl: string): Promise { + public async $computeDefaultDocumentColors(modelUrl: string): Promise { const model = this._getModel(modelUrl); if (!model) { return null; @@ -707,7 +427,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { private static readonly _suggestionsLimit = 10000; - public async textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[]; duration: number } | null> { + public async $textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[]; duration: number } | null> { const sw = new StopWatch(); const wordDefRegExp = new RegExp(wordDef, wordDefFlags); @@ -738,7 +458,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { //#region -- word ranges -- - public async computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> { + public async $computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> { const model = this._getModel(modelUrl); if (!model) { return Object.create(null); @@ -769,7 +489,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { //#endregion - public async navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise { + public async $navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise { const model = this._getModel(modelUrl); if (!model) { return null; @@ -796,12 +516,31 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { const result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up); return result; } +} + +/** + * @internal + */ +export class EditorSimpleWorker extends BaseEditorSimpleWorker { + + private _foreignModule: any = null; + + constructor( + private readonly _host: EditorWorkerHost, + private readonly _foreignModuleFactory: IForeignModuleFactory | null + ) { + super(); + } + + public async $ping() { + return 'pong'; + } // ---- BEGIN foreign module support -------------------------------------------------------------------------- - public loadForeignModule(moduleId: string, createData: any, foreignHostMethods: string[]): Promise { + public $loadForeignModule(moduleId: string, createData: any, foreignHostMethods: string[]): Promise { const proxyMethodRequest = (method: string, args: any[]): Promise => { - return this._host.fhr(method, args); + return this._host.$fhr(method, args); }; const foreignHost = createProxyObject(foreignHostMethods, proxyMethodRequest); @@ -818,24 +557,25 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // static foreing module return Promise.resolve(getAllMethodNames(this._foreignModule)); } - // ESM-comment-begin + return new Promise((resolve, reject) => { - require([moduleId], (foreignModule: { create: IForeignModuleFactory }) => { - this._foreignModule = foreignModule.create(ctx, createData); + const onModuleCallback = (foreignModule: { create: IForeignModuleFactory }) => { + this._foreignModule = foreignModule.create(ctx, createData); resolve(getAllMethodNames(this._foreignModule)); + }; - }, reject); + if (!isESM) { + require([`${moduleId}`], onModuleCallback, reject); + } else { + const url = FileAccess.asBrowserUri(`${moduleId}.js` as AppResourcePath).toString(true); + import(`${url}`).then(onModuleCallback).catch(reject); + } }); - // ESM-comment-end - - // ESM-uncomment-begin - // return Promise.reject(new Error(`Unexpected usage`)); - // ESM-uncomment-end } // foreign method request - public fmr(method: string, args: any[]): Promise { + public $fmr(method: string, args: any[]): Promise { if (!this._foreignModule || typeof this._foreignModule[method] !== 'function') { return Promise.reject(new Error('Missing requestHandler or method: ' + method)); } @@ -851,11 +591,12 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { } /** - * Called on the worker side + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle * @internal */ -export function create(host: IEditorWorkerHost): IRequestHandler { - return new EditorSimpleWorker(host, null); +export function create(workerServer: IWorkerServer): IRequestHandler { + return new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), null); } // This is only available in a Web Worker diff --git a/patched-vscode/src/vs/editor/common/services/editorWorker.ts b/patched-vscode/src/vs/editor/common/services/editorWorker.ts index 7e87024c..74fe752b 100644 --- a/patched-vscode/src/vs/editor/common/services/editorWorker.ts +++ b/patched-vscode/src/vs/editor/common/services/editorWorker.ts @@ -7,10 +7,10 @@ import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; -import { IInplaceReplaceSupportResult, TextEdit } from 'vs/editor/common/languages'; +import { IColorInformation, IInplaceReplaceSupportResult, TextEdit } from 'vs/editor/common/languages'; import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import type { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import type { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { SectionHeader, FindSectionHeaderOptions } from 'vs/editor/common/services/findSectionHeaders'; export const IEditorWorkerService = createDecorator('editorWorkerService'); @@ -23,7 +23,7 @@ export interface IEditorWorkerService { canComputeUnicodeHighlights(uri: URI): boolean; computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise; - /** Implementation in {@link EditorSimpleWorker.computeDiff} */ + /** Implementation in {@link BaseEditorSimpleWorker.computeDiff} */ computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise; canComputeDirtyDiff(original: URI, modified: URI): boolean; @@ -39,6 +39,9 @@ export interface IEditorWorkerService { navigateValueSet(resource: URI, range: IRange, up: boolean): Promise; findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise; + + computeDefaultDocumentColors(uri: URI): Promise; + } export interface IDiffComputationResult { diff --git a/patched-vscode/src/vs/editor/common/services/editorWorkerBootstrap.ts b/patched-vscode/src/vs/editor/common/services/editorWorkerBootstrap.ts new file mode 100644 index 00000000..160942a1 --- /dev/null +++ b/patched-vscode/src/vs/editor/common/services/editorWorkerBootstrap.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkerServer, SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; + +type MessageEvent = { + data: any; +}; + +declare const globalThis: { + postMessage: (message: any) => void; + onmessage: (event: MessageEvent) => void; +}; + +let initialized = false; + +export function initialize(factory: any) { + if (initialized) { + return; + } + initialized = true; + + const simpleWorker = new SimpleWorkerServer((msg) => { + globalThis.postMessage(msg); + }, (workerServer: IWorkerServer) => new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), null)); + + globalThis.onmessage = (e: MessageEvent) => { + simpleWorker.onmessage(e.data); + }; +} + +globalThis.onmessage = (e: MessageEvent) => { + // Ignore first message in this case and initialize if not yet initialized + if (!initialized) { + initialize(null); + } +}; + +type CreateFunction = (ctx: C, data: D) => R; + +export function bootstrapSimpleEditorWorker(createFn: CreateFunction) { + globalThis.onmessage = () => { + initialize((ctx: C, createData: D) => { + return createFn.call(self, ctx, createData); + }); + }; +} diff --git a/patched-vscode/src/vs/editor/common/services/editorWorkerHost.ts b/patched-vscode/src/vs/editor/common/services/editorWorkerHost.ts index df0a6aa1..30ceed18 100644 --- a/patched-vscode/src/vs/editor/common/services/editorWorkerHost.ts +++ b/patched-vscode/src/vs/editor/common/services/editorWorkerHost.ts @@ -3,7 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export interface IEditorWorkerHost { +import { IWorkerServer, IWorkerClient } from 'vs/base/common/worker/simpleWorker'; + +export abstract class EditorWorkerHost { + public static CHANNEL_NAME = 'editorWorkerHost'; + public static getChannel(workerServer: IWorkerServer): EditorWorkerHost { + return workerServer.getChannel(EditorWorkerHost.CHANNEL_NAME); + } + public static setChannel(workerClient: IWorkerClient, obj: EditorWorkerHost): void { + workerClient.setChannel(EditorWorkerHost.CHANNEL_NAME, obj); + } + // foreign host request - fhr(method: string, args: any[]): Promise; + abstract $fhr(method: string, args: any[]): Promise; } diff --git a/patched-vscode/src/vs/editor/common/services/getIconClasses.ts b/patched-vscode/src/vs/editor/common/services/getIconClasses.ts index 3608f304..1f3bea99 100644 --- a/patched-vscode/src/vs/editor/common/services/getIconClasses.ts +++ b/patched-vscode/src/vs/editor/common/services/getIconClasses.ts @@ -123,5 +123,5 @@ function detectLanguageId(modelService: IModelService, languageService: ILanguag } function cssEscape(str: string): string { - return str.replace(/[\11\12\14\15\40]/g, '/'); // HTML class names can not contain certain whitespace characters, use / instead, which doesn't exist in file names. + return str.replace(/[\s]/g, '/'); // HTML class names can not contain certain whitespace characters (https://dom.spec.whatwg.org/#interface-domtokenlist), use / instead, which doesn't exist in file names. } diff --git a/patched-vscode/src/vs/editor/common/services/languageService.ts b/patched-vscode/src/vs/editor/common/services/languageService.ts index 6d96f2a2..f0a7f835 100644 --- a/patched-vscode/src/vs/editor/common/services/languageService.ts +++ b/patched-vscode/src/vs/editor/common/services/languageService.ts @@ -11,6 +11,7 @@ import { ILanguageNameIdPair, ILanguageSelection, ILanguageService, ILanguageIco import { firstOrDefault } from 'vs/base/common/arrays'; import { ILanguageIdCodec, TokenizationRegistry } from 'vs/editor/common/languages'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IObservable, observableFromEvent } from 'vs/base/common/observable'; export class LanguageService extends Disposable implements ILanguageService { public _serviceBrand: undefined; @@ -150,51 +151,15 @@ export class LanguageService extends Disposable implements ILanguageService { } class LanguageSelection implements ILanguageSelection { + private readonly _value: IObservable; + public readonly onDidChange: Event; - public languageId: string; - - private _listener: IDisposable | null = null; - private _emitter: Emitter | null = null; - - constructor( - private readonly _onDidChangeLanguages: Event, - private readonly _selector: () => string - ) { - this.languageId = this._selector(); - } - - private _dispose(): void { - if (this._listener) { - this._listener.dispose(); - this._listener = null; - } - if (this._emitter) { - this._emitter.dispose(); - this._emitter = null; - } + constructor(onDidChangeLanguages: Event, selector: () => string) { + this._value = observableFromEvent(this, onDidChangeLanguages, () => selector()); + this.onDidChange = Event.fromObservable(this._value); } - public get onDidChange(): Event { - if (!this._listener) { - this._listener = this._onDidChangeLanguages(() => this._evaluate()); - } - if (!this._emitter) { - this._emitter = new Emitter({ - onDidRemoveLastListener: () => { - this._dispose(); - } - }); - } - return this._emitter.event; - } - - private _evaluate(): void { - const languageId = this._selector(); - if (languageId === this.languageId) { - // no change - return; - } - this.languageId = languageId; - this._emitter?.fire(this.languageId); + public get languageId(): string { + return this._value.get(); } } diff --git a/patched-vscode/src/vs/editor/common/services/modelService.ts b/patched-vscode/src/vs/editor/common/services/modelService.ts index 2bbda14a..00019d6f 100644 --- a/patched-vscode/src/vs/editor/common/services/modelService.ts +++ b/patched-vscode/src/vs/editor/common/services/modelService.ts @@ -14,7 +14,7 @@ import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/core/textModelDefaults'; import { IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; -import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language'; +import { ILanguageSelection } from 'vs/editor/common/languages/language'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -23,7 +23,7 @@ import { StringSHA1 } from 'vs/base/common/hash'; import { isEditStackElement } from 'vs/editor/common/model/editStack'; import { Schemas } from 'vs/base/common/network'; import { equals } from 'vs/base/common/objects'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -107,8 +107,7 @@ export class ModelService extends Disposable implements IModelService { @IConfigurationService private readonly _configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly _resourcePropertiesService: ITextResourcePropertiesService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, - @ILanguageService private readonly _languageService: ILanguageService, - @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); this._modelCreationOptionsByLanguageAndResource = Object.create(null); @@ -314,14 +313,11 @@ export class ModelService extends Disposable implements IModelService { private _createModelData(value: string | ITextBufferFactory, languageIdOrSelection: string | ILanguageSelection, resource: URI | undefined, isForSimpleWidget: boolean): ModelData { // create & save the model const options = this.getCreationOptions(languageIdOrSelection, resource, isForSimpleWidget); - const model: TextModel = new TextModel( + const model: TextModel = this._instantiationService.createInstance(TextModel, value, languageIdOrSelection, options, - resource, - this._undoRedoService, - this._languageService, - this._languageConfigurationService, + resource ); if (resource && this._disposedModels.has(MODEL_ID(resource))) { const disposedModelData = this._removeDisposedModel(resource)!; diff --git a/patched-vscode/src/vs/editor/common/services/semanticTokensProviderStyling.ts b/patched-vscode/src/vs/editor/common/services/semanticTokensProviderStyling.ts index f248e0e2..1bb2e0d6 100644 --- a/patched-vscode/src/vs/editor/common/services/semanticTokensProviderStyling.ts +++ b/patched-vscode/src/vs/editor/common/services/semanticTokensProviderStyling.ts @@ -14,6 +14,8 @@ const enum SemanticTokensProviderStylingConstants { NO_STYLING = 0b01111111111111111111111111111111 } +const ENABLE_TRACE = false; + export class SemanticTokensProviderStyling { private readonly _hashTable: HashTable; @@ -36,7 +38,7 @@ export class SemanticTokensProviderStyling { let metadata: number; if (entry) { metadata = entry.metadata; - if (this._logService.getLevel() === LogLevel.Trace) { + if (ENABLE_TRACE && this._logService.getLevel() === LogLevel.Trace) { this._logService.trace(`SemanticTokensProviderStyling [CACHED] ${tokenTypeIndex} / ${tokenModifierSet}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); } } else { @@ -50,7 +52,7 @@ export class SemanticTokensProviderStyling { } modifierSet = modifierSet >> 1; } - if (modifierSet > 0 && this._logService.getLevel() === LogLevel.Trace) { + if (ENABLE_TRACE && modifierSet > 0 && this._logService.getLevel() === LogLevel.Trace) { this._logService.trace(`SemanticTokensProviderStyling: unknown token modifier index: ${tokenModifierSet.toString(2)} for legend: ${JSON.stringify(this._legend.tokenModifiers)}`); tokenModifiers.push('not-in-legend'); } @@ -86,7 +88,7 @@ export class SemanticTokensProviderStyling { } } } else { - if (this._logService.getLevel() === LogLevel.Trace) { + if (ENABLE_TRACE && this._logService.getLevel() === LogLevel.Trace) { this._logService.trace(`SemanticTokensProviderStyling: unknown token type index: ${tokenTypeIndex} for legend: ${JSON.stringify(this._legend.tokenTypes)}`); } metadata = SemanticTokensProviderStylingConstants.NO_STYLING; @@ -94,7 +96,7 @@ export class SemanticTokensProviderStyling { } this._hashTable.add(tokenTypeIndex, tokenModifierSet, encodedLanguageId, metadata); - if (this._logService.getLevel() === LogLevel.Trace) { + if (ENABLE_TRACE && this._logService.getLevel() === LogLevel.Trace) { this._logService.trace(`SemanticTokensProviderStyling ${tokenTypeIndex} (${tokenType}) / ${tokenModifierSet} (${tokenModifiers.join(' ')}): foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); } } diff --git a/patched-vscode/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts b/patched-vscode/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts new file mode 100644 index 00000000..dc68d666 --- /dev/null +++ b/patched-vscode/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts @@ -0,0 +1,427 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IntervalTimer } from 'vs/base/common/async'; +import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IWorkerClient, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper'; +import { IDocumentColorComputerTarget } from 'vs/editor/common/languages/defaultDocumentColorsComputer'; +import { ILinkComputerTarget } from 'vs/editor/common/languages/linkComputer'; +import { MirrorTextModel as BaseMirrorModel, IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; +import { IMirrorModel, IWordRange } from 'vs/editor/common/services/editorSimpleWorker'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IRawModelData, IWorkerTextModelSyncChannelServer } from 'vs/editor/common/services/textModelSync/textModelSync.protocol'; + +/** + * Stop syncing a model to the worker if it was not needed for 1 min. + */ +export const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000; + +export const WORKER_TEXT_MODEL_SYNC_CHANNEL = 'workerTextModelSync'; + +export class WorkerTextModelSyncClient extends Disposable { + + public static create(workerClient: IWorkerClient, modelService: IModelService): WorkerTextModelSyncClient { + return new WorkerTextModelSyncClient( + workerClient.getChannel(WORKER_TEXT_MODEL_SYNC_CHANNEL), + modelService + ); + } + + private readonly _proxy: IWorkerTextModelSyncChannelServer; + private readonly _modelService: IModelService; + private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null); + private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null); + + constructor(proxy: IWorkerTextModelSyncChannelServer, modelService: IModelService, keepIdleModels: boolean = false) { + super(); + this._proxy = proxy; + this._modelService = modelService; + + if (!keepIdleModels) { + const timer = new IntervalTimer(); + timer.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2)); + this._register(timer); + } + } + + public override dispose(): void { + for (const modelUrl in this._syncedModels) { + dispose(this._syncedModels[modelUrl]); + } + this._syncedModels = Object.create(null); + this._syncedModelsLastUsedTime = Object.create(null); + super.dispose(); + } + + public ensureSyncedResources(resources: URI[], forceLargeModels: boolean = false): void { + for (const resource of resources) { + const resourceStr = resource.toString(); + + if (!this._syncedModels[resourceStr]) { + this._beginModelSync(resource, forceLargeModels); + } + if (this._syncedModels[resourceStr]) { + this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime(); + } + } + } + + private _checkStopModelSync(): void { + const currentTime = (new Date()).getTime(); + + const toRemove: string[] = []; + for (const modelUrl in this._syncedModelsLastUsedTime) { + const elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl]; + if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) { + toRemove.push(modelUrl); + } + } + + for (const e of toRemove) { + this._stopModelSync(e); + } + } + + private _beginModelSync(resource: URI, forceLargeModels: boolean): void { + const model = this._modelService.getModel(resource); + if (!model) { + return; + } + if (!forceLargeModels && model.isTooLargeForSyncing()) { + return; + } + + const modelUrl = resource.toString(); + + this._proxy.$acceptNewModel({ + url: model.uri.toString(), + lines: model.getLinesContent(), + EOL: model.getEOL(), + versionId: model.getVersionId() + }); + + const toDispose = new DisposableStore(); + toDispose.add(model.onDidChangeContent((e) => { + this._proxy.$acceptModelChanged(modelUrl.toString(), e); + })); + toDispose.add(model.onWillDispose(() => { + this._stopModelSync(modelUrl); + })); + toDispose.add(toDisposable(() => { + this._proxy.$acceptRemovedModel(modelUrl); + })); + + this._syncedModels[modelUrl] = toDispose; + } + + private _stopModelSync(modelUrl: string): void { + const toDispose = this._syncedModels[modelUrl]; + delete this._syncedModels[modelUrl]; + delete this._syncedModelsLastUsedTime[modelUrl]; + dispose(toDispose); + } +} + +export class WorkerTextModelSyncServer implements IWorkerTextModelSyncChannelServer { + + private readonly _models: { [uri: string]: MirrorModel }; + + constructor() { + this._models = Object.create(null); + } + + public bindToServer(workerServer: IWorkerServer): void { + workerServer.setChannel(WORKER_TEXT_MODEL_SYNC_CHANNEL, this); + } + + public getModel(uri: string): ICommonModel | undefined { + return this._models[uri]; + } + + public getModels(): ICommonModel[] { + const all: MirrorModel[] = []; + Object.keys(this._models).forEach((key) => all.push(this._models[key])); + return all; + } + + $acceptNewModel(data: IRawModelData): void { + this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId); + } + + $acceptModelChanged(uri: string, e: IModelChangedEvent): void { + if (!this._models[uri]) { + return; + } + const model = this._models[uri]; + model.onEvents(e); + } + + $acceptRemovedModel(uri: string): void { + if (!this._models[uri]) { + return; + } + delete this._models[uri]; + } +} + +export class MirrorModel extends BaseMirrorModel implements ICommonModel { + + public get uri(): URI { + return this._uri; + } + + public get eol(): string { + return this._eol; + } + + public getValue(): string { + return this.getText(); + } + + public findMatches(regex: RegExp): RegExpMatchArray[] { + const matches = []; + for (let i = 0; i < this._lines.length; i++) { + const line = this._lines[i]; + const offsetToAdd = this.offsetAt(new Position(i + 1, 1)); + const iteratorOverMatches = line.matchAll(regex); + for (const match of iteratorOverMatches) { + if (match.index || match.index === 0) { + match.index = match.index + offsetToAdd; + } + matches.push(match); + } + } + return matches; + } + + public getLinesContent(): string[] { + return this._lines.slice(0); + } + + public getLineCount(): number { + return this._lines.length; + } + + public getLineContent(lineNumber: number): string { + return this._lines[lineNumber - 1]; + } + + public getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null { + + const wordAtText = getWordAtText( + position.column, + ensureValidWordDefinition(wordDefinition), + this._lines[position.lineNumber - 1], + 0 + ); + + if (wordAtText) { + return new Range(position.lineNumber, wordAtText.startColumn, position.lineNumber, wordAtText.endColumn); + } + + return null; + } + + public getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition { + const wordAtPosition = this.getWordAtPosition(position, wordDefinition); + if (!wordAtPosition) { + return { + word: '', + startColumn: position.column, + endColumn: position.column + }; + } + return { + word: this._lines[position.lineNumber - 1].substring(wordAtPosition.startColumn - 1, position.column - 1), + startColumn: wordAtPosition.startColumn, + endColumn: position.column + }; + } + + + public words(wordDefinition: RegExp): Iterable { + + const lines = this._lines; + const wordenize = this._wordenize.bind(this); + + let lineNumber = 0; + let lineText = ''; + let wordRangesIdx = 0; + let wordRanges: IWordRange[] = []; + + return { + *[Symbol.iterator]() { + while (true) { + if (wordRangesIdx < wordRanges.length) { + const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); + wordRangesIdx += 1; + yield value; + } else { + if (lineNumber < lines.length) { + lineText = lines[lineNumber]; + wordRanges = wordenize(lineText, wordDefinition); + wordRangesIdx = 0; + lineNumber += 1; + } else { + break; + } + } + } + } + }; + } + + public getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[] { + const content = this._lines[lineNumber - 1]; + const ranges = this._wordenize(content, wordDefinition); + const words: IWordAtPosition[] = []; + for (const range of ranges) { + words.push({ + word: content.substring(range.start, range.end), + startColumn: range.start + 1, + endColumn: range.end + 1 + }); + } + return words; + } + + private _wordenize(content: string, wordDefinition: RegExp): IWordRange[] { + const result: IWordRange[] = []; + let match: RegExpExecArray | null; + + wordDefinition.lastIndex = 0; // reset lastIndex just to be sure + + while (match = wordDefinition.exec(content)) { + if (match[0].length === 0) { + // it did match the empty string + break; + } + result.push({ start: match.index, end: match.index + match[0].length }); + } + return result; + } + + public getValueInRange(range: IRange): string { + range = this._validateRange(range); + + if (range.startLineNumber === range.endLineNumber) { + return this._lines[range.startLineNumber - 1].substring(range.startColumn - 1, range.endColumn - 1); + } + + const lineEnding = this._eol; + const startLineIndex = range.startLineNumber - 1; + const endLineIndex = range.endLineNumber - 1; + const resultLines: string[] = []; + + resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1)); + for (let i = startLineIndex + 1; i < endLineIndex; i++) { + resultLines.push(this._lines[i]); + } + resultLines.push(this._lines[endLineIndex].substring(0, range.endColumn - 1)); + + return resultLines.join(lineEnding); + } + + public offsetAt(position: IPosition): number { + position = this._validatePosition(position); + this._ensureLineStarts(); + return this._lineStarts!.getPrefixSum(position.lineNumber - 2) + (position.column - 1); + } + + public positionAt(offset: number): IPosition { + offset = Math.floor(offset); + offset = Math.max(0, offset); + + this._ensureLineStarts(); + const out = this._lineStarts!.getIndexOf(offset); + const lineLength = this._lines[out.index].length; + + // Ensure we return a valid position + return { + lineNumber: 1 + out.index, + column: 1 + Math.min(out.remainder, lineLength) + }; + } + + private _validateRange(range: IRange): IRange { + + const start = this._validatePosition({ lineNumber: range.startLineNumber, column: range.startColumn }); + const end = this._validatePosition({ lineNumber: range.endLineNumber, column: range.endColumn }); + + if (start.lineNumber !== range.startLineNumber + || start.column !== range.startColumn + || end.lineNumber !== range.endLineNumber + || end.column !== range.endColumn) { + + return { + startLineNumber: start.lineNumber, + startColumn: start.column, + endLineNumber: end.lineNumber, + endColumn: end.column + }; + } + + return range; + } + + private _validatePosition(position: IPosition): IPosition { + if (!Position.isIPosition(position)) { + throw new Error('bad position'); + } + let { lineNumber, column } = position; + let hasChanged = false; + + if (lineNumber < 1) { + lineNumber = 1; + column = 1; + hasChanged = true; + + } else if (lineNumber > this._lines.length) { + lineNumber = this._lines.length; + column = this._lines[lineNumber - 1].length + 1; + hasChanged = true; + + } else { + const maxCharacter = this._lines[lineNumber - 1].length + 1; + if (column < 1) { + column = 1; + hasChanged = true; + } + else if (column > maxCharacter) { + column = maxCharacter; + hasChanged = true; + } + } + + if (!hasChanged) { + return position; + } else { + return { lineNumber, column }; + } + } +} + +export interface ICommonModel extends ILinkComputerTarget, IDocumentColorComputerTarget, IMirrorModel { + uri: URI; + version: number; + eol: string; + getValue(): string; + + getLinesContent(): string[]; + getLineCount(): number; + getLineContent(lineNumber: number): string; + getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[]; + words(wordDefinition: RegExp): Iterable; + getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition; + getValueInRange(range: IRange): string; + getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null; + offsetAt(position: IPosition): number; + positionAt(offset: number): IPosition; + findMatches(regex: RegExp): RegExpMatchArray[]; +} diff --git a/patched-vscode/src/vs/editor/common/services/textModelSync/textModelSync.protocol.ts b/patched-vscode/src/vs/editor/common/services/textModelSync/textModelSync.protocol.ts new file mode 100644 index 00000000..5ffbf6a0 --- /dev/null +++ b/patched-vscode/src/vs/editor/common/services/textModelSync/textModelSync.protocol.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; + +export interface IWorkerTextModelSyncChannelServer { + $acceptNewModel(data: IRawModelData): void; + + $acceptModelChanged(strURL: string, e: IModelChangedEvent): void; + + $acceptRemovedModel(strURL: string): void; +} + +export interface IRawModelData { + url: string; + versionId: number; + lines: string[]; + EOL: string; +} diff --git a/patched-vscode/src/vs/editor/common/services/treeSitterParserService.ts b/patched-vscode/src/vs/editor/common/services/treeSitterParserService.ts new file mode 100644 index 00000000..aed18b37 --- /dev/null +++ b/patched-vscode/src/vs/editor/common/services/treeSitterParserService.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { Parser } from '@vscode/tree-sitter-wasm'; +import { Event } from 'vs/base/common/event'; +import { ITextModel } from 'vs/editor/common/model'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const EDITOR_EXPERIMENTAL_PREFER_TREESITTER = 'editor.experimental.preferTreeSitter'; + +export const ITreeSitterParserService = createDecorator('treeSitterParserService'); + +export interface ITreeSitterParserService { + readonly _serviceBrand: undefined; + onDidAddLanguage: Event<{ id: string; language: Parser.Language }>; + getOrInitLanguage(languageId: string): Parser.Language | undefined; + getParseResult(textModel: ITextModel): ITreeSitterParseResult | undefined; +} + +export interface ITreeSitterParseResult { + readonly tree: Parser.Tree | undefined; + readonly language: Parser.Language; +} diff --git a/patched-vscode/src/vs/editor/common/standalone/standaloneEnums.ts b/patched-vscode/src/vs/editor/common/standalone/standaloneEnums.ts index 4c5c84ee..7cd16daa 100644 --- a/patched-vscode/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/patched-vscode/src/vs/editor/common/standalone/standaloneEnums.ts @@ -261,68 +261,69 @@ export enum EditorOption { pasteAs = 85, parameterHints = 86, peekWidgetDefaultFocus = 87, - definitionLinkOpensInPeek = 88, - quickSuggestions = 89, - quickSuggestionsDelay = 90, - readOnly = 91, - readOnlyMessage = 92, - renameOnType = 93, - renderControlCharacters = 94, - renderFinalNewline = 95, - renderLineHighlight = 96, - renderLineHighlightOnlyWhenFocus = 97, - renderValidationDecorations = 98, - renderWhitespace = 99, - revealHorizontalRightPadding = 100, - roundedSelection = 101, - rulers = 102, - scrollbar = 103, - scrollBeyondLastColumn = 104, - scrollBeyondLastLine = 105, - scrollPredominantAxis = 106, - selectionClipboard = 107, - selectionHighlight = 108, - selectOnLineNumbers = 109, - showFoldingControls = 110, - showUnused = 111, - snippetSuggestions = 112, - smartSelect = 113, - smoothScrolling = 114, - stickyScroll = 115, - stickyTabStops = 116, - stopRenderingLineAfter = 117, - suggest = 118, - suggestFontSize = 119, - suggestLineHeight = 120, - suggestOnTriggerCharacters = 121, - suggestSelection = 122, - tabCompletion = 123, - tabIndex = 124, - unicodeHighlighting = 125, - unusualLineTerminators = 126, - useShadowDOM = 127, - useTabStops = 128, - wordBreak = 129, - wordSegmenterLocales = 130, - wordSeparators = 131, - wordWrap = 132, - wordWrapBreakAfterCharacters = 133, - wordWrapBreakBeforeCharacters = 134, - wordWrapColumn = 135, - wordWrapOverride1 = 136, - wordWrapOverride2 = 137, - wrappingIndent = 138, - wrappingStrategy = 139, - showDeprecated = 140, - inlayHints = 141, - editorClassName = 142, - pixelRatio = 143, - tabFocusMode = 144, - layoutInfo = 145, - wrappingInfo = 146, - defaultColorDecorators = 147, - colorDecoratorsActivatedOn = 148, - inlineCompletionsAccessibilityVerbose = 149 + placeholder = 88, + definitionLinkOpensInPeek = 89, + quickSuggestions = 90, + quickSuggestionsDelay = 91, + readOnly = 92, + readOnlyMessage = 93, + renameOnType = 94, + renderControlCharacters = 95, + renderFinalNewline = 96, + renderLineHighlight = 97, + renderLineHighlightOnlyWhenFocus = 98, + renderValidationDecorations = 99, + renderWhitespace = 100, + revealHorizontalRightPadding = 101, + roundedSelection = 102, + rulers = 103, + scrollbar = 104, + scrollBeyondLastColumn = 105, + scrollBeyondLastLine = 106, + scrollPredominantAxis = 107, + selectionClipboard = 108, + selectionHighlight = 109, + selectOnLineNumbers = 110, + showFoldingControls = 111, + showUnused = 112, + snippetSuggestions = 113, + smartSelect = 114, + smoothScrolling = 115, + stickyScroll = 116, + stickyTabStops = 117, + stopRenderingLineAfter = 118, + suggest = 119, + suggestFontSize = 120, + suggestLineHeight = 121, + suggestOnTriggerCharacters = 122, + suggestSelection = 123, + tabCompletion = 124, + tabIndex = 125, + unicodeHighlighting = 126, + unusualLineTerminators = 127, + useShadowDOM = 128, + useTabStops = 129, + wordBreak = 130, + wordSegmenterLocales = 131, + wordSeparators = 132, + wordWrap = 133, + wordWrapBreakAfterCharacters = 134, + wordWrapBreakBeforeCharacters = 135, + wordWrapColumn = 136, + wordWrapOverride1 = 137, + wordWrapOverride2 = 138, + wrappingIndent = 139, + wrappingStrategy = 140, + showDeprecated = 141, + inlayHints = 142, + editorClassName = 143, + pixelRatio = 144, + tabFocusMode = 145, + layoutInfo = 146, + wrappingInfo = 147, + defaultColorDecorators = 148, + colorDecoratorsActivatedOn = 149, + inlineCompletionsAccessibilityVerbose = 150 } /** diff --git a/patched-vscode/src/vs/editor/common/standaloneStrings.ts b/patched-vscode/src/vs/editor/common/standaloneStrings.ts index 81ffaa07..458c7247 100644 --- a/patched-vscode/src/vs/editor/common/standaloneStrings.ts +++ b/patched-vscode/src/vs/editor/common/standaloneStrings.ts @@ -18,14 +18,22 @@ export namespace AccessibilityHelpNLS { export const auto_off = nls.localize("auto_off", "The application is configured to never be optimized for usage with a Screen Reader."); export const screenReaderModeEnabled = nls.localize("screenReaderModeEnabled", "Screen Reader Optimized Mode enabled."); export const screenReaderModeDisabled = nls.localize("screenReaderModeDisabled", "Screen Reader Optimized Mode disabled."); - export const tabFocusModeOnMsg = nls.localize("tabFocusModeOnMsg", "Pressing Tab in the current editor will move focus to the next focusable element. Toggle this behavior"); - export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior"); - export const stickScroll = nls.localize("stickScrollKb", "Focus Sticky Scroll to focus the currently nested scopes."); + export const tabFocusModeOnMsg = nls.localize("tabFocusModeOnMsg", "Pressing Tab in the current editor will move focus to the next focusable element. Toggle this behavior{0}.", ''); + export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior{0}.", ''); + export const stickScroll = nls.localize("stickScrollKb", "Focus Sticky Scroll{0} to focus the currently nested scopes.", ''); + export const codeFolding = nls.localize("codeFolding", "Use code folding to collapse blocks of code and focus on the code you're interested in via the Toggle Folding Command{0}.", ''); + export const intellisense = nls.localize("intellisense", "Use Intellisense to improve coding efficiency and reduce errors. Trigger suggestions{0}.", ''); + export const showOrFocusHover = nls.localize("showOrFocusHover", "Show or focus the hover{0} to read information about the current symbol.", ''); + export const goToSymbol = nls.localize("goToSymbol", "Go to Symbol{0} to quickly navigate between symbols in the current file.", ''); export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help"); export const listSignalSounds = nls.localize("listSignalSoundsCommand", "Run the command: List Signal Sounds for an overview of all sounds and their current status."); export const listAlerts = nls.localize("listAnnouncementsCommand", "Run the command: List Signal Announcements for an overview of announcements and their current status."); - export const quickChat = nls.localize("quickChatCommand", "Toggle quick chat to open or close a chat session.",); - export const startInlineChat = nls.localize("startInlineChatCommand", "Start inline chat to create an in editor chat session."); + export const quickChat = nls.localize("quickChatCommand", "Toggle quick chat{0} to open or close a chat session.", ''); + export const startInlineChat = nls.localize("startInlineChatCommand", "Start inline chat{0} to create an in editor chat session.", ''); + export const startDebugging = nls.localize('debug.startDebugging', "The Debug: Start Debugging command{0} will start a debug session.", ''); + export const setBreakpoint = nls.localize('debugConsole.setBreakpoint', "The Debug: Inline Breakpoint command{0} will set or unset a breakpoint at the current cursor position in the active editor.", ''); + export const addToWatch = nls.localize('debugConsole.addToWatch', "The Debug: Add to Watch command{0} will add the selected text to the watch view.", ''); + export const debugExecuteSelection = nls.localize('debugConsole.executeSelection', "The Debug: Execute Selection command{0} will execute the selected text in the debug console.", ''); } export namespace InspectTokensNLS { @@ -52,7 +60,6 @@ export namespace QuickOutlineNLS { export namespace StandaloneCodeEditorNLS { export const editorViewAccessibleLabel = nls.localize('editorViewAccessibleLabel', "Editor content"); - export const accessibilityHelpMessage = nls.localize('accessibilityHelpMessage', "Press Alt+F1 for Accessibility Options."); } export namespace ToggleHighContrastNLS { diff --git a/patched-vscode/src/vs/editor/common/textModelEvents.ts b/patched-vscode/src/vs/editor/common/textModelEvents.ts index 58c720ac..7d63afec 100644 --- a/patched-vscode/src/vs/editor/common/textModelEvents.ts +++ b/patched-vscode/src/vs/editor/common/textModelEvents.ts @@ -55,6 +55,9 @@ export interface IModelContentChange { * An event describing a change in the text of a model. */ export interface IModelContentChangedEvent { + /** + * The changes are ordered from the end of the document to the beginning, so they should be safe to apply in sequence. + */ readonly changes: IModelContentChange[]; /** * The (new) end-of-line character. diff --git a/patched-vscode/src/vs/editor/common/tokenizationRegistry.ts b/patched-vscode/src/vs/editor/common/tokenizationRegistry.ts index d9fb1bba..15ad1b85 100644 --- a/patched-vscode/src/vs/editor/common/tokenizationRegistry.ts +++ b/patched-vscode/src/vs/editor/common/tokenizationRegistry.ts @@ -6,13 +6,13 @@ import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ITokenizationRegistry, ITokenizationSupport, ITokenizationSupportChangedEvent, ILazyTokenizationSupport } from 'vs/editor/common/languages'; +import { ITokenizationRegistry, ITokenizationSupportChangedEvent, ILazyTokenizationSupport } from 'vs/editor/common/languages'; import { ColorId } from 'vs/editor/common/encodedTokenAttributes'; -export class TokenizationRegistry implements ITokenizationRegistry { +export class TokenizationRegistry implements ITokenizationRegistry { - private readonly _tokenizationSupports = new Map(); - private readonly _factories = new Map(); + private readonly _tokenizationSupports = new Map(); + private readonly _factories = new Map>(); private readonly _onDidChange = new Emitter(); public readonly onDidChange: Event = this._onDidChange.event; @@ -30,7 +30,7 @@ export class TokenizationRegistry implements ITokenizationRegistry { }); } - public register(languageId: string, support: ITokenizationSupport): IDisposable { + public register(languageId: string, support: TSupport): IDisposable { this._tokenizationSupports.set(languageId, support); this.handleChange([languageId]); return toDisposable(() => { @@ -42,11 +42,11 @@ export class TokenizationRegistry implements ITokenizationRegistry { }); } - public get(languageId: string): ITokenizationSupport | null { + public get(languageId: string): TSupport | null { return this._tokenizationSupports.get(languageId) || null; } - public registerFactory(languageId: string, factory: ILazyTokenizationSupport): IDisposable { + public registerFactory(languageId: string, factory: ILazyTokenizationSupport): IDisposable { this._factories.get(languageId)?.dispose(); const myData = new TokenizationSupportFactoryData(this, languageId, factory); this._factories.set(languageId, myData); @@ -60,7 +60,7 @@ export class TokenizationRegistry implements ITokenizationRegistry { }); } - public async getOrCreate(languageId: string): Promise { + public async getOrCreate(languageId: string): Promise { // check first if the support is already set const tokenizationSupport = this.get(languageId); if (tokenizationSupport) { @@ -112,7 +112,7 @@ export class TokenizationRegistry implements ITokenizationRegistry { } } -class TokenizationSupportFactoryData extends Disposable { +class TokenizationSupportFactoryData extends Disposable { private _isDisposed: boolean = false; private _resolvePromise: Promise | null = null; @@ -123,9 +123,9 @@ class TokenizationSupportFactoryData extends Disposable { } constructor( - private readonly _registry: TokenizationRegistry, + private readonly _registry: TokenizationRegistry, private readonly _languageId: string, - private readonly _factory: ILazyTokenizationSupport, + private readonly _factory: ILazyTokenizationSupport, ) { super(); } diff --git a/patched-vscode/src/vs/editor/common/viewModel/viewModelImpl.ts b/patched-vscode/src/vs/editor/common/viewModel/viewModelImpl.ts index c5af99cc..50366d54 100644 --- a/patched-vscode/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/patched-vscode/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -71,6 +71,7 @@ export class ViewModel extends Disposable implements IViewModel { private readonly languageConfigurationService: ILanguageConfigurationService, private readonly _themeService: IThemeService, private readonly _attachedView: IAttachedView, + private readonly _transactionalTarget: IBatchableTarget, ) { super(); @@ -1102,12 +1103,14 @@ export class ViewModel extends Disposable implements IViewModel { //#endregion private _withViewEventsCollector(callback: (eventsCollector: ViewModelEventsCollector) => T): T { - try { - const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); - return callback(eventsCollector); - } finally { - this._eventDispatcher.endEmitViewEvents(); - } + return this._transactionalTarget.batchChanges(() => { + try { + const eventsCollector = this._eventDispatcher.beginEmitViewEvents(); + return callback(eventsCollector); + } finally { + this._eventDispatcher.endEmitViewEvents(); + } + }); } public batchEvents(callback: () => void): void { @@ -1127,6 +1130,13 @@ export class ViewModel extends Disposable implements IViewModel { } } +export interface IBatchableTarget { + /** + * Allows the target to apply the changes introduced by the callback in a batch. + */ + batchChanges(cb: () => T): T; +} + class ViewportStart implements IDisposable { public static create(model: ITextModel): ViewportStart { diff --git a/patched-vscode/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts b/patched-vscode/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts index ffd9e324..b3530eaa 100644 --- a/patched-vscode/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts +++ b/patched-vscode/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts @@ -23,7 +23,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', { dark: '#A0A0A0', light: '#A0A0A0', hcDark: '#A0A0A0', hcLight: '#A0A0A0' }, nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.')); +const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', '#A0A0A0', nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.')); class JumpToBracketAction extends EditorAction { constructor() { diff --git a/patched-vscode/src/vs/editor/contrib/bracketMatching/test/browser/bracketMatching.test.ts b/patched-vscode/src/vs/editor/contrib/bracketMatching/test/browser/bracketMatching.test.ts index 289fe8aa..7a5f7e10 100644 --- a/patched-vscode/src/vs/editor/contrib/bracketMatching/test/browser/bracketMatching.test.ts +++ b/patched-vscode/src/vs/editor/contrib/bracketMatching/test/browser/bracketMatching.test.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 * as assert from 'assert'; +import assert from 'assert'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; diff --git a/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts b/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts index 4a11f630..1dd22e4c 100644 --- a/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts +++ b/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts @@ -45,3 +45,15 @@ Registry.as(Extensions.Configuration).registerConfigurat }, } }); + +Registry.as(Extensions.Configuration).registerConfiguration({ + ...editorConfigurationBaseNode, + properties: { + 'editor.codeActions.triggerOnFocusChange': { + type: 'boolean', + scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, + markdownDescription: nls.localize('triggerOnFocusChange', 'Enable triggering {0} when {1} is set to {2}. Code Actions must be set to {3} to be triggered for window and focus changes.', '`#editor.codeActionsOnSave#`', '`#files.autoSave#`', '`afterDelay`', '`always`'), + default: false, + }, + } +}); diff --git a/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 65dc839d..862459df 100644 --- a/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -41,7 +41,6 @@ import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/ import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - interface IActionShowOptions { readonly includeDisabledActions?: boolean; readonly fromLightbulb?: boolean; @@ -85,7 +84,7 @@ export class CodeActionController extends Disposable implements IEditorContribut super(); this._editor = editor; - this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService, _configurationService)); + this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService, _configurationService, this._telemetryService)); this._register(this._model.onDidChangeState(newState => this.update(newState))); this._lightBulbWidget = new Lazy(() => { @@ -107,29 +106,6 @@ export class CodeActionController extends Disposable implements IEditorContribut } private async showCodeActionsFromLightbulb(actions: CodeActionSet, at: IAnchor | IPosition): Promise { - - // Telemetry for showing code actions from lightbulb. Shows us how often it was clicked. - type ShowCodeActionListEvent = { - codeActionListLength: number; - codeActions: string[]; - codeActionProviders: string[]; - }; - - type ShowListEventClassification = { - codeActionListLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The length of the code action list from the lightbulb widget.' }; - codeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The title of code actions in this menu.' }; - codeActionProviders: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The provider of code actions in this menu.' }; - owner: 'justschen'; - comment: 'Event used to gain insights into what code actions are being shown'; - }; - - this._telemetryService.publicLog2('codeAction.showCodeActionsFromLightbulb', { - codeActionListLength: actions.validActions.length, - codeActions: actions.validActions.map(action => action.action.title), - codeActionProviders: actions.validActions.map(action => action.provider?.displayName ?? ''), - }); - - if (actions.allAIFixes && actions.validActions.length === 1) { const actionItem = actions.validActions[0]; const command = actionItem.action.command; @@ -183,11 +159,12 @@ export class CodeActionController extends Disposable implements IEditorContribut public hideLightBulbWidget(): void { this._lightBulbWidget.rawValue?.hide(); + this._lightBulbWidget.rawValue?.gutterHide(); } private async update(newState: CodeActionsState.State): Promise { if (newState.type !== CodeActionsState.Type.Triggered) { - this._lightBulbWidget.rawValue?.hide(); + this.hideLightBulbWidget(); return; } @@ -203,6 +180,12 @@ export class CodeActionController extends Disposable implements IEditorContribut return; } + + const selection = this._editor.getSelection(); + if (selection?.startLineNumber !== newState.position.lineNumber) { + return; + } + this._lightBulbWidget.value?.update(actions, newState.trigger, newState.position); if (newState.trigger.type === CodeActionTriggerType.Invoke) { @@ -212,7 +195,7 @@ export class CodeActionController extends Disposable implements IEditorContribut const validActionToApply = this.tryGetValidActionToApply(newState.trigger, actions); if (validActionToApply) { try { - this._lightBulbWidget.value?.hide(); + this.hideLightBulbWidget(); await this._applyCodeAction(validActionToApply, false, false, ApplyCodeActionReason.FromCodeActions); } finally { actions.dispose(); @@ -312,28 +295,6 @@ export class CodeActionController extends Disposable implements IEditorContribut onHide: (didCancel?) => { this._editor?.focus(); currentDecorations.clear(); - // Telemetry for showing code actions here. only log on `showLightbulb`. Logs when code action list is quit out. - if (options.fromLightbulb && didCancel !== undefined) { - type ShowCodeActionListEvent = { - codeActionListLength: number; - didCancel: boolean; - codeActions: string[]; - }; - - type ShowListEventClassification = { - codeActionListLength: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The length of the code action list when quit out. Can be from any code action menu.' }; - didCancel: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code action was cancelled or selected.' }; - codeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What code actions were available when cancelled.' }; - owner: 'justschen'; - comment: 'Event used to gain insights into how many valid code actions are being shown'; - }; - - this._telemetryService.publicLog2('codeAction.showCodeActionList.onHide', { - codeActionListLength: actions.validActions.length, - didCancel: didCancel, - codeActions: actions.validActions.map(action => action.action.title), - }); - } }, onHover: async (action: CodeActionItem, token: CancellationToken) => { if (token.isCancellationRequested) { diff --git a/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts b/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts index 0e11fef8..07c28659 100644 --- a/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts +++ b/patched-vscode/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts @@ -22,6 +22,8 @@ import { IEditorProgressService, Progress } from 'vs/platform/progress/common/pr import { CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types'; import { getCodeActions } from './codeAction'; import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export const SUPPORTED_CODE_ACTIONS = new RawContextKey('supportedCodeAction', ''); @@ -172,6 +174,7 @@ export class CodeActionModel extends Disposable { contextKeyService: IContextKeyService, private readonly _progressService?: IEditorProgressService, private readonly _configurationService?: IConfigurationService, + private readonly _telemetryService?: ITelemetryService ) { super(); this._supportedCodeActions = SUPPORTED_CODE_ACTIONS.bindTo(contextKeyService); @@ -315,7 +318,35 @@ export class CodeActionModel extends Disposable { } } } - // temporarilly hiding here as this is enabled/disabled behind a setting. + + // Case for manual triggers - specifically Source Actions and Refactors + if (trigger.trigger.type === CodeActionTriggerType.Invoke) { + const sw = new StopWatch(); + const codeActions = await getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); + + // Telemetry for duration of each code action on save. + if (this._telemetryService) { + type RenderActionMenu = { + codeActions: number; + duration: number; + }; + + type RenderActionMenuClassification = { + owner: 'justschen'; + comment: 'Information about how long it took for code actions to be received from the provider and shown in the UI.'; + codeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of valid code actions received from TS.' }; + duration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Duration it took for TS to return the action to run for each kind. ' }; + }; + + this._telemetryService.publicLog2('codeAction.invokedDurations', { + codeActions: codeActions.validActions.length, + duration: sw.elapsed() + }); + } + + return codeActions; + } + return getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); }); if (trigger.trigger.type === CodeActionTriggerType.Invoke) { diff --git a/patched-vscode/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.css b/patched-vscode/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.css index ea05c857..4961d4c9 100644 --- a/patched-vscode/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.css +++ b/patched-vscode/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.css @@ -41,6 +41,25 @@ width: 100%; height: 100%; opacity: 0.3; - background-color: var(--vscode-editor-background); z-index: 1; } + +/* gutter decoration */ +.monaco-editor .glyph-margin-widgets .cgmr[class*="codicon-gutter-lightbulb"] { + display: block; + cursor: pointer; +} + +.monaco-editor .glyph-margin-widgets .cgmr.codicon-gutter-lightbulb, +.monaco-editor .glyph-margin-widgets .cgmr.codicon-gutter-lightbulb-sparkle { + color: var(--vscode-editorLightBulb-foreground); +} + +.monaco-editor .glyph-margin-widgets .cgmr.codicon-gutter-lightbulb-auto-fix, +.monaco-editor .glyph-margin-widgets .cgmr.codicon-gutter-lightbulb-aifix-auto-fix { + color: var(--vscode-editorLightBulbAutoFix-foreground, var(--vscode-editorLightBulb-foreground)); +} + +.monaco-editor .glyph-margin-widgets .cgmr.codicon-gutter-lightbulb-sparkle-filled { + color: var(--vscode-editorLightBulbAi-foreground, var(--vscode-icon-foreground)); +} diff --git a/patched-vscode/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts b/patched-vscode/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts index 94518efd..bc71291b 100644 --- a/patched-vscode/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts @@ -10,15 +10,24 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import 'vs/css!./lightBulbWidget'; -import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; +import { GlyphMarginLane, IModelDecorationsChangeAccessor, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { computeIndentLevel } from 'vs/editor/common/model/utils'; import { autoFixCommandId, quickFixCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionSet, CodeActionTrigger } from 'vs/editor/contrib/codeAction/common/types'; import * as nls from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { Range } from 'vs/editor/common/core/range'; + +const GUTTER_LIGHTBULB_ICON = registerIcon('gutter-lightbulb', Codicon.lightBulb, nls.localize('gutterLightbulbWidget', 'Icon which spawns code actions menu from the gutter when there is no space in the editor.')); +const GUTTER_LIGHTBULB_AUTO_FIX_ICON = registerIcon('gutter-lightbulb-auto-fix', Codicon.lightbulbAutofix, nls.localize('gutterLightbulbAutoFixWidget', 'Icon which spawns code actions menu from the gutter when there is no space in the editor and a quick fix is available.')); +const GUTTER_LIGHTBULB_AIFIX_ICON = registerIcon('gutter-lightbulb-sparkle', Codicon.lightbulbSparkle, nls.localize('gutterLightbulbAIFixWidget', 'Icon which spawns code actions menu from the gutter when there is no space in the editor and an AI fix is available.')); +const GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON = registerIcon('gutter-lightbulb-aifix-auto-fix', Codicon.lightbulbSparkleAutofix, nls.localize('gutterLightbulbAIFixAutoFixWidget', 'Icon which spawns code actions menu from the gutter when there is no space in the editor and an AI fix and a quick fix is available.')); +const GUTTER_SPARKLE_FILLED_ICON = registerIcon('gutter-lightbulb-sparkle-filled', Codicon.sparkleFilled, nls.localize('gutterLightbulbSparkleFilledWidget', 'Icon which spawns code actions menu from the gutter when there is no space in the editor and an AI fix and a quick fix is available.')); namespace LightBulbState { @@ -44,6 +53,14 @@ namespace LightBulbState { } export class LightBulbWidget extends Disposable implements IContentWidget { + private _gutterDecorationID: string | undefined; + + private static readonly GUTTER_DECORATION = ModelDecorationOptions.register({ + description: 'codicon-gutter-lightbulb-decoration', + glyphMarginClassName: ThemeIcon.asClassName(Codicon.lightBulb), + glyphMargin: { position: GlyphMarginLane.Left }, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + }); public static readonly ID = 'editor.contrib.lightbulbWidget'; @@ -55,15 +72,25 @@ export class LightBulbWidget extends Disposable implements IContentWidget { public readonly onClick = this._onClick.event; private _state: LightBulbState.State = LightBulbState.Hidden; + private _gutterState: LightBulbState.State = LightBulbState.Hidden; private _iconClasses: string[] = []; + private readonly lightbulbClasses = [ + 'codicon-' + GUTTER_LIGHTBULB_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AUTO_FIX_ICON.id, + 'codicon-' + GUTTER_LIGHTBULB_AIFIX_ICON.id, + 'codicon-' + GUTTER_SPARKLE_FILLED_ICON.id + ]; + private _preferredKbLabel?: string; private _quickFixKbLabel?: string; + private gutterDecoration: ModelDecorationOptions = LightBulbWidget.GUTTER_DECORATION; + constructor( private readonly _editor: ICodeEditor, - @IKeybindingService private readonly _keybindingService: IKeybindingService, - @ICommandService commandService: ICommandService + @IKeybindingService private readonly _keybindingService: IKeybindingService ) { super(); @@ -79,6 +106,10 @@ export class LightBulbWidget extends Disposable implements IContentWidget { if (this.state.type !== LightBulbState.Type.Showing || !editorModel || this.state.editorPosition.lineNumber >= editorModel.getLineCount()) { this.hide(); } + + if (this.gutterState.type !== LightBulbState.Type.Showing || !editorModel || this.gutterState.editorPosition.lineNumber >= editorModel.getLineCount()) { + this.gutterHide(); + } })); this._register(dom.addStandardDisposableGenericMouseDownListener(this._domNode, e => { @@ -121,14 +152,47 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._register(Event.runAndSubscribe(this._keybindingService.onDidUpdateKeybindings, () => { this._preferredKbLabel = this._keybindingService.lookupKeybinding(autoFixCommandId)?.getLabel() ?? undefined; this._quickFixKbLabel = this._keybindingService.lookupKeybinding(quickFixCommandId)?.getLabel() ?? undefined; - this._updateLightBulbTitleAndIcon(); })); + + this._register(this._editor.onMouseDown(async (e: IEditorMouseEvent) => { + + if (!e.target.element || !this.lightbulbClasses.some(cls => e.target.element && e.target.element.classList.contains(cls))) { + return; + } + + if (this.gutterState.type !== LightBulbState.Type.Showing) { + return; + } + + // Make sure that focus / cursor location is not lost when clicking widget icon + this._editor.focus(); + + // a bit of extra work to make sure the menu + // doesn't cover the line-text + const { top, height } = dom.getDomNodePagePosition(e.target.element); + const lineHeight = this._editor.getOption(EditorOption.lineHeight); + + let pad = Math.floor(lineHeight / 3); + if (this.gutterState.widgetPosition.position !== null && this.gutterState.widgetPosition.position.lineNumber < this.gutterState.editorPosition.lineNumber) { + pad += lineHeight; + } + + this._onClick.fire({ + x: e.event.posx, + y: top + height + pad, + actions: this.gutterState.actions, + trigger: this.gutterState.trigger, + }); + })); } override dispose(): void { super.dispose(); this._editor.removeContentWidget(this); + if (this._gutterDecorationID) { + this._removeGutterDecoration(this._gutterDecorationID); + } } getId(): string { @@ -145,17 +209,26 @@ export class LightBulbWidget extends Disposable implements IContentWidget { public update(actions: CodeActionSet, trigger: CodeActionTrigger, atPosition: IPosition) { if (actions.validActions.length <= 0) { + this.gutterHide(); + return this.hide(); + } + + const hasTextFocus = this._editor.hasTextFocus(); + if (!hasTextFocus) { + this.gutterHide(); return this.hide(); } const options = this._editor.getOptions(); if (!options.get(EditorOption.lightbulb).enabled) { + this.gutterHide(); return this.hide(); } const model = this._editor.getModel(); if (!model) { + this.gutterHide(); return this.hide(); } @@ -170,11 +243,63 @@ export class LightBulbWidget extends Disposable implements IContentWidget { return lineNumber > 2 && this._editor.getTopForLineNumber(lineNumber) === this._editor.getTopForLineNumber(lineNumber - 1); }; + // Check for glyph margin decorations of any kind + const currLineDecorations = this._editor.getLineDecorations(lineNumber); + let hasDecoration = false; + if (currLineDecorations) { + for (const decoration of currLineDecorations) { + const glyphClass = decoration.options.glyphMarginClassName; + + if (glyphClass && !this.lightbulbClasses.some(className => glyphClass.includes(className))) { + hasDecoration = true; + break; + } + } + } + let effectiveLineNumber = lineNumber; let effectiveColumnNumber = 1; if (!lineHasSpace) { + // Checks if line is empty or starts with any amount of whitespace + const isLineEmptyOrIndented = (lineNumber: number): boolean => { + const lineContent = model.getLineContent(lineNumber); + return /^\s*$|^\s+/.test(lineContent) || lineContent.length <= effectiveColumnNumber; + }; + if (lineNumber > 1 && !isFolded(lineNumber - 1)) { - effectiveLineNumber -= 1; + const lineCount = model.getLineCount(); + const endLine = lineNumber === lineCount; + const prevLineEmptyOrIndented = lineNumber > 1 && isLineEmptyOrIndented(lineNumber - 1); + const nextLineEmptyOrIndented = !endLine && isLineEmptyOrIndented(lineNumber + 1); + const currLineEmptyOrIndented = isLineEmptyOrIndented(lineNumber); + const notEmpty = !nextLineEmptyOrIndented && !prevLineEmptyOrIndented; + + // check above and below. if both are blocked, display lightbulb in the gutter. + if (!nextLineEmptyOrIndented && !prevLineEmptyOrIndented && !hasDecoration) { + this.gutterState = new LightBulbState.Showing(actions, trigger, atPosition, { + position: { lineNumber: effectiveLineNumber, column: effectiveColumnNumber }, + preference: LightBulbWidget._posPref + }); + this.renderGutterLightbub(); + return this.hide(); + } else if (prevLineEmptyOrIndented || endLine || (prevLineEmptyOrIndented && !currLineEmptyOrIndented)) { + effectiveLineNumber -= 1; + } else if (nextLineEmptyOrIndented || (notEmpty && currLineEmptyOrIndented)) { + effectiveLineNumber += 1; + } + } else if (lineNumber === 1 && (lineNumber === model.getLineCount() || !isLineEmptyOrIndented(lineNumber + 1) && !isLineEmptyOrIndented(lineNumber))) { + // special checks for first line blocked vs. not blocked. + this.gutterState = new LightBulbState.Showing(actions, trigger, atPosition, { + position: { lineNumber: effectiveLineNumber, column: effectiveColumnNumber }, + preference: LightBulbWidget._posPref + }); + + if (hasDecoration) { + this.gutterHide(); + } else { + this.renderGutterLightbub(); + return this.hide(); + } } else if ((lineNumber < model.getLineCount()) && !isFolded(lineNumber + 1)) { effectiveLineNumber += 1; } else if (column * fontInfo.spaceWidth < 22) { @@ -190,6 +315,11 @@ export class LightBulbWidget extends Disposable implements IContentWidget { preference: LightBulbWidget._posPref }); + if (this._gutterDecorationID) { + this._removeGutterDecoration(this._gutterDecorationID); + this.gutterHide(); + } + const validActions = actions.validActions; const actionKind = actions.validActions[0].action.kind; if (validActions.length !== 1 || !actionKind) { @@ -197,7 +327,6 @@ export class LightBulbWidget extends Disposable implements IContentWidget { return; } - this._editor.layoutContentWidget(this); } @@ -210,6 +339,18 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._editor.layoutContentWidget(this); } + public gutterHide(): void { + if (this.gutterState === LightBulbState.Hidden) { + return; + } + + if (this._gutterDecorationID) { + this._removeGutterDecoration(this._gutterDecorationID); + } + + this.gutterState = LightBulbState.Hidden; + } + private get state(): LightBulbState.State { return this._state; } private set state(value) { @@ -217,6 +358,13 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._updateLightBulbTitleAndIcon(); } + private get gutterState(): LightBulbState.State { return this._gutterState; } + + private set gutterState(value) { + this._gutterState = value; + this._updateGutterLightBulbTitleAndIcon(); + } + private _updateLightBulbTitleAndIcon(): void { this._domNode.classList.remove(...this._iconClasses); this._iconClasses = []; @@ -246,6 +394,74 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._domNode.classList.add(...this._iconClasses); } + private _updateGutterLightBulbTitleAndIcon(): void { + if (this.gutterState.type !== LightBulbState.Type.Showing) { + return; + } + let icon: ThemeIcon; + let autoRun = false; + if (this.gutterState.actions.allAIFixes) { + icon = GUTTER_SPARKLE_FILLED_ICON; + if (this.gutterState.actions.validActions.length === 1) { + autoRun = true; + } + } else if (this.gutterState.actions.hasAutoFix) { + if (this.gutterState.actions.hasAIFix) { + icon = GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON; + } else { + icon = GUTTER_LIGHTBULB_AUTO_FIX_ICON; + } + } else if (this.gutterState.actions.hasAIFix) { + icon = GUTTER_LIGHTBULB_AIFIX_ICON; + } else { + icon = GUTTER_LIGHTBULB_ICON; + } + this._updateLightbulbTitle(this.gutterState.actions.hasAutoFix, autoRun); + + const GUTTER_DECORATION = ModelDecorationOptions.register({ + description: 'codicon-gutter-lightbulb-decoration', + glyphMarginClassName: ThemeIcon.asClassName(icon), + glyphMargin: { position: GlyphMarginLane.Left }, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + }); + + this.gutterDecoration = GUTTER_DECORATION; + } + + /* Gutter Helper Functions */ + private renderGutterLightbub(): void { + const selection = this._editor.getSelection(); + if (!selection) { + return; + } + + if (this._gutterDecorationID === undefined) { + this._addGutterDecoration(selection.startLineNumber); + } else { + this._updateGutterDecoration(this._gutterDecorationID, selection.startLineNumber); + } + } + + private _addGutterDecoration(lineNumber: number) { + this._editor.changeDecorations((accessor: IModelDecorationsChangeAccessor) => { + this._gutterDecorationID = accessor.addDecoration(new Range(lineNumber, 0, lineNumber, 0), this.gutterDecoration); + }); + } + + private _removeGutterDecoration(decorationId: string) { + this._editor.changeDecorations((accessor: IModelDecorationsChangeAccessor) => { + accessor.removeDecoration(decorationId); + this._gutterDecorationID = undefined; + }); + } + + private _updateGutterDecoration(decorationId: string, lineNumber: number) { + this._editor.changeDecorations((accessor: IModelDecorationsChangeAccessor) => { + accessor.changeDecoration(decorationId, new Range(lineNumber, 0, lineNumber, 0)); + accessor.changeDecorationOptions(decorationId, this.gutterDecoration); + }); + } + private _updateLightbulbTitle(autoFix: boolean, autoRun: boolean): void { if (this.state.type !== LightBulbState.Type.Showing) { return; diff --git a/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts b/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts index c1783a39..a98da2b0 100644 --- a/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts +++ b/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.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 * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; import { DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts b/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts index 641f1491..664a36b2 100644 --- a/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts +++ b/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { KeyCodeChord } from 'vs/base/common/keybindings'; import { KeyCode } from 'vs/base/common/keyCodes'; import { OperatingSystem } from 'vs/base/common/platform'; @@ -101,4 +101,3 @@ function createCodeActionKeybinding(keycode: KeyCode, command: string, commandAr null, false); } - diff --git a/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts b/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts index 71d6df33..5946fb24 100644 --- a/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { promiseWithResolvers } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; diff --git a/patched-vscode/src/vs/editor/contrib/codelens/browser/codeLensCache.ts b/patched-vscode/src/vs/editor/contrib/codelens/browser/codeLensCache.ts index 67fa3b80..f7666b8c 100644 --- a/patched-vscode/src/vs/editor/contrib/codelens/browser/codeLensCache.ts +++ b/patched-vscode/src/vs/editor/contrib/codelens/browser/codeLensCache.ts @@ -61,18 +61,17 @@ export class CodeLensCache implements ICodeLensCache { this._deserialize(raw); // store lens data on shutdown - Event.once(storageService.onWillSaveState)(e => { - if (e.reason === WillSaveStateReason.SHUTDOWN) { - storageService.store(key, this._serialize(), StorageScope.WORKSPACE, StorageTarget.MACHINE); - } + const onWillSaveStateBecauseOfShutdown = Event.filter(storageService.onWillSaveState, e => e.reason === WillSaveStateReason.SHUTDOWN); + Event.once(onWillSaveStateBecauseOfShutdown)(e => { + storageService.store(key, this._serialize(), StorageScope.WORKSPACE, StorageTarget.MACHINE); }); } put(model: ITextModel, data: CodeLensModel): void { // create a copy of the model that is without command-ids // but with comand-labels - const copyItems = data.lenses.map(item => { - return { + const copyItems = data.lenses.map((item): CodeLens => { + return { range: item.symbol.range, command: item.symbol.command && { id: '', title: item.symbol.command?.title }, }; diff --git a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts index e7642a17..af1162de 100644 --- a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts +++ b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts @@ -11,7 +11,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ColorDecorationInjectedTextMarker } from 'vs/editor/contrib/colorPicker/browser/colorDetector'; import { ColorHoverParticipant } from 'vs/editor/contrib/colorPicker/browser/colorHoverParticipant'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes'; @@ -56,7 +56,7 @@ export class ColorContribution extends Disposable implements IEditorContribution return; } - const hoverController = this._editor.getContribution(HoverController.ID); + const hoverController = this._editor.getContribution(ContentHoverController.ID); if (!hoverController) { return; } diff --git a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts index e37a442f..32956ec0 100644 --- a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts +++ b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts @@ -6,7 +6,7 @@ import { AsyncIterableObject } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; @@ -16,11 +16,12 @@ import { getColorPresentations, getColors } from 'vs/editor/contrib/colorPicker/ import { ColorDetector } from 'vs/editor/contrib/colorPicker/browser/colorDetector'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/browser/colorPickerModel'; import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/browser/colorPickerWidget'; -import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { Dimension } from 'vs/base/browser/dom'; +import * as nls from 'vs/nls'; export class ColorHover implements IHoverPart { @@ -50,6 +51,8 @@ export class ColorHoverParticipant implements IEditorHoverParticipant { + const renderedPart = renderHoverParts(this, this._editor, this._themeService, hoverParts, context); + if (!renderedPart) { + return new RenderedHoverParts([]); + } + this._colorPicker = renderedPart.colorPicker; + const renderedHoverPart: IRenderedHoverPart = { + hoverPart: renderedPart.hoverPart, + hoverElement: this._colorPicker.domNode, + dispose() { renderedPart.disposables.dispose(); } + }; + return new RenderedHoverParts([renderedHoverPart]); + } + + public getAccessibleContent(hoverPart: ColorHover): string { + return nls.localize('hoverAccessibilityColorParticipant', 'There is a color picker here.'); + } + + public handleResize(): void { + this._colorPicker?.layout(); + } + + public isColorPickerVisible(): boolean { + return !!this._colorPicker; } } @@ -146,7 +171,7 @@ export class StandaloneColorPickerParticipant { } } - public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: ColorHover[] | StandaloneColorPickerHover[]): IDisposable { + public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: StandaloneColorPickerHover[]): { disposables: IDisposable; hoverPart: StandaloneColorPickerHover; colorPicker: ColorPickerWidget } | undefined { return renderHoverParts(this, this._editor, this._themeService, hoverParts, context); } @@ -178,9 +203,9 @@ async function _createColorHover(participant: ColorHoverParticipant | Standalone } } -function renderHoverParts(participant: ColorHoverParticipant | StandaloneColorPickerParticipant, editor: ICodeEditor, themeService: IThemeService, hoverParts: ColorHover[] | StandaloneColorPickerHover[], context: IEditorHoverRenderContext) { +function renderHoverParts(participant: ColorHoverParticipant | StandaloneColorPickerParticipant, editor: ICodeEditor, themeService: IThemeService, hoverParts: T[], context: IEditorHoverRenderContext): { hoverPart: T; colorPicker: ColorPickerWidget; disposables: DisposableStore } | undefined { if (hoverParts.length === 0 || !editor.hasModel()) { - return Disposable.None; + return undefined; } if (context.setMinimumDimensions) { const minimumHeight = editor.getOption(EditorOption.lineHeight) + 8; @@ -191,13 +216,12 @@ function renderHoverParts(participant: ColorHoverParticipant | StandaloneColorPi const colorHover = hoverParts[0]; const editorModel = editor.getModel(); const model = colorHover.model; - const widget = disposables.add(new ColorPickerWidget(context.fragment, model, editor.getOption(EditorOption.pixelRatio), themeService, participant instanceof StandaloneColorPickerParticipant)); - context.setColorPicker(widget); + const colorPicker = disposables.add(new ColorPickerWidget(context.fragment, model, editor.getOption(EditorOption.pixelRatio), themeService, participant instanceof StandaloneColorPickerParticipant)); let editorUpdatedByColorPicker = false; let range = new Range(colorHover.range.startLineNumber, colorHover.range.startColumn, colorHover.range.endLineNumber, colorHover.range.endColumn); if (participant instanceof StandaloneColorPickerParticipant) { - const color = hoverParts[0].model.color; + const color = colorHover.model.color; participant.color = color; _updateColorPresentations(editorModel, model, color, range, colorHover); disposables.add(model.onColorFlushed((color: Color) => { @@ -221,7 +245,7 @@ function renderHoverParts(participant: ColorHoverParticipant | StandaloneColorPi editor.focus(); } })); - return disposables; + return { hoverPart: colorHover, colorPicker, disposables }; } function _updateEditorModel(editor: IActiveCodeEditor, range: Range, model: ColorPickerModel): Range { diff --git a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 7688defc..b911fe5b 100644 --- a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -468,6 +468,7 @@ export class InsertButton extends Disposable { export class ColorPickerWidget extends Widget implements IEditorHoverColorPickerWidget { private static readonly ID = 'editor.contrib.colorPickerWidget'; + private readonly _domNode: HTMLElement; body: ColorPickerBody; header: ColorPickerHeader; @@ -477,11 +478,11 @@ export class ColorPickerWidget extends Widget implements IEditorHoverColorPicker this._register(PixelRatio.getInstance(dom.getWindow(container)).onDidChange(() => this.layout())); - const element = $('.colorpicker-widget'); - container.appendChild(element); + this._domNode = $('.colorpicker-widget'); + container.appendChild(this._domNode); - this.header = this._register(new ColorPickerHeader(element, this.model, themeService, standaloneColorPicker)); - this.body = this._register(new ColorPickerBody(element, this.model, this.pixelRatio, standaloneColorPicker)); + this.header = this._register(new ColorPickerHeader(this._domNode, this.model, themeService, standaloneColorPicker)); + this.body = this._register(new ColorPickerBody(this._domNode, this.model, this.pixelRatio, standaloneColorPicker)); } getId(): string { @@ -491,4 +492,8 @@ export class ColorPickerWidget extends Widget implements IEditorHoverColorPicker layout(): void { this.body.layout(); } + + get domNode(): HTMLElement { + return this._domNode; + } } diff --git a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts index 8bb5e3ad..3bd7b848 100644 --- a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts +++ b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts @@ -7,26 +7,19 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { ITextModel } from 'vs/editor/common/model'; import { DocumentColorProvider, IColor, IColorInformation, IColorPresentation } from 'vs/editor/common/languages'; -import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; export class DefaultDocumentColorProvider implements DocumentColorProvider { - private _editorWorkerClient: EditorWorkerClient; - constructor( - modelService: IModelService, - languageConfigurationService: ILanguageConfigurationService, - ) { - this._editorWorkerClient = new EditorWorkerClient(modelService, false, 'editorWorkerService', languageConfigurationService); - } + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + ) { } async provideDocumentColors(model: ITextModel, _token: CancellationToken): Promise { - return this._editorWorkerClient.computeDefaultDocumentColors(model.uri); + return this._editorWorkerService.computeDefaultDocumentColors(model.uri); } provideColorPresentations(_model: ITextModel, colorInfo: IColorInformation, _token: CancellationToken): IColorPresentation[] { @@ -49,12 +42,11 @@ export class DefaultDocumentColorProvider implements DocumentColorProvider { class DefaultDocumentColorProviderFeature extends Disposable { constructor( - @IModelService _modelService: IModelService, - @ILanguageConfigurationService _languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService _languageFeaturesService: ILanguageFeaturesService, + @IEditorWorkerService editorWorkerService: IEditorWorkerService, ) { super(); - this._register(_languageFeaturesService.colorProvider.register('*', new DefaultDocumentColorProvider(_modelService, _languageConfigurationService))); + this._register(_languageFeaturesService.colorProvider.register('*', new DefaultDocumentColorProvider(editorWorkerService))); } } diff --git a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget.ts b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget.ts index 4c8cf865..b5fc34cf 100644 --- a/patched-vscode/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget.ts @@ -12,7 +12,7 @@ import { StandaloneColorPickerHover, StandaloneColorPickerParticipant } from 'vs import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EditorHoverStatusBar } from 'vs/editor/contrib/hover/browser/contentHoverStatusBar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ColorPickerWidget, InsertButton } from 'vs/editor/contrib/colorPicker/browser/colorPickerWidget'; +import { InsertButton } from 'vs/editor/contrib/colorPicker/browser/colorPickerWidget'; import { Emitter } from 'vs/base/common/event'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IColorInformation } from 'vs/editor/common/languages'; @@ -22,11 +22,10 @@ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IRange } from 'vs/editor/common/core/range'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { DefaultDocumentColorProvider } from 'vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider'; import * as dom from 'vs/base/browser/dom'; import 'vs/css!./colorPicker'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; export class StandaloneColorPickerController extends Disposable implements IEditorContribution { @@ -38,11 +37,7 @@ export class StandaloneColorPickerController extends Disposable implements IEdit constructor( private readonly _editor: ICodeEditor, @IContextKeyService _contextKeyService: IContextKeyService, - @IModelService private readonly _modelService: IModelService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService, - @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService ) { super(); this._standaloneColorPickerVisible = EditorContextKeys.standaloneColorPickerVisible.bindTo(_contextKeyService); @@ -54,7 +49,12 @@ export class StandaloneColorPickerController extends Disposable implements IEdit return; } if (!this._standaloneColorPickerVisible.get()) { - this._standaloneColorPickerWidget = new StandaloneColorPickerWidget(this._editor, this._standaloneColorPickerVisible, this._standaloneColorPickerFocused, this._instantiationService, this._modelService, this._keybindingService, this._languageFeatureService, this._languageConfigurationService); + this._standaloneColorPickerWidget = this._instantiationService.createInstance( + StandaloneColorPickerWidget, + this._editor, + this._standaloneColorPickerVisible, + this._standaloneColorPickerFocused + ); } else if (!this._standaloneColorPickerFocused.get()) { this._standaloneColorPickerWidget?.focus(); } @@ -102,10 +102,9 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW private readonly _standaloneColorPickerVisible: IContextKey, private readonly _standaloneColorPickerFocused: IContextKey, @IInstantiationService _instantiationService: IInstantiationService, - @IModelService private readonly _modelService: IModelService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, ) { super(); this._standaloneColorPickerVisible.set(true); @@ -205,7 +204,7 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW range: range, color: { red: 0, green: 0, blue: 0, alpha: 1 } }; - const colorHoverResult: { colorHover: StandaloneColorPickerHover; foundInEditor: boolean } | null = await this._standaloneColorPickerParticipant.createColorHover(colorInfo, new DefaultDocumentColorProvider(this._modelService, this._languageConfigurationService), this._languageFeaturesService.colorProvider); + const colorHoverResult: { colorHover: StandaloneColorPickerHover; foundInEditor: boolean } | null = await this._standaloneColorPickerParticipant.createColorHover(colorInfo, new DefaultDocumentColorProvider(this._editorWorkerService), this._languageFeaturesService.colorProvider); if (!colorHoverResult) { return null; } @@ -215,42 +214,42 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW private _render(colorHover: StandaloneColorPickerHover, foundInEditor: boolean) { const fragment = document.createDocumentFragment(); const statusBar = this._register(new EditorHoverStatusBar(this._keybindingService)); - let colorPickerWidget: ColorPickerWidget | undefined; const context: IEditorHoverRenderContext = { fragment, statusBar, - setColorPicker: (widget: ColorPickerWidget) => colorPickerWidget = widget, onContentsChanged: () => { }, hide: () => this.hide() }; this._colorHover = colorHover; - this._register(this._standaloneColorPickerParticipant.renderHoverParts(context, [colorHover])); - if (colorPickerWidget === undefined) { + const renderedHoverPart = this._standaloneColorPickerParticipant.renderHoverParts(context, [colorHover]); + if (!renderedHoverPart) { return; } + this._register(renderedHoverPart.disposables); + const colorPicker = renderedHoverPart.colorPicker; this._body.classList.add('standalone-colorpicker-body'); this._body.style.maxHeight = Math.max(this._editor.getLayoutInfo().height / 4, 250) + 'px'; this._body.style.maxWidth = Math.max(this._editor.getLayoutInfo().width * 0.66, 500) + 'px'; this._body.tabIndex = 0; this._body.appendChild(fragment); - colorPickerWidget.layout(); + colorPicker.layout(); - const colorPickerBody = colorPickerWidget.body; + const colorPickerBody = colorPicker.body; const saturationBoxWidth = colorPickerBody.saturationBox.domNode.clientWidth; const widthOfOriginalColorBox = colorPickerBody.domNode.clientWidth - saturationBoxWidth - CLOSE_BUTTON_WIDTH - PADDING; - const enterButton: InsertButton | null = colorPickerWidget.body.enterButton; + const enterButton: InsertButton | null = colorPicker.body.enterButton; enterButton?.onClicked(() => { this.updateEditor(); this.hide(); }); - const colorPickerHeader = colorPickerWidget.header; + const colorPickerHeader = colorPicker.header; const pickedColorNode = colorPickerHeader.pickedColorNode; pickedColorNode.style.width = saturationBoxWidth + PADDING + 'px'; const originalColorNode = colorPickerHeader.originalColorNode; originalColorNode.style.width = widthOfOriginalColorBox + 'px'; - const closeButton = colorPickerWidget.header.closeButton; + const closeButton = colorPicker.header.closeButton; closeButton?.onClicked(() => { this.hide(); }); diff --git a/patched-vscode/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts b/patched-vscode/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts index ff394a83..f40f7b1e 100644 --- a/patched-vscode/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts +++ b/patched-vscode/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/patched-vscode/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/patched-vscode/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index 95609201..650a15e2 100644 --- a/patched-vscode/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/patched-vscode/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -160,9 +160,7 @@ export class ContextMenuController implements IEditorContribution { const result: IAction[] = []; // get menu groups - const menu = this._menuService.createMenu(menuId, this._contextKeyService); - const groups = menu.getActions({ arg: model.uri }); - menu.dispose(); + const groups = this._menuService.getMenuActions(menuId, this._contextKeyService, { arg: model.uri }); // translate them into other actions for (const group of groups) { @@ -227,7 +225,7 @@ export class ContextMenuController implements IEditorContribution { // Show menu this._contextMenuIsBeingShownCount++; this._contextMenuService.showContextMenu({ - domForShadowRoot: useShadowDOM ? this._editor.getDomNode() : undefined, + domForShadowRoot: useShadowDOM ? this._editor.getOverflowWidgetsDomNode() ?? this._editor.getDomNode() : undefined, getAnchor: () => anchor, diff --git a/patched-vscode/src/vs/editor/contrib/cursorUndo/test/browser/cursorUndo.test.ts b/patched-vscode/src/vs/editor/contrib/cursorUndo/test/browser/cursorUndo.test.ts index 3cecf3ee..d0e3423a 100644 --- a/patched-vscode/src/vs/editor/contrib/cursorUndo/test/browser/cursorUndo.test.ts +++ b/patched-vscode/src/vs/editor/contrib/cursorUndo/test/browser/cursorUndo.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreEditingCommands, CoreNavigationCommands } from 'vs/editor/browser/coreCommands'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/patched-vscode/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts b/patched-vscode/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts index 194b8ee4..185d30db 100644 --- a/patched-vscode/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index 87cddb00..1203f86c 100644 --- a/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -5,11 +5,11 @@ import { addDisposableListener, getActiveDocument } from 'vs/base/browser/dom'; import { coalesce } from 'vs/base/common/arrays'; -import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancelablePromise, createCancelablePromise, DeferredPromise, raceCancellation } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { UriList, VSDataTransfer, createStringDataTransferItem, matchesMimeType } from 'vs/base/common/dataTransfer'; import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import * as platform from 'vs/base/common/platform'; import { generateUuid } from 'vs/base/common/uuid'; @@ -36,6 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { PostEditWidgetManager } from './postEditWidget'; +import { CancellationError, isCancellationError } from 'vs/base/common/errors'; export const changePasteTypeCommandId = 'editor.changePasteType'; @@ -54,6 +55,12 @@ type PasteEditWithProvider = DocumentPasteEdit & { provider: DocumentPasteEditProvider; }; + +interface DocumentPasteWithProviderEditsSession { + edits: readonly PasteEditWithProvider[]; + dispose(): void; +} + type PastePreference = | HierarchicalKind | { providerId: string }; @@ -128,8 +135,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi } private isPasteAsEnabled(): boolean { - return this._editor.getOption(EditorOption.pasteAs).enabled - && !this._editor.getOption(EditorOption.readOnly); + return this._editor.getOption(EditorOption.pasteAs).enabled; } public async finishedPaste(): Promise { @@ -141,12 +147,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } - if (platform.isWeb) { - // Explicitly clear the web resources clipboard. - // This is needed because on web, the browser clipboard is faked out using an in-memory store. - // This means the resources clipboard is not properly updated when copying from the editor. - this._clipboardService.writeResources([]); - } + // Explicitly clear the clipboard internal state. + // This is needed because on web, the browser clipboard is faked out using an in-memory store. + // This means the resources clipboard is not properly updated when copying from the editor. + this._clipboardService.clearInternalState?.(); if (!e.clipboardData || !this.isPasteAsEnabled()) { return; @@ -241,8 +245,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi } if ( - !this.isPasteAsEnabled() - && !this._pasteAsActionContext // Still enable if paste as was explicitly requested + this._editor.getOption(EditorOption.readOnly) // Never enabled if editor is readonly. + || (!this.isPasteAsEnabled() && !this._pasteAsActionContext) // Or feature disabled (but still enable if paste was explicitly requested) ) { return; } @@ -299,17 +303,28 @@ export class CopyPasteController extends Disposable implements IEditorContributi } private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, clipboardEvent: ClipboardEvent): void { - const p = createCancelablePromise(async (token) => { + const editor = this._editor; + if (!editor.hasModel()) { + return; + } + + const editorStateCts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined); + + const p = createCancelablePromise(async (pToken) => { const editor = this._editor; if (!editor.hasModel()) { return; } const model = editor.getModel(); - const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined, token); + const disposables = new DisposableStore(); + const cts = disposables.add(new CancellationTokenSource(pToken)); + disposables.add(editorStateCts.token.onCancellationRequested(() => cts.cancel())); + + const token = cts.token; try { - await this.mergeInDataFromCopy(dataTransfer, metadata, tokenSource.token); - if (tokenSource.token.isCancellationRequested) { + await this.mergeInDataFromCopy(dataTransfer, metadata, token); + if (token.isCancellationRequested) { return; } @@ -317,43 +332,75 @@ export class CopyPasteController extends Disposable implements IEditorContributi if (!supportedProviders.length || (supportedProviders.length === 1 && supportedProviders[0] instanceof DefaultTextPasteOrDropEditProvider) // Only our default text provider is active ) { - return this.applyDefaultPasteHandler(dataTransfer, metadata, tokenSource.token, clipboardEvent); + return this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent); } const context: DocumentPasteContext = { triggerKind: DocumentPasteTriggerKind.Automatic, }; - const providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token); - if (tokenSource.token.isCancellationRequested) { + const editSession = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, token); + disposables.add(editSession); + if (token.isCancellationRequested) { return; } // If the only edit returned is our default text edit, use the default paste handler - if (providerEdits.length === 1 && providerEdits[0].provider instanceof DefaultTextPasteOrDropEditProvider) { - return this.applyDefaultPasteHandler(dataTransfer, metadata, tokenSource.token, clipboardEvent); + if (editSession.edits.length === 1 && editSession.edits[0].provider instanceof DefaultTextPasteOrDropEditProvider) { + return this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent); } - if (providerEdits.length) { + if (editSession.edits.length) { const canShowWidget = editor.getOption(EditorOption.pasteAs).showPasteSelector === 'afterPaste'; - return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: 0, allEdits: providerEdits }, canShowWidget, async (edit, token) => { - const resolved = await edit.provider.resolveDocumentPasteEdit?.(edit, token); - if (resolved) { - edit.additionalEdit = resolved.additionalEdit; - } - return edit; - }, tokenSource.token); + return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: 0, allEdits: editSession.edits }, canShowWidget, (edit, token) => { + return new Promise((resolve, reject) => { + (async () => { + try { + const resolveP = edit.provider.resolveDocumentPasteEdit?.(edit, token); + const showP = new DeferredPromise(); + const resolved = resolveP && await this._pasteProgressManager.showWhile(selections[0].getEndPosition(), localize('resolveProcess', "Resolving paste edit. Click to cancel"), Promise.race([showP.p, resolveP]), { + cancel: () => { + showP.cancel(); + return reject(new CancellationError()); + } + }, 0); + if (resolved) { + edit.additionalEdit = resolved.additionalEdit; + } + return resolve(edit); + } catch (err) { + return reject(err); + } + })(); + }); + }, token); } - await this.applyDefaultPasteHandler(dataTransfer, metadata, tokenSource.token, clipboardEvent); + await this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent); } finally { - tokenSource.dispose(); + disposables.dispose(); if (this._currentPasteOperation === p) { this._currentPasteOperation = undefined; } } }); - this._pasteProgressManager.showWhile(selections[0].getEndPosition(), localize('pasteIntoEditorProgress', "Running paste handlers. Click to cancel"), p); + this._pasteProgressManager.showWhile(selections[0].getEndPosition(), localize('pasteIntoEditorProgress', "Running paste handlers. Click to cancel and do basic paste"), p, { + cancel: async () => { + try { + p.cancel(); + + if (editorStateCts.token.isCancellationRequested) { + return; + } + + await this.applyDefaultPasteHandler(dataTransfer, metadata, editorStateCts.token, clipboardEvent); + } finally { + editorStateCts.dispose(); + } + } + }).then(() => { + editorStateCts.dispose(); + }); this._currentPasteOperation = p; } @@ -365,7 +412,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi } const model = editor.getModel(); - const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined, token); + const disposables = new DisposableStore(); + const tokenSource = disposables.add(new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined, token)); try { await this.mergeInDataFromCopy(dataTransfer, metadata, tokenSource.token); if (tokenSource.token.isCancellationRequested) { @@ -383,23 +431,26 @@ export class CopyPasteController extends Disposable implements IEditorContributi triggerKind: DocumentPasteTriggerKind.PasteAs, only: preference && preference instanceof HierarchicalKind ? preference : undefined, }; - let providerEdits = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token); + let editSession = disposables.add(await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token)); if (tokenSource.token.isCancellationRequested) { return; } // Filter out any edits that don't match the requested kind if (preference) { - providerEdits = providerEdits.filter(edit => { - if (preference instanceof HierarchicalKind) { - return preference.contains(edit.kind); - } else { - return preference.providerId === edit.provider.id; - } - }); + editSession = { + edits: editSession.edits.filter(edit => { + if (preference instanceof HierarchicalKind) { + return preference.contains(edit.kind); + } else { + return preference.providerId === edit.provider.id; + } + }), + dispose: editSession.dispose + }; } - if (!providerEdits.length) { + if (!editSession.edits.length) { if (context.only) { this.showPasteAsNoEditMessage(selections, context.only); } @@ -408,10 +459,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi let pickedEdit: DocumentPasteEdit | undefined; if (preference) { - pickedEdit = providerEdits.at(0); + pickedEdit = editSession.edits.at(0); } else { const selected = await this._quickInputService.pick( - providerEdits.map((edit): IQuickPickItem & { edit: DocumentPasteEdit } => ({ + editSession.edits.map((edit): IQuickPickItem & { edit: DocumentPasteEdit } => ({ label: edit.title, description: edit.kind?.value, edit, @@ -428,7 +479,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi const combinedWorkspaceEdit = createCombinedWorkspaceEdit(model.uri, selections, pickedEdit); await this._bulkEditService.apply(combinedWorkspaceEdit, { editor: this._editor }); } finally { - tokenSource.dispose(); + disposables.dispose(); if (this._currentPasteOperation === p) { this._currentPasteOperation = undefined; } @@ -499,23 +550,32 @@ export class CopyPasteController extends Disposable implements IEditorContributi } } - private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], context: DocumentPasteContext, token: CancellationToken): Promise { + private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], context: DocumentPasteContext, token: CancellationToken): Promise { + const disposables = new DisposableStore(); + const results = await raceCancellation( Promise.all(providers.map(async provider => { try { const edits = await provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, context, token); - // TODO: dispose of edits + if (edits) { + disposables.add(edits); + } return edits?.edits?.map(edit => ({ ...edit, provider })); } catch (err) { - console.error(err); + if (!isCancellationError(err)) { + console.error(err); + } + return undefined; } - return undefined; })), token); const edits = coalesce(results ?? []).flat().filter(edit => { return !context.only || context.only.contains(edit.kind); }); - return sortEditsByYieldTo(edits); + return { + edits: sortEditsByYieldTo(edits), + dispose: () => disposables.dispose() + }; } private async applyDefaultPasteHandler(dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken, clipboardEvent: ClipboardEvent) { diff --git a/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts b/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts index 00671361..e44d9341 100644 --- a/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts +++ b/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts @@ -14,7 +14,7 @@ import { relativePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; -import { DocumentDropEdit, DocumentDropEditProvider, DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider, DocumentPasteEditsSession, DocumentPasteTriggerKind } from 'vs/editor/common/languages'; +import { DocumentDropEditProvider, DocumentDropEditsSession, DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider, DocumentPasteEditsSession, DocumentPasteTriggerKind } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { localize } from 'vs/nls'; @@ -34,14 +34,20 @@ abstract class SimplePasteAndDropProvider implements DocumentDropEditProvider, D } return { + edits: [{ insertText: edit.insertText, title: edit.title, kind: edit.kind, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo }], dispose() { }, - edits: [{ insertText: edit.insertText, title: edit.title, kind: edit.kind, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo }] }; } - async provideDocumentDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { + async provideDocumentDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { const edit = await this.getEdit(dataTransfer, token); - return edit ? [{ insertText: edit.insertText, title: edit.title, kind: edit.kind, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo }] : undefined; + if (!edit) { + return; + } + return { + edits: [{ insertText: edit.insertText, title: edit.title, kind: edit.kind, handledMimeType: edit.handledMimeType, yieldTo: edit.yieldTo }], + dispose() { }, + }; } protected abstract getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise; diff --git a/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts b/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts index 20adc65d..082b7447 100644 --- a/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts +++ b/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts @@ -7,7 +7,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async'; import { VSDataTransfer, matchesMimeType } from 'vs/base/common/dataTransfer'; import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { toExternalVSDataTransfer } from 'vs/editor/browser/dnd'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -84,8 +84,9 @@ export class DropIntoEditorController extends Disposable implements IEditorContr editor.setPosition(position); const p = createCancelablePromise(async (token) => { - const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value, undefined, token); + const disposables = new DisposableStore(); + const tokenSource = disposables.add(new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value, undefined, token)); try { const ourDataTransfer = await this.extractDataTransferData(dragEvent); if (ourDataTransfer.size === 0 || tokenSource.token.isCancellationRequested) { @@ -107,34 +108,39 @@ export class DropIntoEditorController extends Disposable implements IEditorContr return provider.dropMimeTypes.some(mime => ourDataTransfer.matches(mime)); }); - const edits = await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource); + const editSession = disposables.add(await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource)); if (tokenSource.token.isCancellationRequested) { return; } - if (edits.length) { - const activeEditIndex = this.getInitialActiveEditIndex(model, edits); + if (editSession.edits.length) { + const activeEditIndex = this.getInitialActiveEditIndex(model, editSession.edits); const canShowWidget = editor.getOption(EditorOption.dropIntoEditor).showDropSelector === 'afterDrop'; // Pass in the parent token here as it tracks cancelling the entire drop operation - await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex, allEdits: edits }, canShowWidget, async edit => edit, token); + await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex, allEdits: editSession.edits }, canShowWidget, async edit => edit, token); } } finally { - tokenSource.dispose(); + disposables.dispose(); if (this._currentOperation === p) { this._currentOperation = undefined; } } }); - this._dropProgressManager.showWhile(position, localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel"), p); + this._dropProgressManager.showWhile(position, localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel"), p, { cancel: () => p.cancel() }); this._currentOperation = p; } private async getDropEdits(providers: readonly DocumentDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) { + const disposables = new DisposableStore(); + const results = await raceCancellation(Promise.all(providers.map(async provider => { try { const edits = await provider.provideDocumentDropEdits(model, position, dataTransfer, tokenSource.token); - return edits?.map(edit => ({ ...edit, providerId: provider.id })); + if (edits) { + disposables.add(edits); + } + return edits?.edits.map(edit => ({ ...edit, providerId: provider.id })); } catch (err) { console.error(err); } @@ -142,7 +148,10 @@ export class DropIntoEditorController extends Disposable implements IEditorContr })), tokenSource.token); const edits = coalesce(results ?? []).flat(); - return sortEditsByYieldTo(edits); + return { + edits: sortEditsByYieldTo(edits), + dispose: () => disposables.dispose() + }; } private getInitialActiveEditIndex(model: ITextModel, edits: ReadonlyArray) { diff --git a/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts b/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts index ee0b8e56..ec61f04a 100644 --- a/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.ts +++ b/patched-vscode/src/vs/editor/contrib/dropOrPasteInto/test/browser/editSort.test.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 * as assert from 'assert'; +import assert from 'assert'; import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DocumentDropEdit } from 'vs/editor/common/languages'; diff --git a/patched-vscode/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts b/patched-vscode/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts index cc2a8d0f..239da13d 100644 --- a/patched-vscode/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts +++ b/patched-vscode/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/patched-vscode/src/vs/editor/contrib/find/browser/findController.ts b/patched-vscode/src/vs/editor/contrib/find/browser/findController.ts index bbcc79d0..a11d534d 100644 --- a/patched-vscode/src/vs/editor/contrib/find/browser/findController.ts +++ b/patched-vscode/src/vs/editor/contrib/find/browser/findController.ts @@ -5,7 +5,7 @@ import { Delayer } from 'vs/base/common/async'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, EditorContributionInstantiation, MultiEditorAction, registerEditorAction, registerEditorCommand, registerEditorContribution, registerMultiEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -771,7 +771,8 @@ export class MoveToMatchFindAction extends EditorAction { } const quickInputService = accessor.get(IQuickInputService); - const inputBox = quickInputService.createInputBox(); + const disposables = new DisposableStore(); + const inputBox = disposables.add(quickInputService.createInputBox()); inputBox.placeholder = nls.localize('findMatchAction.inputPlaceHolder', "Type a number to go to a specific match (between 1 and {0})", matchesCount); const toFindMatchIndex = (value: string): number | undefined => { @@ -805,11 +806,11 @@ export class MoveToMatchFindAction extends EditorAction { this.clearDecorations(editor); } }; - inputBox.onDidChangeValue(value => { + disposables.add(inputBox.onDidChangeValue(value => { updatePickerAndEditor(value); - }); + })); - inputBox.onDidAccept(() => { + disposables.add(inputBox.onDidAccept(() => { const index = toFindMatchIndex(inputBox.value); if (typeof index === 'number') { controller.goToMatch(index); @@ -817,12 +818,12 @@ export class MoveToMatchFindAction extends EditorAction { } else { inputBox.validationMessage = nls.localize('findMatchAction.inputValidationMessage', "Please type a number between 1 and {0}", controller.getState().matchesCount); } - }); + })); - inputBox.onDidHide(() => { + disposables.add(inputBox.onDidHide(() => { this.clearDecorations(editor); - inputBox.dispose(); - }); + disposables.dispose(); + })); inputBox.show(); } diff --git a/patched-vscode/src/vs/editor/contrib/find/browser/findModel.ts b/patched-vscode/src/vs/editor/contrib/find/browser/findModel.ts index a4611d37..a6958ce4 100644 --- a/patched-vscode/src/vs/editor/contrib/find/browser/findModel.ts +++ b/patched-vscode/src/vs/editor/contrib/find/browser/findModel.ts @@ -97,7 +97,12 @@ export class FindModelBoundToEditorModel { this._decorations = new FindDecorations(editor); this._toDispose.add(this._decorations); - this._updateDecorationsScheduler = new RunOnceScheduler(() => this.research(false), 100); + this._updateDecorationsScheduler = new RunOnceScheduler(() => { + if (!this._editor.hasModel()) { + return; + } + return this.research(false); + }, 100); this._toDispose.add(this._updateDecorationsScheduler); this._toDispose.add(this._editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { diff --git a/patched-vscode/src/vs/editor/contrib/find/browser/findWidget.ts b/patched-vscode/src/vs/editor/contrib/find/browser/findWidget.ts index 303a9a58..9645d4b5 100644 --- a/patched-vscode/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/find/browser/findWidget.ts @@ -410,9 +410,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } // remove previous content - if (this._matchesCount.firstChild) { - this._matchesCount.removeChild(this._matchesCount.firstChild); - } + this._matchesCount.firstChild?.remove(); let label: string; if (this._state.matchesCount > 0) { @@ -1345,7 +1343,7 @@ export class SimpleButton extends Widget { this._domNode.className = className; this._domNode.setAttribute('role', 'button'); this._domNode.setAttribute('aria-label', this._opts.label); - this._register(hoverService.setupUpdatableHover(opts.hoverDelegate ?? getDefaultHoverDelegate('element'), this._domNode, this._opts.label)); + this._register(hoverService.setupManagedHover(opts.hoverDelegate ?? getDefaultHoverDelegate('element'), this._domNode, this._opts.label)); this.onclick(this._domNode, (e) => { this._opts.onTrigger(); diff --git a/patched-vscode/src/vs/editor/contrib/find/test/browser/find.test.ts b/patched-vscode/src/vs/editor/contrib/find/test/browser/find.test.ts index 466c39ba..580ea739 100644 --- a/patched-vscode/src/vs/editor/contrib/find/test/browser/find.test.ts +++ b/patched-vscode/src/vs/editor/contrib/find/test/browser/find.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/contrib/find/test/browser/findController.test.ts b/patched-vscode/src/vs/editor/contrib/find/test/browser/findController.test.ts index 9bdb1cb7..49fef95b 100644 --- a/patched-vscode/src/vs/editor/contrib/find/test/browser/findController.test.ts +++ b/patched-vscode/src/vs/editor/contrib/find/test/browser/findController.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Delayer } from 'vs/base/common/async'; import * as platform from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/contrib/find/test/browser/findModel.test.ts b/patched-vscode/src/vs/editor/contrib/find/test/browser/findModel.test.ts index 7d1ae5f5..8cc75338 100644 --- a/patched-vscode/src/vs/editor/contrib/find/test/browser/findModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/find/test/browser/findModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreNavigationCommands } from 'vs/editor/browser/coreCommands'; diff --git a/patched-vscode/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts b/patched-vscode/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts index cc9e76c9..1f534bbd 100644 --- a/patched-vscode/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts +++ b/patched-vscode/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { buildReplaceStringWithCasePreserved } from 'vs/base/common/search'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { parseReplaceString, ReplacePattern, ReplacePiece } from 'vs/editor/contrib/find/browser/replacePattern'; diff --git a/patched-vscode/src/vs/editor/contrib/folding/browser/folding.css b/patched-vscode/src/vs/editor/contrib/folding/browser/folding.css index f973d5f7..5f7ab05d 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/browser/folding.css +++ b/patched-vscode/src/vs/editor/contrib/folding/browser/folding.css @@ -31,7 +31,7 @@ } .monaco-editor .inline-folded:after { - color: grey; + color: var(--vscode-editor-foldPlaceholderForeground); margin: 0.1em 0.2em 0 0.2em; content: "\22EF"; /* ellipses unicode character */ display: inline; @@ -49,4 +49,3 @@ .monaco-editor .cldr.codicon.codicon-folding-manual-collapsed { color: var(--vscode-editorGutter-foldingControlForeground) !important; } - diff --git a/patched-vscode/src/vs/editor/contrib/folding/browser/folding.ts b/patched-vscode/src/vs/editor/contrib/folding/browser/folding.ts index 993c8cf0..b713765e 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/browser/folding.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/browser/folding.ts @@ -17,6 +17,7 @@ import { EditorAction, EditorContributionInstantiation, registerEditorAction, re import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; @@ -328,8 +329,7 @@ export class FoldingController extends Disposable implements IEditorContribution // some cursors might have moved into hidden regions, make sure they are in expanded regions const selections = this.editor.getSelections(); - const selectionLineNumbers = selections ? selections.map(s => s.startLineNumber) : []; - foldingModel.update(foldingRanges, selectionLineNumbers); + foldingModel.update(foldingRanges, toSelectedLines(selections)); scrollState?.restore(this.editor); @@ -582,6 +582,29 @@ abstract class FoldingAction extends EditorAction { } } +export interface SelectedLines { + startsInside(startLine: number, endLine: number): boolean; +} + +export function toSelectedLines(selections: Selection[] | null): SelectedLines { + if (!selections || selections.length === 0) { + return { + startsInside: () => false + }; + } + return { + startsInside(startLine: number, endLine: number): boolean { + for (const s of selections) { + const line = s.startLineNumber; + if (line >= startLine && line <= endLine) { + return true; + } + } + return false; + } + }; +} + interface FoldingArguments { levels?: number; direction?: 'up' | 'down'; @@ -809,6 +832,30 @@ class FoldRecursivelyAction extends FoldingAction { } } + +class ToggleFoldRecursivelyAction extends FoldingAction { + + constructor() { + super({ + id: 'editor.toggleFoldRecursively', + label: nls.localize('toggleFoldRecursivelyAction.label', "Toggle Fold Recursively"), + alias: 'Toggle Fold Recursively', + precondition: CONTEXT_FOLDING_ENABLED, + kbOpts: { + kbExpr: EditorContextKeys.editorTextFocus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL), + weight: KeybindingWeight.EditorContrib + } + }); + } + + invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void { + const selectedLines = this.getSelectedLines(editor); + toggleCollapseState(foldingModel, Number.MAX_VALUE, selectedLines); + } +} + + class FoldAllBlockCommentsAction extends FoldingAction { constructor() { @@ -1189,6 +1236,7 @@ registerEditorAction(UnfoldAction); registerEditorAction(UnFoldRecursivelyAction); registerEditorAction(FoldAction); registerEditorAction(FoldRecursivelyAction); +registerEditorAction(ToggleFoldRecursivelyAction); registerEditorAction(FoldAllAction); registerEditorAction(UnfoldAllAction); registerEditorAction(FoldAllBlockCommentsAction); diff --git a/patched-vscode/src/vs/editor/contrib/folding/browser/foldingDecorations.ts b/patched-vscode/src/vs/editor/contrib/folding/browser/foldingDecorations.ts index 03a9b4a4..2350f9aa 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/browser/foldingDecorations.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/browser/foldingDecorations.ts @@ -15,7 +15,8 @@ import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; const foldBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hcDark: null, hcLight: null }, localize('foldBackgroundBackground', "Background color behind folded ranges. The color must not be opaque so as not to hide underlying decorations."), true); -registerColor('editorGutter.foldingControlForeground', { dark: iconForeground, light: iconForeground, hcDark: iconForeground, hcLight: iconForeground }, localize('editorGutter.foldingControlForeground', 'Color of the folding control in the editor gutter.')); +registerColor('editor.foldPlaceholderForeground', { light: '#808080', dark: '#808080', hcDark: null, hcLight: null }, localize('collapsedTextColor', "Color of the collapsed text after the first line of a folded range.")); +registerColor('editorGutter.foldingControlForeground', iconForeground, localize('editorGutter.foldingControlForeground', 'Color of the folding control in the editor gutter.')); export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown, localize('foldingExpandedIcon', 'Icon for expanded ranges in the editor glyph margin.')); export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight, localize('foldingCollapsedIcon', 'Icon for collapsed ranges in the editor glyph margin.')); diff --git a/patched-vscode/src/vs/editor/contrib/folding/browser/foldingModel.ts b/patched-vscode/src/vs/editor/contrib/folding/browser/foldingModel.ts index b031a05a..7b1c6253 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/browser/foldingModel.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/browser/foldingModel.ts @@ -7,6 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { FoldingRegion, FoldingRegions, ILineRange, FoldRange, FoldSource } from './foldingRanges'; import { hash } from 'vs/base/common/hash'; +import { SelectedLines } from 'vs/editor/contrib/folding/browser/folding'; export interface IDecorationProvider { getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManual: boolean): IModelDecorationOptions; @@ -111,9 +112,9 @@ export class FoldingModel { this.updatePost(FoldingRegions.fromFoldRanges(newFoldingRanges)); } - public update(newRegions: FoldingRegions, blockedLineNumers: number[] = []): void { - const foldedOrManualRanges = this._currentFoldedOrManualRanges(blockedLineNumers); - const newRanges = FoldingRegions.sanitizeAndMerge(newRegions, foldedOrManualRanges, this._textModel.getLineCount()); + public update(newRegions: FoldingRegions, selection?: SelectedLines): void { + const foldedOrManualRanges = this._currentFoldedOrManualRanges(selection); + const newRanges = FoldingRegions.sanitizeAndMerge(newRegions, foldedOrManualRanges, this._textModel.getLineCount(), selection); this.updatePost(FoldingRegions.fromFoldRanges(newRanges)); } @@ -141,17 +142,7 @@ export class FoldingModel { this._updateEventEmitter.fire({ model: this }); } - private _currentFoldedOrManualRanges(blockedLineNumers: number[] = []): FoldRange[] { - - const isBlocked = (startLineNumber: number, endLineNumber: number) => { - for (const blockedLineNumber of blockedLineNumers) { - if (startLineNumber < blockedLineNumber && blockedLineNumber <= endLineNumber) { // first line is visible - return true; - } - } - return false; - }; - + private _currentFoldedOrManualRanges(selection?: SelectedLines): FoldRange[] { const foldedRanges: FoldRange[] = []; for (let i = 0, limit = this._regions.length; i < limit; i++) { let isCollapsed = this.regions.isCollapsed(i); @@ -160,7 +151,7 @@ export class FoldingModel { const foldRange = this._regions.toFoldRange(i); const decRange = this._textModel.getDecorationRange(this._editorDecorationIds[i]); if (decRange) { - if (isCollapsed && isBlocked(decRange.startLineNumber, decRange.endLineNumber)) { + if (isCollapsed && selection?.startsInside(decRange.startLineNumber + 1, decRange.endLineNumber)) { isCollapsed = false; // uncollapse is the range is blocked } foldedRanges.push({ diff --git a/patched-vscode/src/vs/editor/contrib/folding/browser/foldingRanges.ts b/patched-vscode/src/vs/editor/contrib/folding/browser/foldingRanges.ts index ee23abbf..1220d014 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/browser/foldingRanges.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/browser/foldingRanges.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { SelectedLines } from 'vs/editor/contrib/folding/browser/folding'; + export interface ILineRange { startLineNumber: number; endLineNumber: number; @@ -299,7 +301,10 @@ export class FoldingRegions { public static sanitizeAndMerge( rangesA: FoldingRegions | FoldRange[], rangesB: FoldingRegions | FoldRange[], - maxLineNumber: number | undefined): FoldRange[] { + maxLineNumber: number | undefined, + selection?: SelectedLines + ): FoldRange[] { + maxLineNumber = maxLineNumber ?? Number.MAX_VALUE; const getIndexedFunction = (r: FoldingRegions | FoldRange[], limit: number) => { @@ -330,7 +335,8 @@ export class FoldingRegions { } else { // a previously folded range or a (possibly unfolded) recovered range useRange = nextA; - useRange.isCollapsed = nextB.isCollapsed && nextA.endLineNumber === nextB.endLineNumber; + // stays collapsed if the range still has the same number of lines or the selection is not in the range or after it + useRange.isCollapsed = nextB.isCollapsed && (nextA.endLineNumber === nextB.endLineNumber || !selection?.startsInside(nextA.startLineNumber + 1, nextA.endLineNumber + 1)); useRange.source = FoldSource.provider; } nextA = getA(++indexA); // not necessary, just for speed diff --git a/patched-vscode/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts b/patched-vscode/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts index 3cef0c2a..f33f67b3 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts @@ -2,14 +2,16 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { IModelDecorationsChangeAccessor, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { toSelectedLines } from 'vs/editor/contrib/folding/browser/folding'; import { FoldingModel, getNextFoldLine, getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp } from 'vs/editor/contrib/folding/browser/foldingModel'; import { FoldingRegion } from 'vs/editor/contrib/folding/browser/foldingRanges'; import { computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider'; @@ -288,7 +290,7 @@ suite('Folding Model', () => { textModel.applyEdits([EditOperation.delete(new Range(6, 11, 9, 0))]); - foldingModel.update(computeRanges(textModel, false, undefined)); + foldingModel.update(computeRanges(textModel, true, undefined), toSelectedLines([new Selection(7, 1, 7, 1)])); assertRanges(foldingModel, [r(1, 9, false), r(2, 8, false), r(3, 5, false), r(6, 8, false)]); } finally { diff --git a/patched-vscode/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts b/patched-vscode/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts index 912cf515..a1a6dbe1 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { FoldingMarkers } from 'vs/editor/common/languages/languageConfiguration'; import { MAX_FOLDING_REGIONS, FoldRange, FoldingRegions, FoldSource } from 'vs/editor/contrib/folding/browser/foldingRanges'; diff --git a/patched-vscode/src/vs/editor/contrib/folding/test/browser/hiddenRangeModel.test.ts b/patched-vscode/src/vs/editor/contrib/folding/test/browser/hiddenRangeModel.test.ts index 71281efa..6d57dec4 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/test/browser/hiddenRangeModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/test/browser/hiddenRangeModel.test.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 * as assert from 'assert'; +import assert from 'assert'; import { IRange } from 'vs/editor/common/core/range'; import { FoldingModel } from 'vs/editor/contrib/folding/browser/foldingModel'; import { HiddenRangeModel } from 'vs/editor/contrib/folding/browser/hiddenRangeModel'; diff --git a/patched-vscode/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts b/patched-vscode/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts index afee83df..25db0e6d 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/test/browser/indentFold.test.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/test/browser/indentFold.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; diff --git a/patched-vscode/src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts b/patched-vscode/src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts index 0a1c3220..837dd6c0 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/test/browser/indentRangeProvider.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { FoldingMarkers } from 'vs/editor/common/languages/languageConfiguration'; import { computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider'; diff --git a/patched-vscode/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts b/patched-vscode/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts index 3551f446..3b2bdb06 100644 --- a/patched-vscode/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.ts +++ b/patched-vscode/src/vs/editor/contrib/folding/test/browser/syntaxFold.test.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 * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModel } from 'vs/editor/common/model'; import { FoldingContext, FoldingRange, FoldingRangeProvider, ProviderResult } from 'vs/editor/common/languages'; diff --git a/patched-vscode/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts b/patched-vscode/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts index 24ffac21..cf0e5c7d 100644 --- a/patched-vscode/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts @@ -308,10 +308,9 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._disposables.add(this._actionbarWidget!.actionRunner.onWillRun(e => this.editor.focus())); const actions: IAction[] = []; - const menu = this._menuService.createMenu(MarkerNavigationWidget.TitleMenu, this._contextKeyService); - createAndFillInActionBarActions(menu, undefined, actions); + const menu = this._menuService.getMenuActions(MarkerNavigationWidget.TitleMenu, this._contextKeyService); + createAndFillInActionBarActions(menu, actions); this._actionbarWidget!.push(actions, { label: false, icon: true, index: 0 }); - menu.dispose(); } protected override _fillTitleIcon(container: HTMLElement): void { @@ -410,4 +409,4 @@ const editorMarkerNavigationWarningHeader = registerColor('editorMarkerNavigatio const editorMarkerNavigationInfo = registerColor('editorMarkerNavigationInfo.background', { dark: infoDefault, light: infoDefault, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('editorMarkerNavigationInfo', 'Editor marker navigation widget info color.')); const editorMarkerNavigationInfoHeader = registerColor('editorMarkerNavigationInfo.headerBackground', { dark: transparent(editorMarkerNavigationInfo, .1), light: transparent(editorMarkerNavigationInfo, .1), hcDark: null, hcLight: null }, nls.localize('editorMarkerNavigationInfoHeaderBackground', 'Editor marker navigation widget info heading background.')); -const editorMarkerNavigationBackground = registerColor('editorMarkerNavigation.background', { dark: editorBackground, light: editorBackground, hcDark: editorBackground, hcLight: editorBackground }, nls.localize('editorMarkerNavigationBackground', 'Editor marker navigation widget background.')); +const editorMarkerNavigationBackground = registerColor('editorMarkerNavigation.background', editorBackground, nls.localize('editorMarkerNavigationBackground', 'Editor marker navigation widget background.')); diff --git a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index 76b08cc6..63541f54 100644 --- a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -253,7 +253,7 @@ export abstract class SymbolNavigationAction extends EditorAction2 { export class DefinitionAction extends SymbolNavigationAction { protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, token), nls.localize('def.title', 'Definitions')); + return new ReferencesModel(await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, false, token), nls.localize('def.title', 'Definitions')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -380,7 +380,7 @@ registerAction2(class PeekDefinitionAction extends DefinitionAction { class DeclarationAction extends SymbolNavigationAction { protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, token), nls.localize('decl.title', 'Declarations')); + return new ReferencesModel(await getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, false, token), nls.localize('decl.title', 'Declarations')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -467,7 +467,7 @@ registerAction2(class PeekDeclarationAction extends DeclarationAction { class TypeDefinitionAction extends SymbolNavigationAction { protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, token), nls.localize('typedef.title', 'Type Definitions')); + return new ReferencesModel(await getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, false, token), nls.localize('typedef.title', 'Type Definitions')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -553,7 +553,7 @@ registerAction2(class PeekTypeDefinitionAction extends TypeDefinitionAction { class ImplementationAction extends SymbolNavigationAction { protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, token), nls.localize('impl.title', 'Implementations')); + return new ReferencesModel(await getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, false, token), nls.localize('impl.title', 'Implementations')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -695,7 +695,7 @@ registerAction2(class GoToReferencesAction extends ReferencesAction { } protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, true, token), nls.localize('ref.title', 'References')); + return new ReferencesModel(await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, true, false, token), nls.localize('ref.title', 'References')); } }); @@ -723,7 +723,7 @@ registerAction2(class PeekReferencesAction extends ReferencesAction { } protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, token), nls.localize('ref.title', 'References')); + return new ReferencesModel(await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, false, token), nls.localize('ref.title', 'References')); } }); @@ -846,7 +846,7 @@ CommandsRegistry.registerCommand({ return undefined; } - const references = createCancelablePromise(token => getReferencesAtPosition(languageFeaturesService.referenceProvider, control.getModel(), corePosition.Position.lift(position), false, token).then(references => new ReferencesModel(references, nls.localize('ref.title', 'References')))); + const references = createCancelablePromise(token => getReferencesAtPosition(languageFeaturesService.referenceProvider, control.getModel(), corePosition.Position.lift(position), false, false, token).then(references => new ReferencesModel(references, nls.localize('ref.title', 'References')))); const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); return Promise.resolve(controller.toggleWidget(range, references, false)); }); diff --git a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/goToSymbol.ts b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/goToSymbol.ts index 738af806..1477a394 100644 --- a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/goToSymbol.ts +++ b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/goToSymbol.ts @@ -22,7 +22,7 @@ function shouldIncludeLocationLink(sourceModel: ITextModel, loc: LocationLink): } // Otherwise filter out locations from internal schemes - if (matchesSomeScheme(loc.uri, Schemas.walkThroughSnippet, Schemas.vscodeChatCodeBlock, Schemas.vscodeChatCodeCompareBlock, Schemas.vscodeCopilotBackingChatCodeBlock)) { + if (matchesSomeScheme(loc.uri, Schemas.walkThroughSnippet, Schemas.vscodeChatCodeBlock, Schemas.vscodeChatCodeCompareBlock)) { return false; } @@ -33,9 +33,10 @@ async function getLocationLinks( model: ITextModel, position: Position, registry: LanguageFeatureRegistry, + recursive: boolean, provide: (provider: T, model: ITextModel, position: Position) => ProviderResult ): Promise { - const provider = registry.ordered(model); + const provider = registry.ordered(model, recursive); // get results const promises = provider.map((provider): Promise => { @@ -49,32 +50,32 @@ async function getLocationLinks( return coalesce(values.flat()).filter(loc => shouldIncludeLocationLink(model, loc)); } -export function getDefinitionsAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, token: CancellationToken): Promise { - return getLocationLinks(model, position, registry, (provider, model, position) => { +export function getDefinitionsAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, recursive: boolean, token: CancellationToken): Promise { + return getLocationLinks(model, position, registry, recursive, (provider, model, position) => { return provider.provideDefinition(model, position, token); }); } -export function getDeclarationsAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, token: CancellationToken): Promise { - return getLocationLinks(model, position, registry, (provider, model, position) => { +export function getDeclarationsAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, recursive: boolean, token: CancellationToken): Promise { + return getLocationLinks(model, position, registry, recursive, (provider, model, position) => { return provider.provideDeclaration(model, position, token); }); } -export function getImplementationsAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, token: CancellationToken): Promise { - return getLocationLinks(model, position, registry, (provider, model, position) => { +export function getImplementationsAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, recursive: boolean, token: CancellationToken): Promise { + return getLocationLinks(model, position, registry, recursive, (provider, model, position) => { return provider.provideImplementation(model, position, token); }); } -export function getTypeDefinitionsAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, token: CancellationToken): Promise { - return getLocationLinks(model, position, registry, (provider, model, position) => { +export function getTypeDefinitionsAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, recursive: boolean, token: CancellationToken): Promise { + return getLocationLinks(model, position, registry, recursive, (provider, model, position) => { return provider.provideTypeDefinition(model, position, token); }); } -export function getReferencesAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, compact: boolean, token: CancellationToken): Promise { - return getLocationLinks(model, position, registry, async (provider, model, position) => { +export function getReferencesAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, compact: boolean, recursive: boolean, token: CancellationToken): Promise { + return getLocationLinks(model, position, registry, recursive, async (provider, model, position) => { const result = (await provider.provideReferences(model, position, { includeDeclaration: true }, token))?.filter(ref => shouldIncludeLocationLink(model, ref)); if (!compact || !result || result.length !== 2) { return result; @@ -99,30 +100,59 @@ async function _sortedAndDeduped(callback: () => Promise): Promi registerModelAndPositionCommand('_executeDefinitionProvider', (accessor, model, position) => { const languageFeaturesService = accessor.get(ILanguageFeaturesService); - const promise = getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, CancellationToken.None); + const promise = getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, false, CancellationToken.None); + return _sortedAndDeduped(() => promise); +}); + +registerModelAndPositionCommand('_executeDefinitionProvider_recursive', (accessor, model, position) => { + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + const promise = getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, true, CancellationToken.None); return _sortedAndDeduped(() => promise); }); registerModelAndPositionCommand('_executeTypeDefinitionProvider', (accessor, model, position) => { const languageFeaturesService = accessor.get(ILanguageFeaturesService); - const promise = getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, CancellationToken.None); + const promise = getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, false, CancellationToken.None); + return _sortedAndDeduped(() => promise); +}); + +registerModelAndPositionCommand('_executeTypeDefinitionProvider_recursive', (accessor, model, position) => { + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + const promise = getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, true, CancellationToken.None); return _sortedAndDeduped(() => promise); }); registerModelAndPositionCommand('_executeDeclarationProvider', (accessor, model, position) => { const languageFeaturesService = accessor.get(ILanguageFeaturesService); - const promise = getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, CancellationToken.None); + const promise = getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, false, CancellationToken.None); + return _sortedAndDeduped(() => promise); +}); +registerModelAndPositionCommand('_executeDeclarationProvider_recursive', (accessor, model, position) => { + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + const promise = getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, true, CancellationToken.None); return _sortedAndDeduped(() => promise); }); registerModelAndPositionCommand('_executeReferenceProvider', (accessor, model, position) => { const languageFeaturesService = accessor.get(ILanguageFeaturesService); - const promise = getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, CancellationToken.None); + const promise = getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, false, CancellationToken.None); + return _sortedAndDeduped(() => promise); +}); + +registerModelAndPositionCommand('_executeReferenceProvider_recursive', (accessor, model, position) => { + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + const promise = getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, true, CancellationToken.None); return _sortedAndDeduped(() => promise); }); registerModelAndPositionCommand('_executeImplementationProvider', (accessor, model, position) => { const languageFeaturesService = accessor.get(ILanguageFeaturesService); - const promise = getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, CancellationToken.None); + const promise = getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, false, CancellationToken.None); + return _sortedAndDeduped(() => promise); +}); + +registerModelAndPositionCommand('_executeImplementationProvider_recursive', (accessor, model, position) => { + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + const promise = getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, true, CancellationToken.None); return _sortedAndDeduped(() => promise); }); diff --git a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts index f5a08f94..097de76f 100644 --- a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts +++ b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.ts @@ -297,7 +297,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri return Promise.resolve(null); } - return getDefinitionsAtPosition(this.languageFeaturesService.definitionProvider, model, position, token); + return getDefinitionsAtPosition(this.languageFeaturesService.definitionProvider, model, position, false, token); } private gotoDefinition(position: Position, openToSide: boolean): Promise { diff --git a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts index 40d344f3..e7691080 100644 --- a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts +++ b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts @@ -105,9 +105,14 @@ export abstract class ReferencesController implements IEditorContribution { modelPromise.cancel(); if (this._widget) { this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.PROFILE, StorageTarget.MACHINE); + if (!this._widget.isClosing) { + // to prevent calling this too many times, check whether it was already closing. + this.closeWidget(); + } this._widget = undefined; + } else { + this.closeWidget(); } - this.closeWidget(); })); this._disposables.add(this._widget.onDidSelectReference(event => { diff --git a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts index b80e75d4..977e68ec 100644 --- a/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts @@ -23,9 +23,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel'; import { Location } from 'vs/editor/common/languages'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { AccessibilityProvider, DataSource, Delegate, FileReferencesRenderer, IdentityProvider, OneReferenceRenderer, StringRepresentationProvider, TreeElement } from 'vs/editor/contrib/gotoSymbol/browser/peek/referencesTree'; import * as peekView from 'vs/editor/contrib/peekView/browser/peekView'; @@ -35,7 +33,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkbenchAsyncDataTreeOptions, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { FileReferences, OneReference, ReferencesModel } from '../referencesModel'; class DecorationsManager implements IDisposable { @@ -214,6 +211,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { private _previewContainer!: HTMLElement; private _messageContainer!: HTMLElement; private _dim = new dom.Dimension(0, 0); + private _isClosing = false; // whether or not a dispose is already in progress constructor( editor: ICodeEditor, @@ -224,10 +222,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { @IInstantiationService private readonly _instantiationService: IInstantiationService, @peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService, @ILabelService private readonly _uriLabel: ILabelService, - @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @ILanguageService private readonly _languageService: ILanguageService, - @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, ) { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true, supportOnTitleClick: true }, _instantiationService); @@ -237,7 +232,12 @@ export class ReferenceWidget extends peekView.PeekViewWidget { this.create(); } + get isClosing() { + return this._isClosing; + } + override dispose(): void { + this._isClosing = true; this.setModel(undefined); this._callOnDispose.dispose(); this._disposeOnNewModel.dispose(); @@ -315,7 +315,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { }; this._preview = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._previewContainer, options, {}, this.editor); dom.hide(this._previewContainer); - this._previewNotAvailableMessage = new TextModel(nls.localize('missingPreviewMessage', "no preview available"), PLAINTEXT_LANGUAGE_ID, TextModel.DEFAULT_CREATION_OPTIONS, null, this._undoRedoService, this._languageService, this._languageConfigurationService); + this._previewNotAvailableMessage = this._instantiationService.createInstance(TextModel, nls.localize('missingPreviewMessage', "no preview available"), PLAINTEXT_LANGUAGE_ID, TextModel.DEFAULT_CREATION_OPTIONS, null); // tree this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline')); @@ -390,7 +390,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { this._onDidSelectReference.fire({ element, kind, source: 'tree' }); } }; - this._tree.onDidOpen(e => { + this._disposables.add(this._tree.onDidOpen(e => { if (e.sideBySide) { onEvent(e.element, 'side'); } else if (e.editorOptions.pinned) { @@ -398,7 +398,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { } else { onEvent(e.element, 'show'); } - }); + })); dom.hide(this._treeContainer); } diff --git a/patched-vscode/src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts b/patched-vscode/src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts index d1195449..a547f945 100644 --- a/patched-vscode/src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/gotoSymbol/test/browser/referencesModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverController.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverController.ts deleted file mode 100644 index 81ad7b79..00000000 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverController.ts +++ /dev/null @@ -1,456 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as dom from 'vs/base/browser/dom'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { TokenizationRegistry } from 'vs/editor/common/languages'; -import { HoverOperation, HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; -import { HoverAnchor, HoverParticipantRegistry, HoverRangeAnchor, IEditorHoverColorPickerWidget, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { MarkdownHoverParticipant } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant'; -import { InlayHintsHover } from 'vs/editor/contrib/inlayHints/browser/inlayHintsHover'; -import { HoverVerbosityAction } from 'vs/editor/common/standalone/standaloneEnums'; -import { ContentHoverWidget } from 'vs/editor/contrib/hover/browser/contentHoverWidget'; -import { ContentHoverComputer } from 'vs/editor/contrib/hover/browser/contentHoverComputer'; -import { ContentHoverVisibleData, HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes'; -import { EditorHoverStatusBar } from 'vs/editor/contrib/hover/browser/contentHoverStatusBar'; -import { Emitter } from 'vs/base/common/event'; - -export class ContentHoverController extends Disposable implements IHoverWidget { - - private _currentResult: HoverResult | null = null; - - private readonly _computer: ContentHoverComputer; - private readonly _widget: ContentHoverWidget; - private readonly _participants: IEditorHoverParticipant[]; - // TODO@aiday-mar make array of participants, dispatch between them - private readonly _markdownHoverParticipant: MarkdownHoverParticipant | undefined; - private readonly _hoverOperation: HoverOperation; - - private readonly _onContentsChanged = this._register(new Emitter()); - public readonly onContentsChanged = this._onContentsChanged.event; - - constructor( - private readonly _editor: ICodeEditor, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, - ) { - super(); - - this._widget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor)); - - // Instantiate participants and sort them by `hoverOrdinal` which is relevant for rendering order. - this._participants = []; - for (const participant of HoverParticipantRegistry.getAll()) { - const participantInstance = this._instantiationService.createInstance(participant, this._editor); - if (participantInstance instanceof MarkdownHoverParticipant && !(participantInstance instanceof InlayHintsHover)) { - this._markdownHoverParticipant = participantInstance; - } - this._participants.push(participantInstance); - } - this._participants.sort((p1, p2) => p1.hoverOrdinal - p2.hoverOrdinal); - - this._computer = new ContentHoverComputer(this._editor, this._participants); - this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer)); - - this._register(this._hoverOperation.onResult((result) => { - if (!this._computer.anchor) { - // invalid state, ignore result - return; - } - const messages = (result.hasLoadingMessage ? this._addLoadingMessage(result.value) : result.value); - this._withResult(new HoverResult(this._computer.anchor, messages, result.isComplete)); - })); - this._register(dom.addStandardDisposableListener(this._widget.getDomNode(), 'keydown', (e) => { - if (e.equals(KeyCode.Escape)) { - this.hide(); - } - })); - this._register(TokenizationRegistry.onDidChange(() => { - if (this._widget.position && this._currentResult) { - this._setCurrentResult(this._currentResult); // render again - } - })); - } - - /** - * Returns true if the hover shows now or will show. - */ - private _startShowingOrUpdateHover( - anchor: HoverAnchor | null, - mode: HoverStartMode, - source: HoverStartSource, - focus: boolean, - mouseEvent: IEditorMouseEvent | null - ): boolean { - - if (!this._widget.position || !this._currentResult) { - // The hover is not visible - if (anchor) { - this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); - return true; - } - return false; - } - - // The hover is currently visible - const isHoverSticky = this._editor.getOption(EditorOption.hover).sticky; - const isGettingCloser = ( - isHoverSticky - && mouseEvent - && this._widget.isMouseGettingCloser(mouseEvent.event.posx, mouseEvent.event.posy) - ); - - if (isGettingCloser) { - // The mouse is getting closer to the hover, so we will keep the hover untouched - // But we will kick off a hover update at the new anchor, insisting on keeping the hover visible. - if (anchor) { - this._startHoverOperationIfNecessary(anchor, mode, source, focus, true); - } - return true; - } - - if (!anchor) { - this._setCurrentResult(null); - return false; - } - - if (anchor && this._currentResult.anchor.equals(anchor)) { - // The widget is currently showing results for the exact same anchor, so no update is needed - return true; - } - - if (!anchor.canAdoptVisibleHover(this._currentResult.anchor, this._widget.position)) { - // The new anchor is not compatible with the previous anchor - this._setCurrentResult(null); - this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); - return true; - } - - // We aren't getting any closer to the hover, so we will filter existing results - // and keep those which also apply to the new anchor. - this._setCurrentResult(this._currentResult.filter(anchor)); - this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); - return true; - } - - private _startHoverOperationIfNecessary(anchor: HoverAnchor, mode: HoverStartMode, source: HoverStartSource, focus: boolean, insistOnKeepingHoverVisible: boolean): void { - - if (this._computer.anchor && this._computer.anchor.equals(anchor)) { - // We have to start a hover operation at the exact same anchor as before, so no work is needed - return; - } - this._hoverOperation.cancel(); - this._computer.anchor = anchor; - this._computer.shouldFocus = focus; - this._computer.source = source; - this._computer.insistOnKeepingHoverVisible = insistOnKeepingHoverVisible; - this._hoverOperation.start(mode); - } - - private _setCurrentResult(hoverResult: HoverResult | null): void { - - if (this._currentResult === hoverResult) { - // avoid updating the DOM to avoid resetting the user selection - return; - } - if (hoverResult && hoverResult.messages.length === 0) { - hoverResult = null; - } - this._currentResult = hoverResult; - if (this._currentResult) { - this._renderMessages(this._currentResult.anchor, this._currentResult.messages); - } else { - this._widget.hide(); - } - } - - private _addLoadingMessage(result: IHoverPart[]): IHoverPart[] { - if (this._computer.anchor) { - for (const participant of this._participants) { - if (participant.createLoadingMessage) { - const loadingMessage = participant.createLoadingMessage(this._computer.anchor); - if (loadingMessage) { - return result.slice(0).concat([loadingMessage]); - } - } - } - } - return result; - } - - private _withResult(hoverResult: HoverResult): void { - if (this._widget.position && this._currentResult && this._currentResult.isComplete) { - // The hover is visible with a previous complete result. - - if (!hoverResult.isComplete) { - // Instead of rendering the new partial result, we wait for the result to be complete. - return; - } - - if (this._computer.insistOnKeepingHoverVisible && hoverResult.messages.length === 0) { - // The hover would now hide normally, so we'll keep the previous messages - return; - } - } - - this._setCurrentResult(hoverResult); - } - - private _renderMessages(anchor: HoverAnchor, messages: IHoverPart[]): void { - const { showAtPosition, showAtSecondaryPosition, highlightRange } = ContentHoverController.computeHoverRanges(this._editor, anchor.range, messages); - - const disposables = new DisposableStore(); - const statusBar = disposables.add(new EditorHoverStatusBar(this._keybindingService)); - const fragment = document.createDocumentFragment(); - - let colorPicker: IEditorHoverColorPickerWidget | null = null; - const context: IEditorHoverRenderContext = { - fragment, - statusBar, - setColorPicker: (widget) => colorPicker = widget, - onContentsChanged: () => this._doOnContentsChanged(), - setMinimumDimensions: (dimensions: dom.Dimension) => this._widget.setMinimumDimensions(dimensions), - hide: () => this.hide() - }; - - for (const participant of this._participants) { - const hoverParts = messages.filter(msg => msg.owner === participant); - if (hoverParts.length > 0) { - disposables.add(participant.renderHoverParts(context, hoverParts)); - } - } - - const isBeforeContent = messages.some(m => m.isBeforeContent); - - if (statusBar.hasContent) { - fragment.appendChild(statusBar.hoverElement); - } - - if (fragment.hasChildNodes()) { - if (highlightRange) { - const highlightDecoration = this._editor.createDecorationsCollection(); - highlightDecoration.set([{ - range: highlightRange, - options: ContentHoverController._DECORATION_OPTIONS - }]); - disposables.add(toDisposable(() => { - highlightDecoration.clear(); - })); - } - - this._widget.showAt(fragment, new ContentHoverVisibleData( - anchor.initialMousePosX, - anchor.initialMousePosY, - colorPicker, - showAtPosition, - showAtSecondaryPosition, - this._editor.getOption(EditorOption.hover).above, - this._computer.shouldFocus, - this._computer.source, - isBeforeContent, - disposables - )); - } else { - disposables.dispose(); - } - } - - private _doOnContentsChanged(): void { - this._onContentsChanged.fire(); - this._widget.onContentsChanged(); - } - - private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ - description: 'content-hover-highlight', - className: 'hoverHighlight' - }); - - public static computeHoverRanges(editor: ICodeEditor, anchorRange: Range, messages: IHoverPart[]) { - - let startColumnBoundary = 1; - if (editor.hasModel()) { - // Ensure the range is on the current view line - const viewModel = editor._getViewModel(); - const coordinatesConverter = viewModel.coordinatesConverter; - const anchorViewRange = coordinatesConverter.convertModelRangeToViewRange(anchorRange); - const anchorViewRangeStart = new Position(anchorViewRange.startLineNumber, viewModel.getLineMinColumn(anchorViewRange.startLineNumber)); - startColumnBoundary = coordinatesConverter.convertViewPositionToModelPosition(anchorViewRangeStart).column; - } - - // The anchor range is always on a single line - const anchorLineNumber = anchorRange.startLineNumber; - let renderStartColumn = anchorRange.startColumn; - let highlightRange = messages[0].range; - let forceShowAtRange = null; - - for (const msg of messages) { - highlightRange = Range.plusRange(highlightRange, msg.range); - if (msg.range.startLineNumber === anchorLineNumber && msg.range.endLineNumber === anchorLineNumber) { - // this message has a range that is completely sitting on the line of the anchor - renderStartColumn = Math.max(Math.min(renderStartColumn, msg.range.startColumn), startColumnBoundary); - } - if (msg.forceShowAtRange) { - forceShowAtRange = msg.range; - } - } - - const showAtPosition = forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorLineNumber, anchorRange.startColumn); - const showAtSecondaryPosition = forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorLineNumber, renderStartColumn); - - return { - showAtPosition, - showAtSecondaryPosition, - highlightRange - }; - } - - public showsOrWillShow(mouseEvent: IEditorMouseEvent): boolean { - - if (this._widget.isResizing) { - return true; - } - - const anchorCandidates: HoverAnchor[] = []; - for (const participant of this._participants) { - if (participant.suggestHoverAnchor) { - const anchor = participant.suggestHoverAnchor(mouseEvent); - if (anchor) { - anchorCandidates.push(anchor); - } - } - } - - const target = mouseEvent.target; - - if (target.type === MouseTargetType.CONTENT_TEXT) { - anchorCandidates.push(new HoverRangeAnchor(0, target.range, mouseEvent.event.posx, mouseEvent.event.posy)); - } - - if (target.type === MouseTargetType.CONTENT_EMPTY) { - const epsilon = this._editor.getOption(EditorOption.fontInfo).typicalHalfwidthCharacterWidth / 2; - if ( - !target.detail.isAfterLines - && typeof target.detail.horizontalDistanceToText === 'number' - && target.detail.horizontalDistanceToText < epsilon - ) { - // Let hover kick in even when the mouse is technically in the empty area after a line, given the distance is small enough - anchorCandidates.push(new HoverRangeAnchor(0, target.range, mouseEvent.event.posx, mouseEvent.event.posy)); - } - } - - if (anchorCandidates.length === 0) { - return this._startShowingOrUpdateHover(null, HoverStartMode.Delayed, HoverStartSource.Mouse, false, mouseEvent); - } - - anchorCandidates.sort((a, b) => b.priority - a.priority); - return this._startShowingOrUpdateHover(anchorCandidates[0], HoverStartMode.Delayed, HoverStartSource.Mouse, false, mouseEvent); - } - - public startShowingAtRange(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean): void { - this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, source, focus, null); - } - - public async updateMarkdownHoverVerbosityLevel(action: HoverVerbosityAction, index?: number, focus?: boolean): Promise { - this._markdownHoverParticipant?.updateMarkdownHoverVerbosityLevel(action, index, focus); - } - - public focusedMarkdownHoverIndex(): number { - return this._markdownHoverParticipant?.focusedMarkdownHoverIndex() ?? -1; - } - - public markdownHoverContentAtIndex(index: number): string { - return this._markdownHoverParticipant?.markdownHoverContentAtIndex(index) ?? ''; - } - - public doesMarkdownHoverAtIndexSupportVerbosityAction(index: number, action: HoverVerbosityAction): boolean { - return this._markdownHoverParticipant?.doesMarkdownHoverAtIndexSupportVerbosityAction(index, action) ?? false; - } - - public getWidgetContent(): string | undefined { - const node = this._widget.getDomNode(); - if (!node.textContent) { - return undefined; - } - return node.textContent; - } - - public containsNode(node: Node | null | undefined): boolean { - return (node ? this._widget.getDomNode().contains(node) : false); - } - - public focus(): void { - this._widget.focus(); - } - - public scrollUp(): void { - this._widget.scrollUp(); - } - - public scrollDown(): void { - this._widget.scrollDown(); - } - - public scrollLeft(): void { - this._widget.scrollLeft(); - } - - public scrollRight(): void { - this._widget.scrollRight(); - } - - public pageUp(): void { - this._widget.pageUp(); - } - - public pageDown(): void { - this._widget.pageDown(); - } - - public goToTop(): void { - this._widget.goToTop(); - } - - public goToBottom(): void { - this._widget.goToBottom(); - } - - public hide(): void { - this._computer.anchor = null; - this._hoverOperation.cancel(); - this._setCurrentResult(null); - } - - public get isColorPickerVisible(): boolean { - return this._widget.isColorPickerVisible; - } - - public get isVisibleFromKeyboard(): boolean { - return this._widget.isVisibleFromKeyboard; - } - - public get isVisible(): boolean { - return this._widget.isVisible; - } - - public get isFocused(): boolean { - return this._widget.isFocused; - } - - public get isResizing(): boolean { - return this._widget.isResizing; - } - - public get widget() { - return this._widget; - } -} diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverController.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverController2.ts similarity index 75% rename from patched-vscode/src/vs/editor/contrib/hover/browser/hoverController.ts rename to patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverController2.ts index 293902db..e4ca2826 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverController.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverController2.ts @@ -7,22 +7,21 @@ import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution, IScrollEvent } from 'vs/editor/common/editorCommon'; import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes'; -import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget'; +import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { HoverVerbosityAction } from 'vs/editor/common/languages'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { ContentHoverWidget } from 'vs/editor/contrib/hover/browser/contentHoverWidget'; -import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController'; +import { isMousePositionWithinElement } from 'vs/editor/contrib/hover/browser/hoverUtils'; +import { ContentHoverWidgetWrapper } from 'vs/editor/contrib/hover/browser/contentHoverWidgetWrapper'; import 'vs/css!./hover'; -import { MarginHoverWidget } from 'vs/editor/contrib/hover/browser/marginHoverWidget'; import { Emitter } from 'vs/base/common/event'; // sticky hover widget which doesn't disappear on focus out and such @@ -41,24 +40,18 @@ interface IHoverState { activatedByDecoratorClick: boolean; } -const enum HoverWidgetType { - Content, - Glyph, -} - -export class HoverController extends Disposable implements IEditorContribution { +export class ContentHoverController extends Disposable implements IEditorContribution { private readonly _onHoverContentsChanged = this._register(new Emitter()); public readonly onHoverContentsChanged = this._onHoverContentsChanged.event; - public static readonly ID = 'editor.contrib.hover'; + public static readonly ID = 'editor.contrib.contentHover'; public shouldKeepOpenOnEditorMouseMoveOrLeave: boolean = false; private readonly _listenersStore = new DisposableStore(); - private _glyphWidget: MarginHoverWidget | undefined; - private _contentWidget: ContentHoverController | undefined; + private _contentWidget: ContentHoverWidgetWrapper | undefined; private _mouseMoveEvent: IEditorMouseEvent | undefined; private _reactToEditorMouseMoveRunner: RunOnceScheduler; @@ -89,8 +82,8 @@ export class HoverController extends Disposable implements IEditorContribution { })); } - static get(editor: ICodeEditor): HoverController | null { - return editor.getContribution(HoverController.ID); + static get(editor: ICodeEditor): ContentHoverController | null { + return editor.getContribution(ContentHoverController.ID); } private _hookListeners(): void { @@ -99,7 +92,7 @@ export class HoverController extends Disposable implements IEditorContribution { this._hoverSettings = { enabled: hoverOpts.enabled, sticky: hoverOpts.sticky, - hidingDelay: hoverOpts.delay + hidingDelay: hoverOpts.hidingDelay }; if (hoverOpts.enabled) { @@ -149,30 +142,15 @@ export class HoverController extends Disposable implements IEditorContribution { } private _shouldNotHideCurrentHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { - if ( - this._isMouseOnContentHoverWidget(mouseEvent) - || this._isMouseOnMarginHoverWidget(mouseEvent) - || this._isContentWidgetResizing() - ) { - return true; - } - return false; - } - - private _isMouseOnMarginHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { - const target = mouseEvent.target; - if (!target) { - return false; - } - return target.type === MouseTargetType.OVERLAY_WIDGET && target.detail === MarginHoverWidget.ID; + return this._isMouseOnContentHoverWidget(mouseEvent) || this._isContentWidgetResizing(); } private _isMouseOnContentHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { - const target = mouseEvent.target; - if (!target) { - return false; + const contentWidgetNode = this._contentWidget?.getDomNode(); + if (contentWidgetNode) { + return isMousePositionWithinElement(contentWidgetNode, mouseEvent.event.posx, mouseEvent.event.posy); } - return target.type === MouseTargetType.CONTENT_WIDGET && target.detail === ContentHoverWidget.ID; + return false; } private _onEditorMouseUp(): void { @@ -200,35 +178,25 @@ export class HoverController extends Disposable implements IEditorContribution { const isHoverSticky = this._hoverSettings.sticky; - const isMouseOnStickyMarginHoverWidget = (mouseEvent: IEditorMouseEvent, isHoverSticky: boolean) => { - const isMouseOnMarginHoverWidget = this._isMouseOnMarginHoverWidget(mouseEvent); - return isHoverSticky && isMouseOnMarginHoverWidget; - }; - const isMouseOnStickyContentHoverWidget = (mouseEvent: IEditorMouseEvent, isHoverSticky: boolean) => { + const isMouseOnStickyContentHoverWidget = (mouseEvent: IEditorMouseEvent, isHoverSticky: boolean): boolean => { const isMouseOnContentHoverWidget = this._isMouseOnContentHoverWidget(mouseEvent); return isHoverSticky && isMouseOnContentHoverWidget; }; - const isMouseOnColorPicker = (mouseEvent: IEditorMouseEvent) => { + const isMouseOnColorPicker = (mouseEvent: IEditorMouseEvent): boolean => { const isMouseOnContentHoverWidget = this._isMouseOnContentHoverWidget(mouseEvent); - const isColorPickerVisible = this._contentWidget?.isColorPickerVisible; + const isColorPickerVisible = this._contentWidget?.isColorPickerVisible ?? false; return isMouseOnContentHoverWidget && isColorPickerVisible; }; // TODO@aiday-mar verify if the following is necessary code - const isTextSelectedWithinContentHoverWidget = (mouseEvent: IEditorMouseEvent, sticky: boolean) => { - return sticky + const isTextSelectedWithinContentHoverWidget = (mouseEvent: IEditorMouseEvent, sticky: boolean): boolean => { + return (sticky && this._contentWidget?.containsNode(mouseEvent.event.browserEvent.view?.document.activeElement) - && !mouseEvent.event.browserEvent.view?.getSelection()?.isCollapsed; + && !mouseEvent.event.browserEvent.view?.getSelection()?.isCollapsed) ?? false; }; - if ( - isMouseOnStickyMarginHoverWidget(mouseEvent, isHoverSticky) - || isMouseOnStickyContentHoverWidget(mouseEvent, isHoverSticky) + return isMouseOnStickyContentHoverWidget(mouseEvent, isHoverSticky) || isMouseOnColorPicker(mouseEvent) - || isTextSelectedWithinContentHoverWidget(mouseEvent, isHoverSticky) - ) { - return true; - } - return false; + || isTextSelectedWithinContentHoverWidget(mouseEvent, isHoverSticky); } private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { @@ -294,44 +262,20 @@ export class HoverController extends Disposable implements IEditorContribution { return; } - const contentHoverShowsOrWillShow = this._tryShowHoverWidget(mouseEvent, HoverWidgetType.Content); + const contentHoverShowsOrWillShow = this._tryShowHoverWidget(mouseEvent); if (contentHoverShowsOrWillShow) { return; } - const glyphWidgetShowsOrWillShow = this._tryShowHoverWidget(mouseEvent, HoverWidgetType.Glyph); - if (glyphWidgetShowsOrWillShow) { - return; - } if (_sticky) { return; } this._hideWidgets(); } - private _tryShowHoverWidget(mouseEvent: IEditorMouseEvent, hoverWidgetType: HoverWidgetType): boolean { + private _tryShowHoverWidget(mouseEvent: IEditorMouseEvent): boolean { const contentWidget: IHoverWidget = this._getOrCreateContentWidget(); - const glyphWidget: IHoverWidget = this._getOrCreateGlyphWidget(); - let currentWidget: IHoverWidget; - let otherWidget: IHoverWidget; - switch (hoverWidgetType) { - case HoverWidgetType.Content: - currentWidget = contentWidget; - otherWidget = glyphWidget; - break; - case HoverWidgetType.Glyph: - currentWidget = glyphWidget; - otherWidget = contentWidget; - break; - default: - throw new Error(`HoverWidgetType ${hoverWidgetType} is unrecognized`); - } - - const showsOrWillShow = currentWidget.showsOrWillShow(mouseEvent); - if (showsOrWillShow) { - otherWidget.hide(); - } - return showsOrWillShow; + return contentWidget.showsOrWillShow(mouseEvent); } private _onKeyDown(e: IKeyboardEvent): void { @@ -379,25 +323,17 @@ export class HoverController extends Disposable implements IEditorContribution { return; } this._hoverState.activatedByDecoratorClick = false; - this._glyphWidget?.hide(); this._contentWidget?.hide(); } - private _getOrCreateContentWidget(): ContentHoverController { + private _getOrCreateContentWidget(): ContentHoverWidgetWrapper { if (!this._contentWidget) { - this._contentWidget = this._instantiationService.createInstance(ContentHoverController, this._editor); + this._contentWidget = this._instantiationService.createInstance(ContentHoverWidgetWrapper, this._editor); this._listenersStore.add(this._contentWidget.onContentsChanged(() => this._onHoverContentsChanged.fire())); } return this._contentWidget; } - private _getOrCreateGlyphWidget(): MarginHoverWidget { - if (!this._glyphWidget) { - this._glyphWidget = this._instantiationService.createInstance(MarginHoverWidget, this._editor); - } - return this._glyphWidget; - } - public hideContentHover(): void { this._hideWidgets(); } @@ -417,26 +353,26 @@ export class HoverController extends Disposable implements IEditorContribution { return this._contentWidget?.widget.isResizing || false; } - public focusedMarkdownHoverIndex(): number { - return this._getOrCreateContentWidget().focusedMarkdownHoverIndex(); + public focusedHoverPartIndex(): number { + return this._getOrCreateContentWidget().focusedHoverPartIndex(); } - public markdownHoverContentAtIndex(index: number): string { - return this._getOrCreateContentWidget().markdownHoverContentAtIndex(index); + public doesHoverAtIndexSupportVerbosityAction(index: number, action: HoverVerbosityAction): boolean { + return this._getOrCreateContentWidget().doesHoverAtIndexSupportVerbosityAction(index, action); } - public doesMarkdownHoverAtIndexSupportVerbosityAction(index: number, action: HoverVerbosityAction): boolean { - return this._getOrCreateContentWidget().doesMarkdownHoverAtIndexSupportVerbosityAction(index, action); - } - - public updateMarkdownHoverVerbosityLevel(action: HoverVerbosityAction, index?: number, focus?: boolean): void { - this._getOrCreateContentWidget().updateMarkdownHoverVerbosityLevel(action, index, focus); + public updateHoverVerbosityLevel(action: HoverVerbosityAction, index: number, focus?: boolean): void { + this._getOrCreateContentWidget().updateHoverVerbosityLevel(action, index, focus); } public focus(): void { this._contentWidget?.focus(); } + public focusHoverPartWithIndex(index: number): void { + this._contentWidget?.focusHoverPartWithIndex(index); + } + public scrollUp(): void { this._contentWidget?.scrollUp(); } @@ -473,6 +409,14 @@ export class HoverController extends Disposable implements IEditorContribution { return this._contentWidget?.getWidgetContent(); } + public getAccessibleWidgetContent(): string | undefined { + return this._contentWidget?.getAccessibleWidgetContent(); + } + + public getAccessibleWidgetContentAtIndex(index: number): string | undefined { + return this._contentWidget?.getAccessibleWidgetContentAtIndex(index); + } + public get isColorPickerVisible(): boolean | undefined { return this._contentWidget?.isColorPickerVisible; } @@ -485,7 +429,6 @@ export class HoverController extends Disposable implements IEditorContribution { super.dispose(); this._unhookListeners(); this._listenersStore.dispose(); - this._glyphWidget?.dispose(); this._contentWidget?.dispose(); } } diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts new file mode 100644 index 00000000..9308e6f2 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts @@ -0,0 +1,436 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEditorHoverContext, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverParts, RenderedHoverParts } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ContentHoverComputer } from 'vs/editor/contrib/hover/browser/contentHoverComputer'; +import { EditorHoverStatusBar } from 'vs/editor/contrib/hover/browser/contentHoverStatusBar'; +import { HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes'; +import * as dom from 'vs/base/browser/dom'; +import { HoverVerbosityAction } from 'vs/editor/common/languages'; +import { MarkdownHoverParticipant } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant'; +import { ColorHoverParticipant } from 'vs/editor/contrib/colorPicker/browser/colorHoverParticipant'; +import { localize } from 'vs/nls'; +import { InlayHintsHover } from 'vs/editor/contrib/inlayHints/browser/inlayHintsHover'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { HoverAction } from 'vs/base/browser/ui/hover/hoverWidget'; + +export class RenderedContentHover extends Disposable { + + public closestMouseDistance: number | undefined; + public initialMousePosX: number | undefined; + public initialMousePosY: number | undefined; + + public readonly showAtPosition: Position; + public readonly showAtSecondaryPosition: Position; + public readonly shouldFocus: boolean; + public readonly source: HoverStartSource; + public readonly shouldAppearBeforeContent: boolean; + + private readonly _renderedHoverParts: RenderedContentHoverParts; + + constructor( + editor: ICodeEditor, + hoverResult: HoverResult, + participants: IEditorHoverParticipant[], + computer: ContentHoverComputer, + context: IEditorHoverContext, + keybindingService: IKeybindingService + ) { + super(); + const anchor = hoverResult.anchor; + const parts = hoverResult.hoverParts; + this._renderedHoverParts = this._register(new RenderedContentHoverParts( + editor, + participants, + parts, + keybindingService, + context + )); + const { showAtPosition, showAtSecondaryPosition } = RenderedContentHover.computeHoverPositions(editor, anchor.range, parts); + this.shouldAppearBeforeContent = parts.some(m => m.isBeforeContent); + this.showAtPosition = showAtPosition; + this.showAtSecondaryPosition = showAtSecondaryPosition; + this.initialMousePosX = anchor.initialMousePosX; + this.initialMousePosY = anchor.initialMousePosY; + this.shouldFocus = computer.shouldFocus; + this.source = computer.source; + } + + public get domNode(): DocumentFragment { + return this._renderedHoverParts.domNode; + } + + public get domNodeHasChildren(): boolean { + return this._renderedHoverParts.domNodeHasChildren; + } + + public get focusedHoverPartIndex(): number { + return this._renderedHoverParts.focusedHoverPartIndex; + } + + public focusHoverPartWithIndex(index: number): void { + this._renderedHoverParts.focusHoverPartWithIndex(index); + } + + public getAccessibleWidgetContent(): string { + return this._renderedHoverParts.getAccessibleContent(); + } + + public getAccessibleWidgetContentAtIndex(index: number): string { + return this._renderedHoverParts.getAccessibleHoverContentAtIndex(index); + } + + public async updateHoverVerbosityLevel(action: HoverVerbosityAction, index: number, focus?: boolean): Promise { + this._renderedHoverParts.updateHoverVerbosityLevel(action, index, focus); + } + + public doesHoverAtIndexSupportVerbosityAction(index: number, action: HoverVerbosityAction): boolean { + return this._renderedHoverParts.doesHoverAtIndexSupportVerbosityAction(index, action); + } + + public isColorPickerVisible(): boolean { + return this._renderedHoverParts.isColorPickerVisible(); + } + + public static computeHoverPositions(editor: ICodeEditor, anchorRange: Range, hoverParts: IHoverPart[]): { showAtPosition: Position; showAtSecondaryPosition: Position } { + + let startColumnBoundary = 1; + if (editor.hasModel()) { + // Ensure the range is on the current view line + const viewModel = editor._getViewModel(); + const coordinatesConverter = viewModel.coordinatesConverter; + const anchorViewRange = coordinatesConverter.convertModelRangeToViewRange(anchorRange); + const anchorViewMinColumn = viewModel.getLineMinColumn(anchorViewRange.startLineNumber); + const anchorViewRangeStart = new Position(anchorViewRange.startLineNumber, anchorViewMinColumn); + startColumnBoundary = coordinatesConverter.convertViewPositionToModelPosition(anchorViewRangeStart).column; + } + + // The anchor range is always on a single line + const anchorStartLineNumber = anchorRange.startLineNumber; + let secondaryPositionColumn = anchorRange.startColumn; + let forceShowAtRange: Range | undefined; + + for (const hoverPart of hoverParts) { + const hoverPartRange = hoverPart.range; + const hoverPartRangeOnAnchorStartLine = hoverPartRange.startLineNumber === anchorStartLineNumber; + const hoverPartRangeOnAnchorEndLine = hoverPartRange.endLineNumber === anchorStartLineNumber; + const hoverPartRangeIsOnAnchorLine = hoverPartRangeOnAnchorStartLine && hoverPartRangeOnAnchorEndLine; + if (hoverPartRangeIsOnAnchorLine) { + // this message has a range that is completely sitting on the line of the anchor + const hoverPartStartColumn = hoverPartRange.startColumn; + const minSecondaryPositionColumn = Math.min(secondaryPositionColumn, hoverPartStartColumn); + secondaryPositionColumn = Math.max(minSecondaryPositionColumn, startColumnBoundary); + } + if (hoverPart.forceShowAtRange) { + forceShowAtRange = hoverPartRange; + } + } + + let showAtPosition: Position; + let showAtSecondaryPosition: Position; + if (forceShowAtRange) { + const forceShowAtPosition = forceShowAtRange.getStartPosition(); + showAtPosition = forceShowAtPosition; + showAtSecondaryPosition = forceShowAtPosition; + } else { + showAtPosition = anchorRange.getStartPosition(); + showAtSecondaryPosition = new Position(anchorStartLineNumber, secondaryPositionColumn); + } + return { + showAtPosition, + showAtSecondaryPosition, + }; + } +} + +interface IRenderedContentHoverPart { + /** + * Type of rendered part + */ + type: 'hoverPart'; + /** + * Participant of the rendered hover part + */ + participant: IEditorHoverParticipant; + /** + * The rendered hover part + */ + hoverPart: IHoverPart; + /** + * The HTML element containing the hover status bar. + */ + hoverElement: HTMLElement; +} + +interface IRenderedContentStatusBar { + /** + * Type of rendered part + */ + type: 'statusBar'; + /** + * The HTML element containing the hover status bar. + */ + hoverElement: HTMLElement; + /** + * The actions of the hover status bar. + */ + actions: HoverAction[]; +} + +type IRenderedContentHoverPartOrStatusBar = IRenderedContentHoverPart | IRenderedContentStatusBar; + +class RenderedStatusBar implements IDisposable { + + constructor(fragment: DocumentFragment, private readonly _statusBar: EditorHoverStatusBar) { + fragment.appendChild(this._statusBar.hoverElement); + } + + get hoverElement(): HTMLElement { + return this._statusBar.hoverElement; + } + + get actions(): HoverAction[] { + return this._statusBar.actions; + } + + dispose() { + this._statusBar.dispose(); + } +} + +class RenderedContentHoverParts extends Disposable { + + private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ + description: 'content-hover-highlight', + className: 'hoverHighlight' + }); + + private readonly _renderedParts: IRenderedContentHoverPartOrStatusBar[] = []; + private readonly _fragment: DocumentFragment; + private readonly _context: IEditorHoverContext; + + private _markdownHoverParticipant: MarkdownHoverParticipant | undefined; + private _colorHoverParticipant: ColorHoverParticipant | undefined; + private _focusedHoverPartIndex: number = -1; + + constructor( + editor: ICodeEditor, + participants: IEditorHoverParticipant[], + hoverParts: IHoverPart[], + keybindingService: IKeybindingService, + context: IEditorHoverContext + ) { + super(); + this._context = context; + this._fragment = document.createDocumentFragment(); + this._register(this._renderParts(participants, hoverParts, context, keybindingService)); + this._register(this._registerListenersOnRenderedParts()); + this._register(this._createEditorDecorations(editor, hoverParts)); + this._updateMarkdownAndColorParticipantInfo(participants); + } + + private _createEditorDecorations(editor: ICodeEditor, hoverParts: IHoverPart[]): IDisposable { + if (hoverParts.length === 0) { + return Disposable.None; + } + let highlightRange = hoverParts[0].range; + for (const hoverPart of hoverParts) { + const hoverPartRange = hoverPart.range; + highlightRange = Range.plusRange(highlightRange, hoverPartRange); + } + const highlightDecoration = editor.createDecorationsCollection(); + highlightDecoration.set([{ + range: highlightRange, + options: RenderedContentHoverParts._DECORATION_OPTIONS + }]); + return toDisposable(() => { + highlightDecoration.clear(); + }); + } + + private _renderParts(participants: IEditorHoverParticipant[], hoverParts: IHoverPart[], hoverContext: IEditorHoverContext, keybindingService: IKeybindingService): IDisposable { + const statusBar = new EditorHoverStatusBar(keybindingService); + const hoverRenderingContext: IEditorHoverRenderContext = { + fragment: this._fragment, + statusBar, + ...hoverContext + }; + const disposables = new DisposableStore(); + for (const participant of participants) { + const renderedHoverParts = this._renderHoverPartsForParticipant(hoverParts, participant, hoverRenderingContext); + disposables.add(renderedHoverParts); + for (const renderedHoverPart of renderedHoverParts.renderedHoverParts) { + this._renderedParts.push({ + type: 'hoverPart', + participant, + hoverPart: renderedHoverPart.hoverPart, + hoverElement: renderedHoverPart.hoverElement, + }); + } + } + const renderedStatusBar = this._renderStatusBar(this._fragment, statusBar); + if (renderedStatusBar) { + disposables.add(renderedStatusBar); + this._renderedParts.push({ + type: 'statusBar', + hoverElement: renderedStatusBar.hoverElement, + actions: renderedStatusBar.actions, + }); + } + return toDisposable(() => { disposables.dispose(); }); + } + + private _renderHoverPartsForParticipant(hoverParts: IHoverPart[], participant: IEditorHoverParticipant, hoverRenderingContext: IEditorHoverRenderContext): IRenderedHoverParts { + const hoverPartsForParticipant = hoverParts.filter(hoverPart => hoverPart.owner === participant); + const hasHoverPartsForParticipant = hoverPartsForParticipant.length > 0; + if (!hasHoverPartsForParticipant) { + return new RenderedHoverParts([]); + } + return participant.renderHoverParts(hoverRenderingContext, hoverPartsForParticipant); + } + + private _renderStatusBar(fragment: DocumentFragment, statusBar: EditorHoverStatusBar): RenderedStatusBar | undefined { + if (!statusBar.hasContent) { + return undefined; + } + return new RenderedStatusBar(fragment, statusBar); + } + + private _registerListenersOnRenderedParts(): IDisposable { + const disposables = new DisposableStore(); + this._renderedParts.forEach((renderedPart: IRenderedContentHoverPartOrStatusBar, index: number) => { + const element = renderedPart.hoverElement; + element.tabIndex = 0; + disposables.add(dom.addDisposableListener(element, dom.EventType.FOCUS_IN, (event: Event) => { + event.stopPropagation(); + this._focusedHoverPartIndex = index; + })); + disposables.add(dom.addDisposableListener(element, dom.EventType.FOCUS_OUT, (event: Event) => { + event.stopPropagation(); + this._focusedHoverPartIndex = -1; + })); + }); + return disposables; + } + + private _updateMarkdownAndColorParticipantInfo(participants: IEditorHoverParticipant[]) { + const markdownHoverParticipant = participants.find(p => { + return (p instanceof MarkdownHoverParticipant) && !(p instanceof InlayHintsHover); + }); + if (markdownHoverParticipant) { + this._markdownHoverParticipant = markdownHoverParticipant as MarkdownHoverParticipant; + } + this._colorHoverParticipant = participants.find(p => p instanceof ColorHoverParticipant); + } + + public focusHoverPartWithIndex(index: number): void { + if (index < 0 || index >= this._renderedParts.length) { + return; + } + this._renderedParts[index].hoverElement.focus(); + } + + public getAccessibleContent(): string { + const content: string[] = []; + for (let i = 0; i < this._renderedParts.length; i++) { + content.push(this.getAccessibleHoverContentAtIndex(i)); + } + return content.join('\n\n'); + } + + public getAccessibleHoverContentAtIndex(index: number): string { + const renderedPart = this._renderedParts[index]; + if (!renderedPart) { + return ''; + } + if (renderedPart.type === 'statusBar') { + const statusBarDescription = [localize('hoverAccessibilityStatusBar', "This is a hover status bar.")]; + for (const action of renderedPart.actions) { + const keybinding = action.actionKeybindingLabel; + if (keybinding) { + statusBarDescription.push(localize('hoverAccessibilityStatusBarActionWithKeybinding', "It has an action with label {0} and keybinding {1}.", action.actionLabel, keybinding)); + } else { + statusBarDescription.push(localize('hoverAccessibilityStatusBarActionWithoutKeybinding', "It has an action with label {0}.", action.actionLabel)); + } + } + return statusBarDescription.join('\n'); + } + return renderedPart.participant.getAccessibleContent(renderedPart.hoverPart); + } + + public async updateHoverVerbosityLevel(action: HoverVerbosityAction, index: number, focus?: boolean): Promise { + if (!this._markdownHoverParticipant) { + return; + } + const normalizedMarkdownHoverIndex = this._normalizedIndexToMarkdownHoverIndexRange(this._markdownHoverParticipant, index); + if (normalizedMarkdownHoverIndex === undefined) { + return; + } + const renderedPart = await this._markdownHoverParticipant.updateMarkdownHoverVerbosityLevel(action, normalizedMarkdownHoverIndex, focus); + if (!renderedPart) { + return; + } + this._renderedParts[index] = { + type: 'hoverPart', + participant: this._markdownHoverParticipant, + hoverPart: renderedPart.hoverPart, + hoverElement: renderedPart.hoverElement, + }; + this._context.onContentsChanged(); + } + + public doesHoverAtIndexSupportVerbosityAction(index: number, action: HoverVerbosityAction): boolean { + if (!this._markdownHoverParticipant) { + return false; + } + const normalizedMarkdownHoverIndex = this._normalizedIndexToMarkdownHoverIndexRange(this._markdownHoverParticipant, index); + if (normalizedMarkdownHoverIndex === undefined) { + return false; + } + return this._markdownHoverParticipant.doesMarkdownHoverAtIndexSupportVerbosityAction(normalizedMarkdownHoverIndex, action); + } + + public isColorPickerVisible(): boolean { + return this._colorHoverParticipant?.isColorPickerVisible() ?? false; + } + + private _normalizedIndexToMarkdownHoverIndexRange(markdownHoverParticipant: MarkdownHoverParticipant, index: number): number | undefined { + const renderedPart = this._renderedParts[index]; + if (!renderedPart || renderedPart.type !== 'hoverPart') { + return undefined; + } + const isHoverPartMarkdownHover = renderedPart.participant === markdownHoverParticipant; + if (!isHoverPartMarkdownHover) { + return undefined; + } + const firstIndexOfMarkdownHovers = this._renderedParts.findIndex(renderedPart => + renderedPart.type === 'hoverPart' + && renderedPart.participant === markdownHoverParticipant + ); + if (firstIndexOfMarkdownHovers === -1) { + throw new BugIndicatingError(); + } + return index - firstIndexOfMarkdownHovers; + } + + public get domNode(): DocumentFragment { + return this._fragment; + } + + public get domNodeHasChildren(): boolean { + return this._fragment.hasChildNodes(); + } + + public get focusedHoverPartIndex(): number { + return this._focusedHoverPartIndex; + } +} diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts index 04d84593..bb439594 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts @@ -13,6 +13,8 @@ const $ = dom.$; export class EditorHoverStatusBar extends Disposable implements IEditorHoverStatusBar { public readonly hoverElement: HTMLElement; + public readonly actions: HoverAction[] = []; + private readonly actionsElement: HTMLElement; private _hasContent: boolean = false; @@ -39,7 +41,9 @@ export class EditorHoverStatusBar extends Disposable implements IEditorHoverStat const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId); const keybindingLabel = keybinding ? keybinding.getLabel() : null; this._hasContent = true; - return this._register(HoverAction.render(this.actionsElement, actionOptions, keybindingLabel)); + const action = this._register(HoverAction.render(this.actionsElement, actionOptions, keybindingLabel)); + this.actions.push(action); + return action; } public append(element: HTMLElement): HTMLElement { diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverTypes.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverTypes.ts index 5d32dc83..a52d758b 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverTypes.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverTypes.ts @@ -3,25 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Position } from 'vs/editor/common/core/position'; -import { HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; -import { HoverAnchor, IEditorHoverColorPickerWidget, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { HoverAnchor, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; export class HoverResult { constructor( public readonly anchor: HoverAnchor, - public readonly messages: IHoverPart[], + public readonly hoverParts: IHoverPart[], public readonly isComplete: boolean ) { } public filter(anchor: HoverAnchor): HoverResult { - const filteredMessages = this.messages.filter((m) => m.isValidForHoverAnchor(anchor)); - if (filteredMessages.length === this.messages.length) { + const filteredHoverParts = this.hoverParts.filter((m) => m.isValidForHoverAnchor(anchor)); + if (filteredHoverParts.length === this.hoverParts.length) { return this; } - return new FilteredHoverResult(this, this.anchor, filteredMessages, this.isComplete); + return new FilteredHoverResult(this, this.anchor, filteredHoverParts, this.isComplete); } } @@ -40,21 +37,3 @@ export class FilteredHoverResult extends HoverResult { return this.original.filter(anchor); } } - -export class ContentHoverVisibleData { - - public closestMouseDistance: number | undefined = undefined; - - constructor( - public initialMousePosX: number | undefined, - public initialMousePosY: number | undefined, - public readonly colorPicker: IEditorHoverColorPickerWidget | null, - public readonly showAtPosition: Position, - public readonly showAtSecondaryPosition: Position, - public readonly preferAbove: boolean, - public readonly stoleFocus: boolean, - public readonly source: HoverStartSource, - public readonly isBeforeContent: boolean, - public readonly disposables: DisposableStore - ) { } -} diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts index 58903400..37cf8740 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts @@ -15,7 +15,8 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getHoverAccessibleViewHint, HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; import { PositionAffinity } from 'vs/editor/common/model'; -import { ContentHoverVisibleData } from 'vs/editor/contrib/hover/browser/contentHoverTypes'; +import { Emitter } from 'vs/base/common/event'; +import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered'; const HORIZONTAL_SCROLLING_BY = 30; const CONTAINER_HEIGHT_PADDING = 6; @@ -25,7 +26,7 @@ export class ContentHoverWidget extends ResizableContentWidget { public static ID = 'editor.contrib.resizableContentHoverWidget'; private static _lastDimensions: dom.Dimension = new dom.Dimension(0, 0); - private _visibleData: ContentHoverVisibleData | undefined; + private _renderedHover: RenderedContentHover | undefined; private _positionPreference: ContentWidgetPositionPreference | undefined; private _minimumSize: dom.Dimension; private _contentWidth: number | undefined; @@ -34,12 +35,11 @@ export class ContentHoverWidget extends ResizableContentWidget { private readonly _hoverVisibleKey: IContextKey; private readonly _hoverFocusedKey: IContextKey; - public get isColorPickerVisible(): boolean { - return Boolean(this._visibleData?.colorPicker); - } + private readonly _onDidResize = this._register(new Emitter()); + public readonly onDidResize = this._onDidResize.event; public get isVisibleFromKeyboard(): boolean { - return (this._visibleData?.source === HoverStartSource.Keyboard); + return (this._renderedHover?.source === HoverStartSource.Keyboard); } public get isVisible(): boolean { @@ -86,13 +86,13 @@ export class ContentHoverWidget extends ResizableContentWidget { this._register(focusTracker.onDidBlur(() => { this._hoverFocusedKey.set(false); })); - this._setHoverData(undefined); + this._setRenderedHover(undefined); this._editor.addContentWidget(this); } public override dispose(): void { super.dispose(); - this._visibleData?.disposables.dispose(); + this._renderedHover?.dispose(); this._editor.removeContentWidget(this); } @@ -158,11 +158,11 @@ export class ContentHoverWidget extends ResizableContentWidget { this._updateResizableNodeMaxDimensions(); this._hover.scrollbar.scanDomNode(); this._editor.layoutContentWidget(this); - this._visibleData?.colorPicker?.layout(); + this._onDidResize.fire(); } private _findAvailableSpaceVertically(): number | undefined { - const position = this._visibleData?.showAtPosition; + const position = this._renderedHover?.showAtPosition; if (!position) { return; } @@ -223,23 +223,20 @@ export class ContentHoverWidget extends ResizableContentWidget { public isMouseGettingCloser(posx: number, posy: number): boolean { - if (!this._visibleData) { + if (!this._renderedHover) { return false; } - if ( - typeof this._visibleData.initialMousePosX === 'undefined' - || typeof this._visibleData.initialMousePosY === 'undefined' - ) { - this._visibleData.initialMousePosX = posx; - this._visibleData.initialMousePosY = posy; + if (this._renderedHover.initialMousePosX === undefined || this._renderedHover.initialMousePosY === undefined) { + this._renderedHover.initialMousePosX = posx; + this._renderedHover.initialMousePosY = posy; return false; } const widgetRect = dom.getDomNodePagePosition(this.getDomNode()); - if (typeof this._visibleData.closestMouseDistance === 'undefined') { - this._visibleData.closestMouseDistance = computeDistanceFromPointToRectangle( - this._visibleData.initialMousePosX, - this._visibleData.initialMousePosY, + if (this._renderedHover.closestMouseDistance === undefined) { + this._renderedHover.closestMouseDistance = computeDistanceFromPointToRectangle( + this._renderedHover.initialMousePosX, + this._renderedHover.initialMousePosY, widgetRect.left, widgetRect.top, widgetRect.width, @@ -255,20 +252,20 @@ export class ContentHoverWidget extends ResizableContentWidget { widgetRect.width, widgetRect.height ); - if (distance > this._visibleData.closestMouseDistance + 4 /* tolerance of 4 pixels */) { + if (distance > this._renderedHover.closestMouseDistance + 4 /* tolerance of 4 pixels */) { // The mouse is getting farther away return false; } - this._visibleData.closestMouseDistance = Math.min(this._visibleData.closestMouseDistance, distance); + this._renderedHover.closestMouseDistance = Math.min(this._renderedHover.closestMouseDistance, distance); return true; } - private _setHoverData(hoverData: ContentHoverVisibleData | undefined): void { - this._visibleData?.disposables.dispose(); - this._visibleData = hoverData; - this._hoverVisibleKey.set(!!hoverData); - this._hover.containerDomNode.classList.toggle('hidden', !hoverData); + private _setRenderedHover(renderedHover: RenderedContentHover | undefined): void { + this._renderedHover?.dispose(); + this._renderedHover = renderedHover; + this._hoverVisibleKey.set(!!renderedHover); + this._hover.containerDomNode.classList.toggle('hidden', !renderedHover); } private _updateFont(): void { @@ -298,10 +295,10 @@ export class ContentHoverWidget extends ResizableContentWidget { this._setHoverWidgetMaxDimensions(width, height); } - private _render(node: DocumentFragment, hoverData: ContentHoverVisibleData) { - this._setHoverData(hoverData); + private _render(renderedHover: RenderedContentHover) { + this._setRenderedHover(renderedHover); this._updateFont(); - this._updateContent(node); + this._updateContent(renderedHover.domNode); this._updateMaxDimensions(); this.onContentsChanged(); // Simply force a synchronous render on the editor @@ -310,33 +307,33 @@ export class ContentHoverWidget extends ResizableContentWidget { } override getPosition(): IContentWidgetPosition | null { - if (!this._visibleData) { + if (!this._renderedHover) { return null; } return { - position: this._visibleData.showAtPosition, - secondaryPosition: this._visibleData.showAtSecondaryPosition, - positionAffinity: this._visibleData.isBeforeContent ? PositionAffinity.LeftOfInjectedText : undefined, + position: this._renderedHover.showAtPosition, + secondaryPosition: this._renderedHover.showAtSecondaryPosition, + positionAffinity: this._renderedHover.shouldAppearBeforeContent ? PositionAffinity.LeftOfInjectedText : undefined, preference: [this._positionPreference ?? ContentWidgetPositionPreference.ABOVE] }; } - public showAt(node: DocumentFragment, hoverData: ContentHoverVisibleData): void { + public show(renderedHover: RenderedContentHover): void { if (!this._editor || !this._editor.hasModel()) { return; } - this._render(node, hoverData); + this._render(renderedHover); const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); - const widgetPosition = hoverData.showAtPosition; + const widgetPosition = renderedHover.showAtPosition; this._positionPreference = this._findPositionPreference(widgetHeight, widgetPosition) ?? ContentWidgetPositionPreference.ABOVE; // See https://github.com/microsoft/vscode/issues/140339 // TODO: Doing a second layout of the hover after force rendering the editor this.onContentsChanged(); - if (hoverData.stoleFocus) { + if (renderedHover.shouldFocus) { this._hover.containerDomNode.focus(); } - hoverData.colorPicker?.layout(); + this._onDidResize.fire(); // The aria label overrides the label, so if we add to it, add the contents of the hover const hoverFocused = this._hover.containerDomNode.ownerDocument.activeElement === this._hover.containerDomNode; const accessibleViewHint = hoverFocused && getHoverAccessibleViewHint( @@ -350,16 +347,16 @@ export class ContentHoverWidget extends ResizableContentWidget { } public hide(): void { - if (!this._visibleData) { + if (!this._renderedHover) { return; } - const stoleFocus = this._visibleData.stoleFocus || this._hoverFocusedKey.get(); - this._setHoverData(undefined); + const hoverStoleFocus = this._renderedHover.shouldFocus || this._hoverFocusedKey.get(); + this._setRenderedHover(undefined); this._resizableNode.maxSize = new dom.Dimension(Infinity, Infinity); this._resizableNode.clearSashHoverState(); this._hoverFocusedKey.set(false); this._editor.layoutContentWidget(this); - if (stoleFocus) { + if (hoverStoleFocus) { this._editor.focus(); } } @@ -406,9 +403,9 @@ export class ContentHoverWidget extends ResizableContentWidget { this._updateMinimumWidth(); this._resizableNode.layout(height, width); - if (this._visibleData?.showAtPosition) { + if (this._renderedHover?.showAtPosition) { const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); - this._positionPreference = this._findPositionPreference(widgetHeight, this._visibleData.showAtPosition); + this._positionPreference = this._findPositionPreference(widgetHeight, this._renderedHover.showAtPosition); } this._layoutContentWidget(); } diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts new file mode 100644 index 00000000..492bce8b --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts @@ -0,0 +1,406 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Range } from 'vs/editor/common/core/range'; +import { TokenizationRegistry } from 'vs/editor/common/languages'; +import { HoverOperation, HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; +import { HoverAnchor, HoverParticipantRegistry, HoverRangeAnchor, IEditorHoverContext, IEditorHoverParticipant, IHoverPart, IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { HoverVerbosityAction } from 'vs/editor/common/standalone/standaloneEnums'; +import { ContentHoverWidget } from 'vs/editor/contrib/hover/browser/contentHoverWidget'; +import { ContentHoverComputer } from 'vs/editor/contrib/hover/browser/contentHoverComputer'; +import { HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes'; +import { Emitter } from 'vs/base/common/event'; +import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered'; +import { isMousePositionWithinElement } from 'vs/editor/contrib/hover/browser/hoverUtils'; + +export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidget { + + private _currentResult: HoverResult | null = null; + private _renderedContentHover: RenderedContentHover | undefined; + + private readonly _computer: ContentHoverComputer; + private readonly _contentHoverWidget: ContentHoverWidget; + private readonly _participants: IEditorHoverParticipant[]; + private readonly _hoverOperation: HoverOperation; + + private readonly _onContentsChanged = this._register(new Emitter()); + public readonly onContentsChanged = this._onContentsChanged.event; + + constructor( + private readonly _editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + ) { + super(); + this._contentHoverWidget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor)); + this._participants = this._initializeHoverParticipants(); + this._computer = new ContentHoverComputer(this._editor, this._participants); + this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer)); + this._registerListeners(); + } + + private _initializeHoverParticipants(): IEditorHoverParticipant[] { + const participants: IEditorHoverParticipant[] = []; + for (const participant of HoverParticipantRegistry.getAll()) { + const participantInstance = this._instantiationService.createInstance(participant, this._editor); + participants.push(participantInstance); + } + participants.sort((p1, p2) => p1.hoverOrdinal - p2.hoverOrdinal); + this._register(this._contentHoverWidget.onDidResize(() => { + this._participants.forEach(participant => participant.handleResize?.()); + })); + return participants; + } + + private _registerListeners(): void { + this._register(this._hoverOperation.onResult((result) => { + if (!this._computer.anchor) { + // invalid state, ignore result + return; + } + const messages = (result.hasLoadingMessage ? this._addLoadingMessage(result.value) : result.value); + this._withResult(new HoverResult(this._computer.anchor, messages, result.isComplete)); + })); + const contentHoverWidgetNode = this._contentHoverWidget.getDomNode(); + this._register(dom.addStandardDisposableListener(contentHoverWidgetNode, 'keydown', (e) => { + if (e.equals(KeyCode.Escape)) { + this.hide(); + } + })); + this._register(dom.addStandardDisposableListener(contentHoverWidgetNode, 'mouseleave', (e) => { + this._onMouseLeave(e); + })); + this._register(TokenizationRegistry.onDidChange(() => { + if (this._contentHoverWidget.position && this._currentResult) { + this._setCurrentResult(this._currentResult); // render again + } + })); + } + + /** + * Returns true if the hover shows now or will show. + */ + private _startShowingOrUpdateHover( + anchor: HoverAnchor | null, + mode: HoverStartMode, + source: HoverStartSource, + focus: boolean, + mouseEvent: IEditorMouseEvent | null + ): boolean { + const contentHoverIsVisible = this._contentHoverWidget.position && this._currentResult; + if (!contentHoverIsVisible) { + if (anchor) { + this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); + return true; + } + return false; + } + const isHoverSticky = this._editor.getOption(EditorOption.hover).sticky; + const isMouseGettingCloser = mouseEvent && this._contentHoverWidget.isMouseGettingCloser(mouseEvent.event.posx, mouseEvent.event.posy); + const isHoverStickyAndIsMouseGettingCloser = isHoverSticky && isMouseGettingCloser; + // The mouse is getting closer to the hover, so we will keep the hover untouched + // But we will kick off a hover update at the new anchor, insisting on keeping the hover visible. + if (isHoverStickyAndIsMouseGettingCloser) { + if (anchor) { + this._startHoverOperationIfNecessary(anchor, mode, source, focus, true); + } + return true; + } + // If mouse is not getting closer and anchor not defined, hide the hover + if (!anchor) { + this._setCurrentResult(null); + return false; + } + // If mouse if not getting closer and anchor is defined, and the new anchor is the same as the previous anchor + const currentAnchorEqualsPreviousAnchor = this._currentResult!.anchor.equals(anchor); + if (currentAnchorEqualsPreviousAnchor) { + return true; + } + // If mouse if not getting closer and anchor is defined, and the new anchor is not compatible with the previous anchor + const currentAnchorCompatibleWithPreviousAnchor = anchor.canAdoptVisibleHover(this._currentResult!.anchor, this._contentHoverWidget.position); + if (!currentAnchorCompatibleWithPreviousAnchor) { + this._setCurrentResult(null); + this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); + return true; + } + // We aren't getting any closer to the hover, so we will filter existing results + // and keep those which also apply to the new anchor. + this._setCurrentResult(this._currentResult!.filter(anchor)); + this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); + return true; + } + + private _startHoverOperationIfNecessary(anchor: HoverAnchor, mode: HoverStartMode, source: HoverStartSource, focus: boolean, insistOnKeepingHoverVisible: boolean): void { + const currentAnchorEqualToPreviousHover = this._computer.anchor && this._computer.anchor.equals(anchor); + if (currentAnchorEqualToPreviousHover) { + return; + } + this._hoverOperation.cancel(); + this._computer.anchor = anchor; + this._computer.shouldFocus = focus; + this._computer.source = source; + this._computer.insistOnKeepingHoverVisible = insistOnKeepingHoverVisible; + this._hoverOperation.start(mode); + } + + private _setCurrentResult(hoverResult: HoverResult | null): void { + let currentHoverResult = hoverResult; + const currentResultEqualToPreviousResult = this._currentResult === currentHoverResult; + if (currentResultEqualToPreviousResult) { + return; + } + const currentHoverResultIsEmpty = currentHoverResult && currentHoverResult.hoverParts.length === 0; + if (currentHoverResultIsEmpty) { + currentHoverResult = null; + } + this._currentResult = currentHoverResult; + if (this._currentResult) { + this._showHover(this._currentResult); + } else { + this._hideHover(); + } + } + + private _addLoadingMessage(result: IHoverPart[]): IHoverPart[] { + if (!this._computer.anchor) { + return result; + } + for (const participant of this._participants) { + if (!participant.createLoadingMessage) { + continue; + } + const loadingMessage = participant.createLoadingMessage(this._computer.anchor); + if (!loadingMessage) { + continue; + } + return result.slice(0).concat([loadingMessage]); + } + return result; + } + + private _withResult(hoverResult: HoverResult): void { + const previousHoverIsVisibleWithCompleteResult = this._contentHoverWidget.position && this._currentResult && this._currentResult.isComplete; + if (!previousHoverIsVisibleWithCompleteResult) { + this._setCurrentResult(hoverResult); + } + // The hover is visible with a previous complete result. + const isCurrentHoverResultComplete = hoverResult.isComplete; + if (!isCurrentHoverResultComplete) { + // Instead of rendering the new partial result, we wait for the result to be complete. + return; + } + const currentHoverResultIsEmpty = hoverResult.hoverParts.length === 0; + const insistOnKeepingPreviousHoverVisible = this._computer.insistOnKeepingHoverVisible; + const shouldKeepPreviousHoverVisible = currentHoverResultIsEmpty && insistOnKeepingPreviousHoverVisible; + if (shouldKeepPreviousHoverVisible) { + // The hover would now hide normally, so we'll keep the previous messages + return; + } + this._setCurrentResult(hoverResult); + } + + private _showHover(hoverResult: HoverResult): void { + const context = this._getHoverContext(); + this._renderedContentHover = new RenderedContentHover(this._editor, hoverResult, this._participants, this._computer, context, this._keybindingService); + if (this._renderedContentHover.domNodeHasChildren) { + this._contentHoverWidget.show(this._renderedContentHover); + } else { + this._renderedContentHover.dispose(); + } + } + + private _hideHover(): void { + this._contentHoverWidget.hide(); + } + + private _getHoverContext(): IEditorHoverContext { + const hide = () => { + this.hide(); + }; + const onContentsChanged = () => { + this._onContentsChanged.fire(); + this._contentHoverWidget.onContentsChanged(); + }; + const setMinimumDimensions = (dimensions: dom.Dimension) => { + this._contentHoverWidget.setMinimumDimensions(dimensions); + }; + return { hide, onContentsChanged, setMinimumDimensions }; + } + + + public showsOrWillShow(mouseEvent: IEditorMouseEvent): boolean { + const isContentWidgetResizing = this._contentHoverWidget.isResizing; + if (isContentWidgetResizing) { + return true; + } + const anchorCandidates: HoverAnchor[] = this._findHoverAnchorCandidates(mouseEvent); + const anchorCandidatesExist = anchorCandidates.length > 0; + if (!anchorCandidatesExist) { + return this._startShowingOrUpdateHover(null, HoverStartMode.Delayed, HoverStartSource.Mouse, false, mouseEvent); + } + const anchor = anchorCandidates[0]; + return this._startShowingOrUpdateHover(anchor, HoverStartMode.Delayed, HoverStartSource.Mouse, false, mouseEvent); + } + + private _findHoverAnchorCandidates(mouseEvent: IEditorMouseEvent): HoverAnchor[] { + const anchorCandidates: HoverAnchor[] = []; + for (const participant of this._participants) { + if (!participant.suggestHoverAnchor) { + continue; + } + const anchor = participant.suggestHoverAnchor(mouseEvent); + if (!anchor) { + continue; + } + anchorCandidates.push(anchor); + } + const target = mouseEvent.target; + switch (target.type) { + case MouseTargetType.CONTENT_TEXT: { + anchorCandidates.push(new HoverRangeAnchor(0, target.range, mouseEvent.event.posx, mouseEvent.event.posy)); + break; + } + case MouseTargetType.CONTENT_EMPTY: { + const epsilon = this._editor.getOption(EditorOption.fontInfo).typicalHalfwidthCharacterWidth / 2; + // Let hover kick in even when the mouse is technically in the empty area after a line, given the distance is small enough + const mouseIsWithinLinesAndCloseToHover = !target.detail.isAfterLines + && typeof target.detail.horizontalDistanceToText === 'number' + && target.detail.horizontalDistanceToText < epsilon; + if (!mouseIsWithinLinesAndCloseToHover) { + break; + } + anchorCandidates.push(new HoverRangeAnchor(0, target.range, mouseEvent.event.posx, mouseEvent.event.posy)); + break; + } + } + anchorCandidates.sort((a, b) => b.priority - a.priority); + return anchorCandidates; + } + + private _onMouseLeave(e: MouseEvent): void { + const editorDomNode = this._editor.getDomNode(); + const isMousePositionOutsideOfEditor = !editorDomNode || !isMousePositionWithinElement(editorDomNode, e.x, e.y); + if (isMousePositionOutsideOfEditor) { + this.hide(); + } + } + + public startShowingAtRange(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean): void { + this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, source, focus, null); + } + + public getWidgetContent(): string | undefined { + const node = this._contentHoverWidget.getDomNode(); + if (!node.textContent) { + return undefined; + } + return node.textContent; + } + + public async updateHoverVerbosityLevel(action: HoverVerbosityAction, index: number, focus?: boolean): Promise { + this._renderedContentHover?.updateHoverVerbosityLevel(action, index, focus); + } + + public doesHoverAtIndexSupportVerbosityAction(index: number, action: HoverVerbosityAction): boolean { + return this._renderedContentHover?.doesHoverAtIndexSupportVerbosityAction(index, action) ?? false; + } + + public getAccessibleWidgetContent(): string | undefined { + return this._renderedContentHover?.getAccessibleWidgetContent(); + } + + public getAccessibleWidgetContentAtIndex(index: number): string | undefined { + return this._renderedContentHover?.getAccessibleWidgetContentAtIndex(index); + } + + public focusedHoverPartIndex(): number { + return this._renderedContentHover?.focusedHoverPartIndex ?? -1; + } + + public containsNode(node: Node | null | undefined): boolean { + return (node ? this._contentHoverWidget.getDomNode().contains(node) : false); + } + + public focus(): void { + this._contentHoverWidget.focus(); + } + + public focusHoverPartWithIndex(index: number): void { + this._renderedContentHover?.focusHoverPartWithIndex(index); + } + + public scrollUp(): void { + this._contentHoverWidget.scrollUp(); + } + + public scrollDown(): void { + this._contentHoverWidget.scrollDown(); + } + + public scrollLeft(): void { + this._contentHoverWidget.scrollLeft(); + } + + public scrollRight(): void { + this._contentHoverWidget.scrollRight(); + } + + public pageUp(): void { + this._contentHoverWidget.pageUp(); + } + + public pageDown(): void { + this._contentHoverWidget.pageDown(); + } + + public goToTop(): void { + this._contentHoverWidget.goToTop(); + } + + public goToBottom(): void { + this._contentHoverWidget.goToBottom(); + } + + public hide(): void { + this._computer.anchor = null; + this._hoverOperation.cancel(); + this._setCurrentResult(null); + } + + public getDomNode(): HTMLElement { + return this._contentHoverWidget.getDomNode(); + } + + public get isColorPickerVisible(): boolean { + return this._renderedContentHover?.isColorPickerVisible() ?? false; + } + + public get isVisibleFromKeyboard(): boolean { + return this._contentHoverWidget.isVisibleFromKeyboard; + } + + public get isVisible(): boolean { + return this._contentHoverWidget.isVisible; + } + + public get isFocused(): boolean { + return this._contentHoverWidget.isFocused; + } + + public get isResizing(): boolean { + return this._contentHoverWidget.isResizing; + } + + public get widget() { + return this._contentHoverWidget; + } +} diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/getHover.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/getHover.ts index 608f8a1d..9fc1d78f 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/getHover.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/getHover.ts @@ -34,14 +34,14 @@ async function executeProvider(provider: HoverProvider, ordinal: number, model: return new HoverProviderResult(provider, result, ordinal); } -export function getHoverProviderResultsAsAsyncIterable(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, token: CancellationToken): AsyncIterableObject { - const providers = registry.ordered(model); +export function getHoverProviderResultsAsAsyncIterable(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, token: CancellationToken, recursive = false): AsyncIterableObject { + const providers = registry.ordered(model, recursive); const promises = providers.map((provider, index) => executeProvider(provider, index, model, position, token)); return AsyncIterableObject.fromPromises(promises).coalesce(); } -export function getHoversPromise(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, token: CancellationToken): Promise { - return getHoverProviderResultsAsAsyncIterable(registry, model, position, token).map(item => item.hover).toPromise(); +export function getHoversPromise(registry: LanguageFeatureRegistry, model: ITextModel, position: Position, token: CancellationToken, recursive = false): Promise { + return getHoverProviderResultsAsAsyncIterable(registry, model, position, token, recursive).map(item => item.hover).toPromise(); } registerModelAndPositionCommand('_executeHoverProvider', (accessor, model, position): Promise => { @@ -49,6 +49,11 @@ registerModelAndPositionCommand('_executeHoverProvider', (accessor, model, posit return getHoversPromise(languageFeaturesService.hoverProvider, model, position, CancellationToken.None); }); +registerModelAndPositionCommand('_executeHoverProvider_recursive', (accessor, model, position): Promise => { + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + return getHoversPromise(languageFeaturesService.hoverProvider, model, position, CancellationToken.None, true); +}); + function isValid(result: Hover) { const hasRange = (typeof result.range !== 'undefined'); const hasHtmlContent = typeof result.contents !== 'undefined' && result.contents && result.contents.length > 0; diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts index 2998c0e3..05eff6ac 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; -import { AccessibleViewType, AccessibleViewProviderId, AdvancedContentProvider, IAccessibleViewContentProvider, IAccessibleViewOptions } from 'vs/platform/accessibility/browser/accessibleView'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; +import { AccessibleViewType, AccessibleViewProviderId, AccessibleContentProvider, IAccessibleViewContentProvider, IAccessibleViewOptions } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IHoverService } from 'vs/platform/hover/browser/hover'; @@ -18,15 +18,13 @@ import { Action, IAction } from 'vs/base/common/actions'; import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { labelForHoverVerbosityAction } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant'; namespace HoverAccessibilityHelpNLS { - export const intro = localize('intro', "Focus on the hover widget to cycle through the hover parts with the Tab key."); - export const increaseVerbosity = localize('increaseVerbosity', "- The focused hover part verbosity level can be increased with the Increase Hover Verbosity command.", INCREASE_HOVER_VERBOSITY_ACTION_ID); - export const decreaseVerbosity = localize('decreaseVerbosity', "- The focused hover part verbosity level can be decreased with the Decrease Hover Verbosity command.", DECREASE_HOVER_VERBOSITY_ACTION_ID); - export const hoverContent = localize('contentHover', "The last focused hover content is the following."); + export const increaseVerbosity = localize('increaseVerbosity', '- The focused hover part verbosity level can be increased with the Increase Hover Verbosity command.', ``); + export const decreaseVerbosity = localize('decreaseVerbosity', '- The focused hover part verbosity level can be decreased with the Decrease Hover Verbosity command.', ``); } export class HoverAccessibleView implements IAccessibleViewImplentation { @@ -36,25 +34,18 @@ export class HoverAccessibleView implements IAccessibleViewImplentation { public readonly name = 'hover'; public readonly when = EditorContextKeys.hoverFocused; - private _provider: HoverAccessibleViewProvider | undefined; - - getProvider(accessor: ServicesAccessor): AdvancedContentProvider | undefined { + getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined { const codeEditorService = accessor.get(ICodeEditorService); const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); if (!codeEditor) { throw new Error('No active or focused code editor'); } - const hoverController = HoverController.get(codeEditor); + const hoverController = ContentHoverController.get(codeEditor); if (!hoverController) { return; } const keybindingService = accessor.get(IKeybindingService); - this._provider = accessor.get(IInstantiationService).createInstance(HoverAccessibleViewProvider, keybindingService, codeEditor, hoverController); - return this._provider; - } - - dispose(): void { - this._provider?.dispose(); + return accessor.get(IInstantiationService).createInstance(HoverAccessibleViewProvider, keybindingService, codeEditor, hoverController); } } @@ -65,27 +56,20 @@ export class HoverAccessibilityHelp implements IAccessibleViewImplentation { public readonly type = AccessibleViewType.Help; public readonly when = EditorContextKeys.hoverVisible; - private _provider: HoverAccessibleViewProvider | undefined; - - getProvider(accessor: ServicesAccessor): AdvancedContentProvider | undefined { + getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined { const codeEditorService = accessor.get(ICodeEditorService); const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); if (!codeEditor) { throw new Error('No active or focused code editor'); } - const hoverController = HoverController.get(codeEditor); + const hoverController = ContentHoverController.get(codeEditor); if (!hoverController) { return; } return accessor.get(IInstantiationService).createInstance(HoverAccessibilityHelpProvider, hoverController); } - - dispose(): void { - this._provider?.dispose(); - } } - abstract class BaseHoverAccessibleViewProvider extends Disposable implements IAccessibleViewContentProvider { abstract provideContent(): string; @@ -97,12 +81,9 @@ abstract class BaseHoverAccessibleViewProvider extends Disposable implements IAc private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); public readonly onDidChangeContent: Event = this._onDidChangeContent.event; - protected _markdownHoverFocusedIndex: number = -1; - private _onHoverContentsChanged: IDisposable | undefined; + protected _focusedHoverPartIndex: number = -1; - constructor( - protected readonly _hoverController: HoverController, - ) { + constructor(protected readonly _hoverController: ContentHoverController) { super(); } @@ -111,8 +92,8 @@ abstract class BaseHoverAccessibleViewProvider extends Disposable implements IAc return; } this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = true; - this._markdownHoverFocusedIndex = this._hoverController.focusedMarkdownHoverIndex(); - this._onHoverContentsChanged = this._register(this._hoverController.onHoverContentsChanged(() => { + this._focusedHoverPartIndex = this._hoverController.focusedHoverPartIndex(); + this._register(this._hoverController.onHoverContentsChanged(() => { this._onDidChangeContent.fire(); })); } @@ -121,33 +102,36 @@ abstract class BaseHoverAccessibleViewProvider extends Disposable implements IAc if (!this._hoverController) { return; } - this._markdownHoverFocusedIndex = -1; - this._hoverController.focus(); + if (this._focusedHoverPartIndex === -1) { + this._hoverController.focus(); + } else { + this._hoverController.focusHoverPartWithIndex(this._focusedHoverPartIndex); + } + this._focusedHoverPartIndex = -1; this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = false; - this._onHoverContentsChanged?.dispose(); - } -} - -export class HoverAccessibilityHelpProvider extends BaseHoverAccessibleViewProvider implements IAccessibleViewContentProvider { - - public readonly options: IAccessibleViewOptions = { type: AccessibleViewType.Help }; - - constructor( - hoverController: HoverController, - ) { - super(hoverController); } - provideContent(): string { - return this.provideContentAtIndex(this._markdownHoverFocusedIndex); - } - - provideContentAtIndex(index: number): string { - const content: string[] = []; - content.push(HoverAccessibilityHelpNLS.intro); - content.push(...this._descriptionsOfVerbosityActionsForIndex(index)); - content.push(...this._descriptionOfFocusedMarkdownHoverAtIndex(index)); - return content.join('\n'); + provideContentAtIndex(focusedHoverIndex: number, includeVerbosityActions: boolean): string { + if (focusedHoverIndex !== -1) { + const accessibleContent = this._hoverController.getAccessibleWidgetContentAtIndex(focusedHoverIndex); + if (accessibleContent === undefined) { + return ''; + } + const contents: string[] = []; + if (includeVerbosityActions) { + contents.push(...this._descriptionsOfVerbosityActionsForIndex(focusedHoverIndex)); + } + contents.push(accessibleContent); + return contents.join('\n'); + } else { + const accessibleContent = this._hoverController.getAccessibleWidgetContent(); + if (accessibleContent === undefined) { + return ''; + } + const contents: string[] = []; + contents.push(accessibleContent); + return contents.join('\n'); + } } private _descriptionsOfVerbosityActionsForIndex(index: number): string[] { @@ -164,7 +148,7 @@ export class HoverAccessibilityHelpProvider extends BaseHoverAccessibleViewProvi } private _descriptionOfVerbosityActionForIndex(action: HoverVerbosityAction, index: number): string | undefined { - const isActionSupported = this._hoverController.doesMarkdownHoverAtIndexSupportVerbosityAction(index, action); + const isActionSupported = this._hoverController.doesHoverAtIndexSupportVerbosityAction(index, action); if (!isActionSupported) { return; } @@ -175,15 +159,18 @@ export class HoverAccessibilityHelpProvider extends BaseHoverAccessibleViewProvi return HoverAccessibilityHelpNLS.decreaseVerbosity; } } +} - protected _descriptionOfFocusedMarkdownHoverAtIndex(index: number): string[] { - const content: string[] = []; - const hoverContent = this._hoverController.markdownHoverContentAtIndex(index); - if (hoverContent) { - content.push('\n' + HoverAccessibilityHelpNLS.hoverContent); - content.push('\n' + hoverContent); - } - return content; +export class HoverAccessibilityHelpProvider extends BaseHoverAccessibleViewProvider implements IAccessibleViewContentProvider { + + public readonly options: IAccessibleViewOptions = { type: AccessibleViewType.Help }; + + constructor(hoverController: ContentHoverController) { + super(hoverController); + } + + provideContent(): string { + return this.provideContentAtIndex(this._focusedHoverPartIndex, true); } } @@ -194,15 +181,14 @@ export class HoverAccessibleViewProvider extends BaseHoverAccessibleViewProvider constructor( private readonly _keybindingService: IKeybindingService, private readonly _editor: ICodeEditor, - hoverController: HoverController, + hoverController: ContentHoverController, ) { super(hoverController); this._initializeOptions(this._editor, hoverController); } public provideContent(): string { - const hoverContent = this._hoverController.markdownHoverContentAtIndex(this._markdownHoverFocusedIndex); - return hoverContent.length > 0 ? hoverContent : this._hoverController.getWidgetContent() || HoverAccessibilityHelpNLS.intro; + return this.provideContentAtIndex(this._focusedHoverPartIndex, false); } public get actions(): IAction[] { @@ -229,26 +215,25 @@ export class HoverAccessibleViewProvider extends BaseHoverAccessibleViewProvider break; } const actionLabel = labelForHoverVerbosityAction(this._keybindingService, action); - const actionEnabled = this._hoverController.doesMarkdownHoverAtIndexSupportVerbosityAction(this._markdownHoverFocusedIndex, action); + const actionEnabled = this._hoverController.doesHoverAtIndexSupportVerbosityAction(this._focusedHoverPartIndex, action); return new Action(accessibleActionId, actionLabel, ThemeIcon.asClassName(actionCodicon), actionEnabled, () => { - editor.getAction(actionId)?.run({ index: this._markdownHoverFocusedIndex, focus: false }); + editor.getAction(actionId)?.run({ index: this._focusedHoverPartIndex, focus: false }); }); } - private _initializeOptions(editor: ICodeEditor, hoverController: HoverController): void { + private _initializeOptions(editor: ICodeEditor, hoverController: ContentHoverController): void { const helpProvider = this._register(new HoverAccessibilityHelpProvider(hoverController)); this.options.language = editor.getModel()?.getLanguageId(); - this.options.customHelp = () => { return helpProvider.provideContentAtIndex(this._markdownHoverFocusedIndex); }; + this.options.customHelp = () => { return helpProvider.provideContentAtIndex(this._focusedHoverPartIndex, true); }; } } export class ExtHoverAccessibleView implements IAccessibleViewImplentation { - public readonly type = AccessibleViewType.View; public readonly priority = 90; public readonly name = 'extension-hover'; - getProvider(accessor: ServicesAccessor): AdvancedContentProvider | undefined { + getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined { const contextViewService = accessor.get(IContextViewService); const contextViewElement = contextViewService.getContextViewElement(); const extensionHoverContent = contextViewElement?.textContent ?? undefined; @@ -258,16 +243,14 @@ export class ExtHoverAccessibleView implements IAccessibleViewImplentation { // The accessible view, itself, uses the context view service to display the text. We don't want to read that. return; } - return { - id: AccessibleViewProviderId.Hover, - verbositySettingKey: 'accessibility.verbosity.hover', - provideContent() { return extensionHoverContent; }, - onClose() { + return new AccessibleContentProvider( + AccessibleViewProviderId.Hover, + { language: 'typescript', type: AccessibleViewType.View }, + () => { return extensionHoverContent; }, + () => { hoverService.showAndFocusLastHover(); }, - options: { language: 'typescript', type: AccessibleViewType.View } - }; + 'accessibility.verbosity.hover', + ); } - - dispose() { } } diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverActions.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverActions.ts index 20a5148f..5e48e53d 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverActions.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverActions.ts @@ -14,7 +14,7 @@ import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/go import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; import { HoverVerbosityAction } from 'vs/editor/common/languages'; import * as nls from 'vs/nls'; import 'vs/css!./hover'; @@ -74,7 +74,7 @@ export class ShowOrFocusHoverAction extends EditorAction { return; } - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -128,7 +128,7 @@ export class ShowDefinitionPreviewHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -176,7 +176,7 @@ export class ScrollUpHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -209,7 +209,7 @@ export class ScrollDownHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -242,7 +242,7 @@ export class ScrollLeftHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -275,7 +275,7 @@ export class ScrollRightHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -309,7 +309,7 @@ export class PageUpHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -343,7 +343,7 @@ export class PageDownHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -377,7 +377,7 @@ export class GoToTopHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -412,7 +412,7 @@ export class GoToBottomHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = HoverController.get(editor); + const controller = ContentHoverController.get(editor); if (!controller) { return; } @@ -432,7 +432,12 @@ export class IncreaseHoverVerbosityLevel extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor, args?: { index: number; focus: boolean }): void { - HoverController.get(editor)?.updateMarkdownHoverVerbosityLevel(HoverVerbosityAction.Increase, args?.index, args?.focus); + const hoverController = ContentHoverController.get(editor); + if (!hoverController) { + return; + } + const index = args?.index !== undefined ? args.index : hoverController.focusedHoverPartIndex(); + hoverController.updateHoverVerbosityLevel(HoverVerbosityAction.Increase, index, args?.focus); } } @@ -448,6 +453,11 @@ export class DecreaseHoverVerbosityLevel extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor, args?: { index: number; focus: boolean }): void { - HoverController.get(editor)?.updateMarkdownHoverVerbosityLevel(HoverVerbosityAction.Decrease, args?.index, args?.focus); + const hoverController = ContentHoverController.get(editor); + if (!hoverController) { + return; + } + const index = args?.index !== undefined ? args.index : hoverController.focusedHoverPartIndex(); + ContentHoverController.get(editor)?.updateHoverVerbosityLevel(HoverVerbosityAction.Decrease, index, args?.focus); } } diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverContribution.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverContribution.ts index bf24cdc1..2724c71d 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverContribution.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverContribution.ts @@ -10,12 +10,14 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { MarkdownHoverParticipant } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant'; import { MarkerHoverParticipant } from 'vs/editor/contrib/hover/browser/markerHoverParticipant'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; +import { MarginHoverController } from 'vs/editor/contrib/hover/browser/marginHoverController'; import 'vs/css!./hover'; import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ExtHoverAccessibleView, HoverAccessibilityHelp, HoverAccessibleView } from 'vs/editor/contrib/hover/browser/hoverAccessibleViews'; -registerEditorContribution(HoverController.ID, HoverController, EditorContributionInstantiation.BeforeFirstInteraction); +registerEditorContribution(ContentHoverController.ID, ContentHoverController, EditorContributionInstantiation.BeforeFirstInteraction); +registerEditorContribution(MarginHoverController.ID, MarginHoverController, EditorContributionInstantiation.BeforeFirstInteraction); registerEditorAction(ShowOrFocusHoverAction); registerEditorAction(ShowDefinitionPreviewHoverAction); registerEditorAction(ScrollUpHoverAction); diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverTypes.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverTypes.ts index 68483d2b..4b23a838 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverTypes.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverTypes.ts @@ -94,7 +94,22 @@ export interface IEditorHoverColorPickerWidget { layout(): void; } -export interface IEditorHoverRenderContext { +export interface IEditorHoverContext { + /** + * The contents rendered inside the fragment have been changed, which means that the hover should relayout. + */ + onContentsChanged(): void; + /** + * Set the minimum dimensions of the resizable hover + */ + setMinimumDimensions?(dimensions: Dimension): void; + /** + * Hide the hover. + */ + hide(): void; +} + +export interface IEditorHoverRenderContext extends IEditorHoverContext { /** * The fragment where dom elements should be attached. */ @@ -103,22 +118,38 @@ export interface IEditorHoverRenderContext { * The status bar for actions for this hover. */ readonly statusBar: IEditorHoverStatusBar; +} + +export interface IRenderedHoverPart extends IDisposable { /** - * Set if the hover will render a color picker widget. - */ - setColorPicker(widget: IEditorHoverColorPickerWidget): void; - /** - * The contents rendered inside the fragment have been changed, which means that the hover should relayout. + * The rendered hover part. */ - onContentsChanged(): void; + hoverPart: T; /** - * Set the minimum dimensions of the resizable hover + * The HTML element containing the hover part. */ - setMinimumDimensions?(dimensions: Dimension): void; + hoverElement: HTMLElement; +} + +export interface IRenderedHoverParts extends IDisposable { /** - * Hide the hover. + * Array of rendered hover parts. */ - hide(): void; + renderedHoverParts: IRenderedHoverPart[]; +} + +/** + * Default implementation of IRenderedHoverParts. + */ +export class RenderedHoverParts implements IRenderedHoverParts { + + constructor(public readonly renderedHoverParts: IRenderedHoverPart[]) { } + + dispose() { + for (const part of this.renderedHoverParts) { + part.dispose(); + } + } } export interface IEditorHoverParticipant { @@ -127,7 +158,9 @@ export interface IEditorHoverParticipant { computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): T[]; computeAsync?(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject; createLoadingMessage?(anchor: HoverAnchor): T | null; - renderHoverParts(context: IEditorHoverRenderContext, hoverParts: T[]): IDisposable; + renderHoverParts(context: IEditorHoverRenderContext, hoverParts: T[]): IRenderedHoverParts; + getAccessibleContent(hoverPart: T): string; + handleResize?(): void; } export type IEditorHoverParticipantCtor = IConstructorSignature; diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/hoverUtils.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverUtils.ts new file mode 100644 index 00000000..3f9ab067 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/hoverUtils.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; + +export function isMousePositionWithinElement(element: HTMLElement, posx: number, posy: number): boolean { + const elementRect = dom.getDomNodePagePosition(element); + if (posx < elementRect.left + || posx > elementRect.left + elementRect.width + || posy < elementRect.top + || posy > elementRect.top + elementRect.height) { + return false; + } + return true; +} diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/marginHoverController.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/marginHoverController.ts new file mode 100644 index 00000000..5b71c668 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/marginHoverController.ts @@ -0,0 +1,232 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; +import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IEditorContribution, IScrollEvent } from 'vs/editor/common/editorCommon'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { isMousePositionWithinElement } from 'vs/editor/contrib/hover/browser/hoverUtils'; +import 'vs/css!./hover'; +import { MarginHoverWidget } from 'vs/editor/contrib/hover/browser/marginHoverWidget'; + +// sticky hover widget which doesn't disappear on focus out and such +const _sticky = false + // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this + ; + +interface IHoverSettings { + readonly enabled: boolean; + readonly sticky: boolean; + readonly hidingDelay: number; +} + +interface IHoverState { + mouseDown: boolean; +} + +export class MarginHoverController extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.marginHover'; + + public shouldKeepOpenOnEditorMouseMoveOrLeave: boolean = false; + + private readonly _listenersStore = new DisposableStore(); + + private _glyphWidget: MarginHoverWidget | undefined; + private _mouseMoveEvent: IEditorMouseEvent | undefined; + private _reactToEditorMouseMoveRunner: RunOnceScheduler; + + private _hoverSettings!: IHoverSettings; + private _hoverState: IHoverState = { + mouseDown: false + }; + + constructor( + private readonly _editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { + super(); + this._reactToEditorMouseMoveRunner = this._register( + new RunOnceScheduler( + () => this._reactToEditorMouseMove(this._mouseMoveEvent), 0 + ) + ); + this._hookListeners(); + this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (e.hasChanged(EditorOption.hover)) { + this._unhookListeners(); + this._hookListeners(); + } + })); + } + + static get(editor: ICodeEditor): MarginHoverController | null { + return editor.getContribution(MarginHoverController.ID); + } + + private _hookListeners(): void { + + const hoverOpts = this._editor.getOption(EditorOption.hover); + this._hoverSettings = { + enabled: hoverOpts.enabled, + sticky: hoverOpts.sticky, + hidingDelay: hoverOpts.hidingDelay + }; + + if (hoverOpts.enabled) { + this._listenersStore.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); + this._listenersStore.add(this._editor.onMouseUp(() => this._onEditorMouseUp())); + this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); + this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); + } else { + this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); + this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); + } + + this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e))); + this._listenersStore.add(this._editor.onDidChangeModel(() => { + this._cancelScheduler(); + this._hideWidgets(); + })); + this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler())); + this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); + } + + private _unhookListeners(): void { + this._listenersStore.clear(); + } + + private _cancelScheduler() { + this._mouseMoveEvent = undefined; + this._reactToEditorMouseMoveRunner.cancel(); + } + + private _onEditorScrollChanged(e: IScrollEvent): void { + if (e.scrollTopChanged || e.scrollLeftChanged) { + this._hideWidgets(); + } + } + + private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { + this._hoverState.mouseDown = true; + const shouldNotHideCurrentHoverWidget = this._isMouseOnMarginHoverWidget(mouseEvent); + if (shouldNotHideCurrentHoverWidget) { + return; + } + this._hideWidgets(); + } + + private _isMouseOnMarginHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { + const marginHoverWidgetNode = this._glyphWidget?.getDomNode(); + if (marginHoverWidgetNode) { + return isMousePositionWithinElement(marginHoverWidgetNode, mouseEvent.event.posx, mouseEvent.event.posy); + } + return false; + } + + private _onEditorMouseUp(): void { + this._hoverState.mouseDown = false; + } + + private _onEditorMouseLeave(mouseEvent: IPartialEditorMouseEvent): void { + if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) { + return; + } + + this._cancelScheduler(); + const shouldNotHideCurrentHoverWidget = this._isMouseOnMarginHoverWidget(mouseEvent); + if (shouldNotHideCurrentHoverWidget) { + return; + } + if (_sticky) { + return; + } + this._hideWidgets(); + } + + private _shouldNotRecomputeCurrentHoverWidget(mouseEvent: IEditorMouseEvent): boolean { + const isHoverSticky = this._hoverSettings.sticky; + const isMouseOnMarginHoverWidget = this._isMouseOnMarginHoverWidget(mouseEvent); + return isHoverSticky && isMouseOnMarginHoverWidget; + } + + private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { + if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) { + return; + } + + this._mouseMoveEvent = mouseEvent; + const shouldNotRecomputeCurrentHoverWidget = this._shouldNotRecomputeCurrentHoverWidget(mouseEvent); + if (shouldNotRecomputeCurrentHoverWidget) { + this._reactToEditorMouseMoveRunner.cancel(); + return; + } + this._reactToEditorMouseMove(mouseEvent); + } + + private _reactToEditorMouseMove(mouseEvent: IEditorMouseEvent | undefined): void { + + if (!mouseEvent) { + return; + } + const glyphWidgetShowsOrWillShow = this._tryShowHoverWidget(mouseEvent); + if (glyphWidgetShowsOrWillShow) { + return; + } + if (_sticky) { + return; + } + this._hideWidgets(); + } + + private _tryShowHoverWidget(mouseEvent: IEditorMouseEvent): boolean { + const glyphWidget: IHoverWidget = this._getOrCreateGlyphWidget(); + return glyphWidget.showsOrWillShow(mouseEvent); + } + + private _onKeyDown(e: IKeyboardEvent): void { + if (!this._editor.hasModel()) { + return; + } + if (e.keyCode === KeyCode.Ctrl + || e.keyCode === KeyCode.Alt + || e.keyCode === KeyCode.Meta + || e.keyCode === KeyCode.Shift) { + // Do not hide hover when a modifier key is pressed + return; + } + this._hideWidgets(); + } + + private _hideWidgets(): void { + if (_sticky) { + return; + } + this._glyphWidget?.hide(); + } + + private _getOrCreateGlyphWidget(): MarginHoverWidget { + if (!this._glyphWidget) { + this._glyphWidget = this._instantiationService.createInstance(MarginHoverWidget, this._editor); + } + return this._glyphWidget; + } + + public hideContentHover(): void { + this._hideWidgets(); + } + + public override dispose(): void { + super.dispose(); + this._unhookListeners(); + this._listenersStore.dispose(); + this._glyphWidget?.dispose(); + } +} diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/marginHoverWidget.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/marginHoverWidget.ts index e909575d..e6fb9f4c 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/marginHoverWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/marginHoverWidget.ts @@ -14,6 +14,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; import { IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { IHoverMessage, LaneOrLineNumber, MarginHoverComputer } from 'vs/editor/contrib/hover/browser/marginHoverComputer'; +import { isMousePositionWithinElement } from 'vs/editor/contrib/hover/browser/hoverUtils'; const $ = dom.$; @@ -34,8 +35,8 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget, IHo constructor( editor: ICodeEditor, - languageService: ILanguageService, - openerService: IOpenerService, + @ILanguageService languageService: ILanguageService, + @IOpenerService openerService: IOpenerService, ) { super(); this._editor = editor; @@ -59,7 +60,9 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget, IHo this._updateFont(); } })); - + this._register(dom.addStandardDisposableListener(this._hover.containerDomNode, 'mouseleave', (e) => { + this._onMouseLeave(e); + })); this._editor.addOverlayWidget(this); } @@ -181,4 +184,12 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget, IHo this._hover.containerDomNode.style.left = `${left}px`; this._hover.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`; } + + private _onMouseLeave(e: MouseEvent): void { + const editorDomNode = this._editor.getDomNode(); + const isMousePositionOutsideOfEditor = !editorDomNode || !isMousePositionWithinElement(editorDomNode, e.x, e.y); + if (isMousePositionOutsideOfEditor) { + this.hide(); + } + } } diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index 1adf0bbe..aaeb5796 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { asArray, compareBy, numberComparator } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IMarkdownString, isEmptyMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID } from 'vs/editor/contrib/hover/browser/hoverActionIds'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -15,7 +15,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration, ITextModel } from 'vs/editor/common/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { HoverAnchor, HoverAnchorType, HoverRangeAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { HoverAnchor, HoverAnchorType, HoverRangeAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from 'vs/editor/contrib/hover/browser/hoverTypes'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -33,6 +33,7 @@ import { IHoverService, WorkbenchHoverDelegate } from 'vs/platform/hover/browser import { AsyncIterableObject } from 'vs/base/common/async'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { getHoverProviderResultsAsAsyncIterable } from 'vs/editor/contrib/hover/browser/getHover'; +import { ICommandService } from 'vs/platform/commands/common/commands'; const $ = dom.$; const increaseHoverVerbosityIcon = registerIcon('hover-increase-verbosity', Codicon.add, nls.localize('increaseHoverVerbosity', 'Icon for increaseing hover verbosity.')); @@ -90,6 +91,7 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant { this._renderedHoverParts = new MarkdownRenderedHoverParts( hoverParts, context.fragment, + this, this._editor, this._languageService, this._openerService, + this._commandService, this._keybindingService, this._hoverService, this._configurationService, @@ -191,55 +195,65 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant { + return Promise.resolve(this._renderedHoverParts?.updateMarkdownHoverPartVerbosityLevel(action, index, focus)); } } -interface RenderedHoverPart { - renderedMarkdown: HTMLElement; - disposables: DisposableStore; - hoverSource?: HoverSource; +class RenderedMarkdownHoverPart implements IRenderedHoverPart { + + constructor( + public readonly hoverPart: MarkdownHover, + public readonly hoverElement: HTMLElement, + public readonly disposables: DisposableStore, + ) { } + + get hoverAccessibleContent(): string { + return this.hoverElement.innerText.trim(); + } + + dispose(): void { + this.disposables.dispose(); + } } -class MarkdownRenderedHoverParts extends Disposable { +class MarkdownRenderedHoverParts implements IRenderedHoverParts { + + public renderedHoverParts: RenderedMarkdownHoverPart[]; - private _renderedHoverParts: RenderedHoverPart[]; - private _focusedHoverPartIndex: number = -1; private _ongoingHoverOperations: Map = new Map(); + private readonly _disposables = new DisposableStore(); + constructor( - hoverParts: MarkdownHover[], // we own! + hoverParts: MarkdownHover[], hoverPartsContainer: DocumentFragment, + private readonly _hoverParticipant: MarkdownHoverParticipant, private readonly _editor: ICodeEditor, private readonly _languageService: ILanguageService, private readonly _openerService: IOpenerService, + private readonly _commandService: ICommandService, private readonly _keybindingService: IKeybindingService, private readonly _hoverService: IHoverService, private readonly _configurationService: IConfigurationService, private readonly _onFinishedRendering: () => void, ) { - super(); - this._renderedHoverParts = this._renderHoverParts(hoverParts, hoverPartsContainer, this._onFinishedRendering); - this._register(toDisposable(() => { - this._renderedHoverParts.forEach(renderedHoverPart => { - renderedHoverPart.disposables.dispose(); + this.renderedHoverParts = this._renderHoverParts(hoverParts, hoverPartsContainer, this._onFinishedRendering); + this._disposables.add(toDisposable(() => { + this.renderedHoverParts.forEach(renderedHoverPart => { + renderedHoverPart.dispose(); + }); + this._ongoingHoverOperations.forEach(operation => { + operation.tokenSource.dispose(true); }); - })); - this._register(toDisposable(() => { - this._ongoingHoverOperations.forEach(operation => { operation.tokenSource.dispose(true); }); })); } @@ -247,75 +261,57 @@ class MarkdownRenderedHoverParts extends Disposable { hoverParts: MarkdownHover[], hoverPartsContainer: DocumentFragment, onFinishedRendering: () => void, - ): RenderedHoverPart[] { + ): RenderedMarkdownHoverPart[] { hoverParts.sort(compareBy(hover => hover.ordinal, numberComparator)); - return hoverParts.map((hoverPart, hoverIndex) => { - const renderedHoverPart = this._renderHoverPart( - hoverIndex, - hoverPart.contents, - hoverPart.source, - onFinishedRendering - ); - hoverPartsContainer.appendChild(renderedHoverPart.renderedMarkdown); + return hoverParts.map(hoverPart => { + const renderedHoverPart = this._renderHoverPart(hoverPart, onFinishedRendering); + hoverPartsContainer.appendChild(renderedHoverPart.hoverElement); return renderedHoverPart; }); } private _renderHoverPart( - hoverPartIndex: number, - hoverContents: IMarkdownString[], - hoverSource: HoverSource | undefined, + hoverPart: MarkdownHover, onFinishedRendering: () => void - ): RenderedHoverPart { + ): RenderedMarkdownHoverPart { - const { renderedMarkdown, disposables } = this._renderMarkdownContent(hoverContents, onFinishedRendering); + const renderedMarkdownPart = this._renderMarkdownHover(hoverPart, onFinishedRendering); + const renderedMarkdownElement = renderedMarkdownPart.hoverElement; + const hoverSource = hoverPart.source; + const disposables = new DisposableStore(); + disposables.add(renderedMarkdownPart); if (!hoverSource) { - return { renderedMarkdown, disposables }; + return new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables); } const canIncreaseVerbosity = hoverSource.supportsVerbosityAction(HoverVerbosityAction.Increase); const canDecreaseVerbosity = hoverSource.supportsVerbosityAction(HoverVerbosityAction.Decrease); if (!canIncreaseVerbosity && !canDecreaseVerbosity) { - return { renderedMarkdown, disposables, hoverSource }; + return new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables); } const actionsContainer = $('div.verbosity-actions'); - renderedMarkdown.prepend(actionsContainer); + renderedMarkdownElement.prepend(actionsContainer); disposables.add(this._renderHoverExpansionAction(actionsContainer, HoverVerbosityAction.Increase, canIncreaseVerbosity)); disposables.add(this._renderHoverExpansionAction(actionsContainer, HoverVerbosityAction.Decrease, canDecreaseVerbosity)); - - this._register(dom.addDisposableListener(renderedMarkdown, dom.EventType.FOCUS_IN, (event: Event) => { - event.stopPropagation(); - this._focusedHoverPartIndex = hoverPartIndex; - })); - this._register(dom.addDisposableListener(renderedMarkdown, dom.EventType.FOCUS_OUT, (event: Event) => { - event.stopPropagation(); - this._focusedHoverPartIndex = -1; - })); - return { renderedMarkdown, disposables, hoverSource }; + return new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables); } - private _renderMarkdownContent( - markdownContent: IMarkdownString[], + private _renderMarkdownHover( + markdownHover: MarkdownHover, onFinishedRendering: () => void - ): RenderedHoverPart { - const renderedMarkdown = $('div.hover-row'); - renderedMarkdown.tabIndex = 0; - const renderedMarkdownContents = $('div.hover-row-contents'); - renderedMarkdown.appendChild(renderedMarkdownContents); - const disposables = new DisposableStore(); - disposables.add(renderMarkdownInContainer( + ): IRenderedHoverPart { + const renderedMarkdownHover = renderMarkdownInContainer( this._editor, - renderedMarkdownContents, - markdownContent, + markdownHover, this._languageService, this._openerService, onFinishedRendering, - )); - return { renderedMarkdown, disposables }; + ); + return renderedMarkdownHover; } private _renderHoverExpansionAction(container: HTMLElement, action: HoverVerbosityAction, actionEnabled: boolean): DisposableStore { @@ -324,59 +320,74 @@ class MarkdownRenderedHoverParts extends Disposable { const actionElement = dom.append(container, $(ThemeIcon.asCSSSelector(isActionIncrease ? increaseHoverVerbosityIcon : decreaseHoverVerbosityIcon))); actionElement.tabIndex = 0; const hoverDelegate = new WorkbenchHoverDelegate('mouse', false, { target: container, position: { hoverPosition: HoverPosition.LEFT } }, this._configurationService, this._hoverService); - store.add(this._hoverService.setupUpdatableHover(hoverDelegate, actionElement, labelForHoverVerbosityAction(this._keybindingService, action))); + store.add(this._hoverService.setupManagedHover(hoverDelegate, actionElement, labelForHoverVerbosityAction(this._keybindingService, action))); if (!actionEnabled) { actionElement.classList.add('disabled'); return store; } actionElement.classList.add('enabled'); - const actionFunction = () => this.updateMarkdownHoverPartVerbosityLevel(action); + const actionFunction = () => this._commandService.executeCommand(action === HoverVerbosityAction.Increase ? INCREASE_HOVER_VERBOSITY_ACTION_ID : DECREASE_HOVER_VERBOSITY_ACTION_ID); store.add(new ClickAction(actionElement, actionFunction)); store.add(new KeyDownAction(actionElement, actionFunction, [KeyCode.Enter, KeyCode.Space])); return store; } - public async updateMarkdownHoverPartVerbosityLevel(action: HoverVerbosityAction, index: number = -1, focus: boolean = true): Promise { + public async updateMarkdownHoverPartVerbosityLevel(action: HoverVerbosityAction, index: number, focus: boolean = true): Promise<{ hoverPart: MarkdownHover; hoverElement: HTMLElement } | undefined> { const model = this._editor.getModel(); if (!model) { - return; + return undefined; } - const indexOfInterest = index !== -1 ? index : this._focusedHoverPartIndex; - const hoverRenderedPart = this._getRenderedHoverPartAtIndex(indexOfInterest); - if (!hoverRenderedPart || !hoverRenderedPart.hoverSource?.supportsVerbosityAction(action)) { - return; + const hoverRenderedPart = this._getRenderedHoverPartAtIndex(index); + const hoverSource = hoverRenderedPart?.hoverPart.source; + if (!hoverRenderedPart || !hoverSource?.supportsVerbosityAction(action)) { + return undefined; } - const hoverSource = hoverRenderedPart.hoverSource; const newHover = await this._fetchHover(hoverSource, model, action); if (!newHover) { - return; + return undefined; } const newHoverSource = new HoverSource(newHover, hoverSource.hoverProvider, hoverSource.hoverPosition); - const newHoverRenderedPart = this._renderHoverPart( - indexOfInterest, + const initialHoverPart = hoverRenderedPart.hoverPart; + const newHoverPart = new MarkdownHover( + this._hoverParticipant, + initialHoverPart.range, newHover.contents, - newHoverSource, + initialHoverPart.isBeforeContent, + initialHoverPart.ordinal, + newHoverSource + ); + const newHoverRenderedPart = this._renderHoverPart( + newHoverPart, this._onFinishedRendering ); - this._replaceRenderedHoverPartAtIndex(indexOfInterest, newHoverRenderedPart); + this._replaceRenderedHoverPartAtIndex(index, newHoverRenderedPart, newHoverPart); if (focus) { - this._focusOnHoverPartWithIndex(indexOfInterest); + this._focusOnHoverPartWithIndex(index); } - this._onFinishedRendering(); - } - - public markdownHoverContentAtIndex(index: number): string { - const hoverRenderedPart = this._getRenderedHoverPartAtIndex(index); - return hoverRenderedPart?.renderedMarkdown.innerText ?? ''; + return { + hoverPart: newHoverPart, + hoverElement: newHoverRenderedPart.hoverElement + }; } - public focusedMarkdownHoverIndex(): number { - return this._focusedHoverPartIndex; + public getAccessibleContent(hoverPart: MarkdownHover): string | undefined { + const renderedHoverPartIndex = this.renderedHoverParts.findIndex(renderedHoverPart => renderedHoverPart.hoverPart === hoverPart); + if (renderedHoverPartIndex === -1) { + return undefined; + } + const renderedHoverPart = this._getRenderedHoverPartAtIndex(renderedHoverPartIndex); + if (!renderedHoverPart) { + return undefined; + } + const hoverElementInnerText = renderedHoverPart.hoverElement.innerText; + const accessibleContent = hoverElementInnerText.replace(/[^\S\n\r]+/gu, ' '); + return accessibleContent; } public doesMarkdownHoverAtIndexSupportVerbosityAction(index: number, action: HoverVerbosityAction): boolean { const hoverRenderedPart = this._getRenderedHoverPartAtIndex(index); - if (!hoverRenderedPart || !hoverRenderedPart.hoverSource?.supportsVerbosityAction(action)) { + const hoverSource = hoverRenderedPart?.hoverPart.source; + if (!hoverRenderedPart || !hoverSource?.supportsVerbosityAction(action)) { return false; } return true; @@ -404,76 +415,94 @@ class MarkdownRenderedHoverParts extends Disposable { return hover; } - private _replaceRenderedHoverPartAtIndex(index: number, renderedHoverPart: RenderedHoverPart): void { - if (index >= this._renderHoverParts.length || index < 0) { + private _replaceRenderedHoverPartAtIndex(index: number, renderedHoverPart: RenderedMarkdownHoverPart, hoverPart: MarkdownHover): void { + if (index >= this.renderedHoverParts.length || index < 0) { return; } - const currentRenderedHoverPart = this._renderedHoverParts[index]; - const currentRenderedMarkdown = currentRenderedHoverPart.renderedMarkdown; - currentRenderedMarkdown.replaceWith(renderedHoverPart.renderedMarkdown); - currentRenderedHoverPart.disposables.dispose(); - this._renderedHoverParts[index] = renderedHoverPart; + const currentRenderedHoverPart = this.renderedHoverParts[index]; + const currentRenderedMarkdown = currentRenderedHoverPart.hoverElement; + const renderedMarkdown = renderedHoverPart.hoverElement; + const renderedChildrenElements = Array.from(renderedMarkdown.children); + currentRenderedMarkdown.replaceChildren(...renderedChildrenElements); + const newRenderedHoverPart = new RenderedMarkdownHoverPart( + hoverPart, + currentRenderedMarkdown, + renderedHoverPart.disposables + ); + currentRenderedMarkdown.focus(); + currentRenderedHoverPart.dispose(); + this.renderedHoverParts[index] = newRenderedHoverPart; } private _focusOnHoverPartWithIndex(index: number): void { - this._renderedHoverParts[index].renderedMarkdown.focus(); + this.renderedHoverParts[index].hoverElement.focus(); + } + + private _getRenderedHoverPartAtIndex(index: number): RenderedMarkdownHoverPart | undefined { + return this.renderedHoverParts[index]; } - private _getRenderedHoverPartAtIndex(index: number): RenderedHoverPart | undefined { - return this._renderedHoverParts[index]; + public dispose(): void { + this._disposables.dispose(); } } export function renderMarkdownHovers( context: IEditorHoverRenderContext, - hoverParts: MarkdownHover[], + markdownHovers: MarkdownHover[], editor: ICodeEditor, languageService: ILanguageService, openerService: IOpenerService, -): IDisposable { +): IRenderedHoverParts { // Sort hover parts to keep them stable since they might come in async, out-of-order - hoverParts.sort(compareBy(hover => hover.ordinal, numberComparator)); - - const disposables = new DisposableStore(); - for (const hoverPart of hoverParts) { - disposables.add(renderMarkdownInContainer( + markdownHovers.sort(compareBy(hover => hover.ordinal, numberComparator)); + const renderedHoverParts: IRenderedHoverPart[] = []; + for (const markdownHover of markdownHovers) { + renderedHoverParts.push(renderMarkdownInContainer( editor, - context.fragment, - hoverPart.contents, + markdownHover, languageService, openerService, context.onContentsChanged, )); } - return disposables; + return new RenderedHoverParts(renderedHoverParts); } function renderMarkdownInContainer( editor: ICodeEditor, - container: DocumentFragment | HTMLElement, - markdownStrings: IMarkdownString[], + markdownHover: MarkdownHover, languageService: ILanguageService, openerService: IOpenerService, onFinishedRendering: () => void, -): IDisposable { - const store = new DisposableStore(); - for (const contents of markdownStrings) { - if (isEmptyMarkdownString(contents)) { +): IRenderedHoverPart { + const disposables = new DisposableStore(); + const renderedMarkdown = $('div.hover-row'); + const renderedMarkdownContents = $('div.hover-row-contents'); + renderedMarkdown.appendChild(renderedMarkdownContents); + const markdownStrings = markdownHover.contents; + for (const markdownString of markdownStrings) { + if (isEmptyMarkdownString(markdownString)) { continue; } const markdownHoverElement = $('div.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); - const renderer = store.add(new MarkdownRenderer({ editor }, languageService, openerService)); - store.add(renderer.onDidRenderAsync(() => { + const renderer = disposables.add(new MarkdownRenderer({ editor }, languageService, openerService)); + disposables.add(renderer.onDidRenderAsync(() => { hoverContentsElement.className = 'hover-contents code-hover-contents'; onFinishedRendering(); })); - const renderedContents = store.add(renderer.render(contents)); + const renderedContents = disposables.add(renderer.render(markdownString)); hoverContentsElement.appendChild(renderedContents.element); - container.appendChild(markdownHoverElement); + renderedMarkdownContents.appendChild(markdownHoverElement); } - return store; + const renderedHoverPart: IRenderedHoverPart = { + hoverPart: markdownHover, + hoverElement: renderedMarkdown, + dispose() { disposables.dispose(); } + }; + return renderedHoverPart; } export function labelForHoverVerbosityAction(keybindingService: IKeybindingService, action: HoverVerbosityAction): string { diff --git a/patched-vscode/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts b/patched-vscode/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts index 3ed0b3fa..033b85ce 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/resources'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -20,7 +20,7 @@ import { getCodeActions, quickFixCommandId } from 'vs/editor/contrib/codeAction/ import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; import { CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types'; import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/browser/gotoError'; -import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from 'vs/editor/contrib/hover/browser/hoverTypes'; import * as nls from 'vs/nls'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IMarker, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; @@ -90,20 +90,29 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant { if (!hoverParts.length) { - return Disposable.None; + return new RenderedHoverParts([]); } const disposables = new DisposableStore(); - hoverParts.forEach(msg => context.fragment.appendChild(this.renderMarkerHover(msg, disposables))); + const renderedHoverParts: IRenderedHoverPart[] = []; + hoverParts.forEach(hoverPart => { + const renderedMarkerHover = this._renderMarkerHover(hoverPart); + context.fragment.appendChild(renderedMarkerHover.hoverElement); + renderedHoverParts.push(renderedMarkerHover); + }); const markerHoverForStatusbar = hoverParts.length === 1 ? hoverParts[0] : hoverParts.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))[0]; this.renderMarkerStatusbar(context, markerHoverForStatusbar, disposables); - return disposables; + return new RenderedHoverParts(renderedHoverParts); + } + + public getAccessibleContent(hoverPart: MarkerHover): string { + return hoverPart.marker.message; } - private renderMarkerHover(markerHover: MarkerHover, disposables: DisposableStore): HTMLElement { + private _renderMarkerHover(markerHover: MarkerHover): IRenderedHoverPart { + const disposables: DisposableStore = new DisposableStore(); const hoverElement = $('div.hover-row'); - hoverElement.tabIndex = 0; const markerElement = dom.append(hoverElement, $('div.marker.hover-contents')); const { source, message, code, relatedInformation } = markerHover.marker; @@ -154,9 +163,10 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant{ selection: { startLineNumber, startColumn } } + editorOptions }).catch(onUnexpectedError); } })); @@ -166,7 +176,12 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant = { + hoverPart: markerHover, + hoverElement, + dispose: () => disposables.dispose() + }; + return renderedHoverPart; } private renderMarkerStatusbar(context: IEditorHoverRenderContext, markerHover: MarkerHover, disposables: DisposableStore): void { diff --git a/patched-vscode/src/vs/editor/contrib/hover/test/browser/contentHover.test.ts b/patched-vscode/src/vs/editor/contrib/hover/test/browser/contentHover.test.ts index b41a164e..65762f59 100644 --- a/patched-vscode/src/vs/editor/contrib/hover/test/browser/contentHover.test.ts +++ b/patched-vscode/src/vs/editor/contrib/hover/test/browser/contentHover.test.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController'; +import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered'; import { IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { TestCodeEditorInstantiationOptions, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; @@ -18,7 +18,7 @@ suite('Content Hover', () => { test('issue #151235: Gitlens hover shows up in the wrong place', () => { const text = 'just some text'; withTestCodeEditor(text, {}, (editor) => { - const actual = ContentHoverController.computeHoverRanges( + const actual = RenderedContentHover.computeHoverPositions( editor, new Range(5, 5, 5, 5), [{ range: new Range(4, 1, 5, 6) }] @@ -27,8 +27,7 @@ suite('Content Hover', () => { actual, { showAtPosition: new Position(5, 5), - showAtSecondaryPosition: new Position(5, 5), - highlightRange: new Range(4, 1, 5, 6) + showAtSecondaryPosition: new Position(5, 5) } ); }); @@ -38,7 +37,7 @@ suite('Content Hover', () => { const text = 'just some text'; const opts: TestCodeEditorInstantiationOptions = { wordWrap: 'wordWrapColumn', wordWrapColumn: 6 }; withTestCodeEditor(text, opts, (editor) => { - const actual = ContentHoverController.computeHoverRanges( + const actual = RenderedContentHover.computeHoverPositions( editor, new Range(1, 8, 1, 8), [{ range: new Range(1, 1, 1, 15) }] @@ -47,8 +46,7 @@ suite('Content Hover', () => { actual, { showAtPosition: new Position(1, 8), - showAtSecondaryPosition: new Position(1, 6), - highlightRange: new Range(1, 1, 1, 15) + showAtSecondaryPosition: new Position(1, 6) } ); }); diff --git a/patched-vscode/src/vs/editor/contrib/indentation/browser/indentation.ts b/patched-vscode/src/vs/editor/contrib/indentation/browser/indentation.ts index f7d859c5..84760fdb 100644 --- a/patched-vscode/src/vs/editor/contrib/indentation/browser/indentation.ts +++ b/patched-vscode/src/vs/editor/contrib/indentation/browser/indentation.ts @@ -418,6 +418,10 @@ export class AutoIndentOnPaste implements IEditorContribution { if (!model) { return; } + const containsOnlyWhitespace = this.rangeContainsOnlyWhitespaceCharacters(model, range); + if (containsOnlyWhitespace) { + return; + } if (isStartOrEndInString(model, range)) { return; } @@ -466,7 +470,7 @@ export class AutoIndentOnPaste implements IEditorContribution { range: new Range(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), text: newIndent }); - firstLineText = newIndent + firstLineText.substr(oldIndentation.length); + firstLineText = newIndent + firstLineText.substring(oldIndentation.length); } else { const indentMetadata = getIndentMetadata(model, startLineNumber, this._languageConfigurationService); @@ -546,6 +550,35 @@ export class AutoIndentOnPaste implements IEditorContribution { } } + private rangeContainsOnlyWhitespaceCharacters(model: ITextModel, range: Range): boolean { + const lineContainsOnlyWhitespace = (content: string): boolean => { + return content.trim().length === 0; + }; + let containsOnlyWhitespace: boolean = true; + if (range.startLineNumber === range.endLineNumber) { + const lineContent = model.getLineContent(range.startLineNumber); + const linePart = lineContent.substring(range.startColumn - 1, range.endColumn - 1); + containsOnlyWhitespace = lineContainsOnlyWhitespace(linePart); + } else { + for (let i = range.startLineNumber; i <= range.endLineNumber; i++) { + const lineContent = model.getLineContent(i); + if (i === range.startLineNumber) { + const linePart = lineContent.substring(range.startColumn - 1); + containsOnlyWhitespace = lineContainsOnlyWhitespace(linePart); + } else if (i === range.endLineNumber) { + const linePart = lineContent.substring(0, range.endColumn - 1); + containsOnlyWhitespace = lineContainsOnlyWhitespace(linePart); + } else { + containsOnlyWhitespace = model.getLineFirstNonWhitespaceColumn(i) === 0; + } + if (!containsOnlyWhitespace) { + break; + } + } + } + return containsOnlyWhitespace; + } + private shouldIgnoreLine(model: ITextModel, lineNumber: number): boolean { model.tokenization.forceTokenization(lineNumber); const nonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(lineNumber); diff --git a/patched-vscode/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/patched-vscode/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index d6adda56..2beed4f8 100644 --- a/patched-vscode/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/patched-vscode/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; @@ -22,7 +22,10 @@ import { goIndentationRules, htmlIndentationRules, javascriptIndentationRules, l import { cppOnEnterRules, htmlOnEnterRules, javascriptOnEnterRules, phpOnEnterRules } from 'vs/editor/test/common/modes/supports/onEnterRules'; import { TypeOperations } from 'vs/editor/common/cursor/cursorTypeOperations'; import { cppBracketRules, goBracketRules, htmlBracketRules, latexBracketRules, luaBracketRules, phpBracketRules, rubyBracketRules, typescriptBracketRules, vbBracketRules } from 'vs/editor/test/common/modes/supports/bracketRules'; -import { latexAutoClosingPairsRules } from 'vs/editor/test/common/modes/supports/autoClosingPairsRules'; +import { javascriptAutoClosingPairsRules, latexAutoClosingPairsRules } from 'vs/editor/test/common/modes/supports/autoClosingPairsRules'; +import { LanguageService } from 'vs/editor/common/services/languageService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; export enum Language { TypeScript = 'ts-test', @@ -44,16 +47,11 @@ function testIndentationToTabsCommand(lines: string[], selection: Selection, tab testCommand(lines, null, selection, (accessor, sel) => new IndentationToTabsCommand(sel, tabSize), expectedLines, expectedSelection); } -export function registerLanguage(instantiationService: TestInstantiationService, language: Language): IDisposable { - const disposables = new DisposableStore(); - const languageService = instantiationService.get(ILanguageService); - disposables.add(registerLanguageConfiguration(instantiationService, language)); - disposables.add(languageService.registerLanguage({ id: language })); - return disposables; +export function registerLanguage(languageService: ILanguageService, language: Language): IDisposable { + return languageService.registerLanguage({ id: language }); } -export function registerLanguageConfiguration(instantiationService: TestInstantiationService, language: Language): IDisposable { - const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); +export function registerLanguageConfiguration(languageConfigurationService: ILanguageConfigurationService, language: Language): IDisposable { switch (language) { case Language.TypeScript: return languageConfigurationService.register(language, { @@ -62,6 +60,7 @@ export function registerLanguageConfiguration(instantiationService: TestInstanti lineComment: '//', blockComment: ['/*', '*/'] }, + autoClosingPairs: javascriptAutoClosingPairsRules, indentationRules: javascriptIndentationRules, onEnterRules: javascriptOnEnterRules }); @@ -317,9 +316,20 @@ suite('Indent With Tab - TypeScript/JavaScript', () => { const languageId = Language.TypeScript; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -343,9 +353,7 @@ suite('Indent With Tab - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(1, 1, 3, 5)); editor.executeCommands('editor.action.indentLines', TypeOperations.indent(viewModel.cursorConfig, editor.getModel(), editor.getSelections())); assert.strictEqual(model.getValue(), [ @@ -369,9 +377,7 @@ suite('Indent With Tab - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(1, 1, 5, 2)); editor.executeCommands('editor.action.indentLines', TypeOperations.indent(viewModel.cursorConfig, editor.getModel(), editor.getSelections())); assert.strictEqual(model.getValue(), [ @@ -389,9 +395,20 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { const languageId = Language.TypeScript; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -405,7 +422,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { const model = createTextModel("", languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { const pasteText = [ '/**', ' * JSDoc', @@ -439,7 +456,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { { startIndex: 15, standardTokenType: StandardTokenType.Other }, ] ]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); viewModel.paste(pasteText, true, undefined, 'keyboard'); @@ -453,7 +469,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { const model = createTextModel("", languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { // no need for tokenization because there are no comments const pasteText = [ @@ -470,7 +486,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { '}' ].join('\n'); - disposables.add(registerLanguage(instantiationService, languageId)); const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); viewModel.paste(pasteText, true, undefined, 'keyboard'); autoIndentOnPasteController.trigger(new Range(1, 1, 11, 2)); @@ -488,8 +503,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(2, 6, 2, 6)); const text = ', null'; viewModel.paste(text, true, undefined, 'keyboard'); @@ -516,8 +530,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(5, 24, 5, 34)); const text = 'IMacLinuxKeyMapping'; viewModel.paste(text, true, undefined, 'keyboard'); @@ -541,8 +554,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { const model = createTextModel('', languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { const text = [ '/*----------------', ' * Copyright (c) ', @@ -569,7 +581,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { const model = createTextModel(initialText, languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [ { startIndex: 0, standardTokenType: StandardTokenType.Other }, @@ -582,7 +594,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { { startIndex: 0, standardTokenType: StandardTokenType.String }, ] ]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); editor.setSelection(new Selection(2, 10, 2, 15)); @@ -602,7 +613,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { const model = createTextModel("", languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { const text = [ '/**', ' * @typedef {', @@ -634,7 +645,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { { startIndex: 3, standardTokenType: StandardTokenType.Other }, ] ]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); viewModel.paste(text, true, undefined, 'keyboard'); @@ -654,7 +664,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(2, 1, 2, 1)); const text = [ '() => {', @@ -662,7 +672,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { '}', '' ].join('\n'); - disposables.add(registerLanguage(instantiationService, languageId)); const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); viewModel.paste(text, true, undefined, 'keyboard'); autoIndentOnPasteController.trigger(new Range(2, 1, 5, 1)); @@ -696,7 +705,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(2, 5, 2, 5)); const text = [ '() => {', @@ -704,7 +713,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { '}', ' ' ].join('\n'); - disposables.add(registerLanguage(instantiationService, languageId)); const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); viewModel.paste(text, true, undefined, 'keyboard'); // todo@aiday-mar, make sure range is correct, and make test work as in real life @@ -727,7 +735,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { const model = createTextModel('', languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(2, 5, 2, 5)); const text = [ 'function makeSub(a,b) {', @@ -735,7 +743,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { 'return subsent;', '}', ].join('\n'); - disposables.add(registerLanguage(instantiationService, languageId)); const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); viewModel.paste(text, true, undefined, 'keyboard'); // todo@aiday-mar, make sure range is correct, and make test work as in real life @@ -760,7 +767,7 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: 'full', serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [ { startIndex: 0, standardTokenType: StandardTokenType.Other }, @@ -791,7 +798,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { { startIndex: 0, standardTokenType: StandardTokenType.Other }, { startIndex: 1, standardTokenType: StandardTokenType.Other }] ]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); editor.setSelection(new Selection(2, 1, 2, 1)); @@ -799,7 +805,6 @@ suite('Auto Indent On Paste - TypeScript/JavaScript', () => { '// comment', 'const foo = 42', ].join('\n'); - disposables.add(registerLanguage(instantiationService, languageId)); const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); viewModel.paste(text, true, undefined, 'keyboard'); autoIndentOnPasteController.trigger(new Range(2, 1, 3, 15)); @@ -817,9 +822,20 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { const languageId = Language.TypeScript; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -837,8 +853,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { const model = createTextModel("", languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { viewModel.type('const add1 = (n) =>'); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -859,8 +874,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 9, 3, 9)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -879,10 +893,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { const model = createTextModel("", languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); - + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { viewModel.type([ 'const add1 = (n) =>', ' n + 1;', @@ -908,9 +919,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 1, 3, 1)); viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -939,8 +948,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: 'advanced', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(7, 6, 7, 6)); viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), @@ -970,8 +978,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: 'advanced', serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 4, 1, 4)); viewModel.type('\n', 'keyboard'); assert.strictEqual(model.getValue(), @@ -996,8 +1003,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 12, 2, 12)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1020,9 +1026,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 19, 2, 19)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1055,9 +1059,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [{ startIndex: 0, standardTokenType: StandardTokenType.Comment }], [{ startIndex: 0, standardTokenType: StandardTokenType.Comment }], @@ -1075,6 +1077,38 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { }); }); + test('issue #209802: allman style braces in JavaScript', () => { + + // https://github.com/microsoft/vscode/issues/209802 + + const model = createTextModel([ + 'if (/*condition*/)', + ].join('\n'), languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { + editor.setSelection(new Selection(1, 19, 1, 19)); + viewModel.type("\n", 'keyboard'); + assert.strictEqual(model.getValue(), [ + 'if (/*condition*/)', + ' ' + ].join('\n')); + viewModel.type("{", 'keyboard'); + assert.strictEqual(model.getValue(), [ + 'if (/*condition*/)', + '{}' + ].join('\n')); + editor.setSelection(new Selection(2, 2, 2, 2)); + viewModel.type("\n", 'keyboard'); + assert.strictEqual(model.getValue(), [ + 'if (/*condition*/)', + '{', + ' ', + '}' + ].join('\n')); + }); + }); + // Failing tests... test.skip('issue #43244: indent after equal sign is detected', () => { @@ -1090,8 +1124,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 14, 1, 14)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1114,8 +1147,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 7, 2, 7)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1138,8 +1170,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 7, 2, 7)); viewModel.type("\n", 'keyboard'); viewModel.type("."); @@ -1163,8 +1194,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 24, 2, 24)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1189,8 +1219,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 5, 3, 5)); viewModel.type("."); assert.strictEqual(model.getValue(), [ @@ -1213,8 +1242,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 25, 2, 25)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1233,10 +1261,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { const model = createTextModel('function foo() {}', languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); - + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 17, 1, 17)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1267,9 +1292,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 14, 3, 14)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1295,9 +1318,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(4, 1, 4, 1)); viewModel.type("}", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1319,16 +1340,13 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 5, 2, 5)); viewModel.type("{}", 'keyboard'); assert.strictEqual(model.getValue(), [ 'if (true)', '{}', ].join('\n')); - editor.setSelection(new Selection(2, 2, 2, 2)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1350,8 +1368,7 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "keep" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "keep", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 5, 2, 5)); viewModel.type("}", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1366,9 +1383,20 @@ suite('Auto Indent On Type - Ruby', () => { const languageId = Language.Ruby; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -1384,10 +1412,7 @@ suite('Auto Indent On Type - Ruby', () => { const model = createTextModel("", languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); - + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { viewModel.type("def foo\n i"); viewModel.type("n", 'keyboard'); assert.strictEqual(model.getValue(), "def foo\n in"); @@ -1412,10 +1437,7 @@ suite('Auto Indent On Type - Ruby', () => { const model = createTextModel("", languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); - + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { viewModel.type("method('#foo') do"); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1430,9 +1452,20 @@ suite('Auto Indent On Type - PHP', () => { const languageId = Language.PHP; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -1448,9 +1481,7 @@ suite('Auto Indent On Type - PHP', () => { const model = createTextModel("preg_replace('{');", languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [ { startIndex: 0, standardTokenType: StandardTokenType.Other }, @@ -1473,9 +1504,20 @@ suite('Auto Indent On Paste - Go', () => { const languageId = Language.Go; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -1500,8 +1542,7 @@ suite('Auto Indent On Paste - Go', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(3, 1, 3, 1)); const text = ' '; const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); @@ -1521,9 +1562,20 @@ suite('Auto Indent On Type - CPP', () => { const languageId = Language.CPP; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -1546,8 +1598,7 @@ suite('Auto Indent On Type - CPP', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 20, 2, 20)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1569,8 +1620,7 @@ suite('Auto Indent On Type - CPP', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 20, 1, 20)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1591,9 +1641,7 @@ suite('Auto Indent On Type - CPP', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "none" }, (editor, viewModel, instantiationService) => { - - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "none", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 3, 2, 3)); viewModel.type("}", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1609,9 +1657,20 @@ suite('Auto Indent On Type - HTML', () => { const languageId = Language.HTML; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -1635,8 +1694,7 @@ suite('Auto Indent On Type - HTML', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 48, 2, 48)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1653,9 +1711,20 @@ suite('Auto Indent On Type - Visual Basic', () => { const languageId = Language.VB; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -1679,8 +1748,7 @@ suite('Auto Indent On Type - Visual Basic', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { editor.setSelection(new Selection(3, 10, 3, 10)); viewModel.type("f", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1697,9 +1765,20 @@ suite('Auto Indent On Type - Latex', () => { const languageId = Language.Latex; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -1722,8 +1801,7 @@ suite('Auto Indent On Type - Latex', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(2, 9, 2, 9)); viewModel.type("{", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1738,9 +1816,20 @@ suite('Auto Indent On Type - Lua', () => { const languageId = Language.Lua; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -1762,8 +1851,7 @@ suite('Auto Indent On Type - Lua', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { - disposables.add(registerLanguage(instantiationService, languageId)); + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => { editor.setSelection(new Selection(1, 28, 1, 28)); viewModel.type("\n", 'keyboard'); assert.strictEqual(model.getValue(), [ @@ -1773,4 +1861,3 @@ suite('Auto Indent On Type - Lua', () => { }); }); }); - diff --git a/patched-vscode/src/vs/editor/contrib/indentation/test/browser/indentationLineProcessor.test.ts b/patched-vscode/src/vs/editor/contrib/indentation/test/browser/indentationLineProcessor.test.ts index 77afbe5c..2004b864 100644 --- a/patched-vscode/src/vs/editor/contrib/indentation/test/browser/indentationLineProcessor.test.ts +++ b/patched-vscode/src/vs/editor/contrib/indentation/test/browser/indentationLineProcessor.test.ts @@ -2,24 +2,39 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IndentationContextProcessor, ProcessedIndentRulesSupport } from 'vs/editor/common/languages/supports/indentationLineProcessor'; -import { Language, registerLanguage, registerTokenizationSupport, StandardTokenTypeData } from 'vs/editor/contrib/indentation/test/browser/indentation.test'; +import { Language, registerLanguage, registerLanguageConfiguration, registerTokenizationSupport, StandardTokenTypeData } from 'vs/editor/contrib/indentation/test/browser/indentation.test'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { Range } from 'vs/editor/common/core/range'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { LanguageService } from 'vs/editor/common/services/languageService'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { ILanguageService } from 'vs/editor/common/languages/language'; suite('Indentation Context Processor - TypeScript/JavaScript', () => { const languageId = Language.TypeScript; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -35,13 +50,12 @@ suite('Indentation Context Processor - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [[ { startIndex: 0, standardTokenType: StandardTokenType.Other }, { startIndex: 16, standardTokenType: StandardTokenType.String }, { startIndex: 28, standardTokenType: StandardTokenType.String } ]]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); const indentationContextProcessor = new IndentationContextProcessor(model, languageConfigurationService); @@ -60,7 +74,7 @@ suite('Indentation Context Processor - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [ { startIndex: 0, standardTokenType: StandardTokenType.Other }, @@ -72,7 +86,6 @@ suite('Indentation Context Processor - TypeScript/JavaScript', () => { { startIndex: 46, standardTokenType: StandardTokenType.Other }, { startIndex: 47, standardTokenType: StandardTokenType.String } ]]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); const indentationContextProcessor = new IndentationContextProcessor(model, languageConfigurationService); @@ -91,7 +104,7 @@ suite('Indentation Context Processor - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [ { startIndex: 0, standardTokenType: StandardTokenType.Other }, @@ -104,7 +117,6 @@ suite('Indentation Context Processor - TypeScript/JavaScript', () => { { startIndex: 44, standardTokenType: StandardTokenType.Other }, ] ]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); const indentationContextProcessor = new IndentationContextProcessor(model, languageConfigurationService); @@ -120,9 +132,20 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => { const languageId = Language.TypeScript; let disposables: DisposableStore; + let serviceCollection: ServiceCollection; setup(() => { disposables = new DisposableStore(); + const languageService = new LanguageService(); + const languageConfigurationService = new TestLanguageConfigurationService(); + disposables.add(languageService); + disposables.add(languageConfigurationService); + disposables.add(registerLanguage(languageService, languageId)); + disposables.add(registerLanguageConfiguration(languageConfigurationService, languageId)); + serviceCollection = new ServiceCollection( + [ILanguageService, languageService], + [ILanguageConfigurationService, languageConfigurationService] + ); }); teardown(() => { @@ -140,7 +163,7 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [ { startIndex: 0, standardTokenType: StandardTokenType.Other } @@ -154,7 +177,6 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => { { startIndex: 17, standardTokenType: StandardTokenType.Comment }, ] ]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); const indentationRulesSupport = languageConfigurationService.getLanguageConfiguration(languageId).indentRulesSupport; @@ -177,13 +199,12 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [{ startIndex: 0, standardTokenType: StandardTokenType.Other }], [{ startIndex: 0, standardTokenType: StandardTokenType.String }], [{ startIndex: 0, standardTokenType: StandardTokenType.Comment }] ]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); const indentationRulesSupport = languageConfigurationService.getLanguageConfiguration(languageId).indentRulesSupport; @@ -206,7 +227,7 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => { ].join('\n'), languageId, {}); disposables.add(model); - withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel, instantiationService) => { const tokens: StandardTokenTypeData[][] = [ [ { startIndex: 0, standardTokenType: StandardTokenType.Other } @@ -220,7 +241,6 @@ suite('Processed Indent Rules Support - TypeScript/JavaScript', () => { { startIndex: 18, standardTokenType: StandardTokenType.RegEx } ] ]; - disposables.add(registerLanguage(instantiationService, languageId)); disposables.add(registerTokenizationSupport(instantiationService, tokens, languageId)); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); const indentationRulesSupport = languageConfigurationService.getLanguageConfiguration(languageId).indentRulesSupport; diff --git a/patched-vscode/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts b/patched-vscode/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts index d49a4bb7..05703e10 100644 --- a/patched-vscode/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts +++ b/patched-vscode/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts @@ -26,6 +26,7 @@ import { asCommandLink } from 'vs/editor/contrib/inlayHints/browser/inlayHints'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { ICommandService } from 'vs/platform/commands/common/commands'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { constructor( @@ -51,8 +52,9 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor @IConfigurationService configurationService: IConfigurationService, @ITextModelService private readonly _resolverService: ITextModelService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @ICommandService commandService: ICommandService ) { - super(editor, languageService, openerService, configurationService, languageFeaturesService, keybindingService, hoverService); + super(editor, languageService, openerService, configurationService, languageFeaturesService, keybindingService, hoverService, commandService); } suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/commandIds.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/commandIds.ts similarity index 100% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/commandIds.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/commandIds.ts diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/commands.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts similarity index 97% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/commands.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts index ddfde908..0c52cfed 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/commands.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts @@ -9,9 +9,9 @@ import { asyncTransaction } from 'vs/base/common/observableInternal/base'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId, inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/commandIds'; -import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; +import { showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId, inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/controller/commandIds'; +import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest'; import * as nls from 'vs/nls'; import { MenuId, Action2 } from 'vs/platform/actions/common/actions'; diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts similarity index 98% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts index fcbcd621..eb881f94 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts @@ -6,7 +6,7 @@ import { IObservable, autorun } from 'vs/base/common/observable'; import { firstNonWhitespaceIndex } from 'vs/base/common/strings'; import { CursorColumns } from 'vs/editor/common/core/cursorColumns'; -import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; +import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel'; import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts similarity index 54% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index c11920c4..89e2ce51 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -3,31 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createStyleSheet2 } from 'vs/base/browser/dom'; +import { createStyleSheetFromObservable } from 'vs/base/browser/domObservable'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { timeout } from 'vs/base/common/async'; import { cancelOnDispose } from 'vs/base/common/cancellation'; -import { itemEquals, itemsEquals } from 'vs/base/common/equals'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, ITransaction, autorun, autorunHandleChanges, constObservable, derived, disposableObservableValue, observableFromEvent, observableSignal, observableValue, transaction, waitForState } from 'vs/base/common/observable'; -import { ISettableObservable, observableValueOpts } from 'vs/base/common/observableInternal/base'; -import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; +import { IObservable, ITransaction, autorun, constObservable, derived, observableFromEvent, observableSignal, observableValue, transaction, waitForState } from 'vs/base/common/observable'; +import { ISettableObservable } from 'vs/base/common/observableInternal/base'; +import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; +import { derivedObservableWithCache, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { isUndefined } from 'vs/base/common/types'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { observableCodeEditor, reactToChange, reactToChangeWithStore } from 'vs/editor/browser/observableCodeEditor'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; -import { inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/commandIds'; -import { GhostTextWidget } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextWidget'; -import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys'; -import { InlineCompletionsHintsWidget, InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget'; -import { InlineCompletionsModel, VersionIdChangeReason } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; -import { SuggestWidgetAdaptor } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; +import { inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/controller/commandIds'; +import { GhostTextView } from 'vs/editor/contrib/inlineCompletions/browser/view/ghostTextView'; +import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys'; +import { InlineCompletionsHintsWidget, InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget'; +import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel'; +import { SuggestWidgetAdaptor } from 'vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdaptor'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; @@ -44,54 +44,77 @@ export class InlineCompletionsController extends Disposable { return editor.getContribution(InlineCompletionsController.ID); } - public readonly model = this._register(disposableObservableValue('inlineCompletionModel', undefined)); - private readonly _textModelVersionId = observableValue(this, -1); - private readonly _positions = observableValueOpts({ owner: this, equalsFn: itemsEquals(itemEquals()) }, [new Position(1, 1)]); + private readonly _editorObs = observableCodeEditor(this.editor); + private readonly _positions = derived(this, reader => this._editorObs.selections.read(reader)?.map(s => s.getEndPosition()) ?? [new Position(1, 1)]); + private readonly _suggestWidgetAdaptor = this._register(new SuggestWidgetAdaptor( this.editor, - () => this.model.get()?.selectedInlineCompletion.get()?.toSingleTextEdit(undefined), - (tx) => this.updateObservables(tx, VersionIdChangeReason.Other), - (item) => { - transaction(tx => { - /** @description InlineCompletionsController.handleSuggestAccepted */ - this.updateObservables(tx, VersionIdChangeReason.Other); - this.model.get()?.handleSuggestAccepted(item); - }); - } + () => { + this._editorObs.forceUpdate(); + return this.model.get()?.selectedInlineCompletion.get()?.toSingleTextEdit(undefined); + }, + (item) => this._editorObs.forceUpdate(_tx => { + /** @description InlineCompletionsController.handleSuggestAccepted */ + this.model.get()?.handleSuggestAccepted(item); + }) )); - private readonly _enabledInConfig = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).enabled); - private readonly _isScreenReaderEnabled = observableFromEvent(this._accessibilityService.onDidChangeScreenReaderOptimized, () => this._accessibilityService.isScreenReaderOptimized()); - private readonly _editorDictationInProgress = observableFromEvent(this._contextKeyService.onDidChangeContext, () => this._contextKeyService.getContext(this.editor.getDomNode()).getValue('editorDictation.inProgress') === true); + + private readonly _suggestWidgetSelectedItem = observableFromEvent(this, cb => this._suggestWidgetAdaptor.onDidSelectedItemChange(() => { + this._editorObs.forceUpdate(_tx => cb(undefined)); + }), () => this._suggestWidgetAdaptor.selectedItem); + + + private readonly _enabledInConfig = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).enabled); + private readonly _isScreenReaderEnabled = observableFromEvent(this, this._accessibilityService.onDidChangeScreenReaderOptimized, () => this._accessibilityService.isScreenReaderOptimized()); + private readonly _editorDictationInProgress = observableFromEvent(this, + this._contextKeyService.onDidChangeContext, + () => this._contextKeyService.getContext(this.editor.getDomNode()).getValue('editorDictation.inProgress') === true + ); private readonly _enabled = derived(this, reader => this._enabledInConfig.read(reader) && (!this._isScreenReaderEnabled.read(reader) || !this._editorDictationInProgress.read(reader))); - private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).fontFamily); + private readonly _debounceValue = this._debounceService.for( + this._languageFeaturesService.inlineCompletionsProvider, + 'InlineCompletionsDebounce', + { min: 50, max: 50 } + ); + + public readonly model = derivedDisposable(this, reader => { + if (this._editorObs.isReadonly.read(reader)) { return undefined; } + const textModel = this._editorObs.model.read(reader); + if (!textModel) { return undefined; } + + const model: InlineCompletionsModel = this._instantiationService.createInstance( + InlineCompletionsModel, + textModel, + this._suggestWidgetSelectedItem, + this._editorObs.versionId, + this._positions, + this._debounceValue, + observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.suggest).preview), + observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.suggest).previewMode), + observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).mode), + this._enabled, + ); + return model; + }).recomputeInitiallyAndOnChange(this._store); private readonly _ghostTexts = derived(this, (reader) => { const model = this.model.read(reader); return model?.ghostTexts.read(reader) ?? []; }); - private readonly _stablizedGhostTexts = convertItemsToStableObservables(this._ghostTexts, this._store); - private readonly _ghostTextWidgets = mapObservableArrayCached(this, this._stablizedGhostTexts, (ghostText, store) => { - return store.add(this._instantiationService.createInstance(GhostTextWidget, this.editor, { + private readonly _ghostTextWidgets = mapObservableArrayCached(this, this._stablizedGhostTexts, (ghostText, store) => + store.add(this._instantiationService.createInstance(GhostTextView, this.editor, { ghostText: ghostText, minReservedLineCount: constObservable(0), targetTextModel: this.model.map(v => v?.textModel), - })); - }).recomputeInitiallyAndOnChange(this._store); - - private readonly _debounceValue = this._debounceService.for( - this._languageFeaturesService.inlineCompletionsProvider, - 'InlineCompletionsDebounce', - { min: 50, max: 50 } - ); + })) + ).recomputeInitiallyAndOnChange(this._store); private readonly _playAccessibilitySignal = observableSignal(this); - private readonly _isReadonly = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.readOnly)); - private readonly _textModel = observableFromEvent(this.editor.onDidChangeModel, () => this.editor.getModel()); - private readonly _textModelIfWritable = derived(reader => this._isReadonly.read(reader) ? undefined : this._textModel.read(reader)); + private readonly _fontFamily = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).fontFamily); constructor( public readonly editor: ICodeEditor, @@ -109,69 +132,11 @@ export class InlineCompletionsController extends Disposable { this._register(new InlineCompletionContextKeys(this._contextKeyService, this.model)); - this._register(autorun(reader => { - /** @description InlineCompletionsController.update model */ - const textModel = this._textModelIfWritable.read(reader); - transaction(tx => { - /** @description InlineCompletionsController.onDidChangeModel/readonly */ - this.model.set(undefined, tx); - this.updateObservables(tx, VersionIdChangeReason.Other); - - if (textModel) { - const model = _instantiationService.createInstance( - InlineCompletionsModel, - textModel, - this._suggestWidgetAdaptor.selectedItem, - this._textModelVersionId, - this._positions, - this._debounceValue, - observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).preview), - observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).previewMode), - observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.inlineSuggest).mode), - this._enabled, - ); - this.model.set(model, tx); - } - }); - })); - - const styleElement = this._register(createStyleSheet2()); - this._register(autorun(reader => { - const fontFamily = this._fontFamily.read(reader); - styleElement.setStyle(fontFamily === '' || fontFamily === 'default' ? `` : ` -.monaco-editor .ghost-text-decoration, -.monaco-editor .ghost-text-decoration-preview, -.monaco-editor .ghost-text { - font-family: ${fontFamily}; -}`); - })); - - const getReason = (e: IModelContentChangedEvent): VersionIdChangeReason => { - if (e.isUndoing) { return VersionIdChangeReason.Undo; } - if (e.isRedoing) { return VersionIdChangeReason.Redo; } - if (this.model.get()?.isAcceptingPartially) { return VersionIdChangeReason.AcceptWord; } - return VersionIdChangeReason.Other; - }; - this._register(editor.onDidChangeModelContent((e) => transaction(tx => - /** @description InlineCompletionsController.onDidChangeModelContent */ - this.updateObservables(tx, getReason(e)) - ))); - - this._register(editor.onDidChangeCursorPosition(e => transaction(tx => { - /** @description InlineCompletionsController.onDidChangeCursorPosition */ - this.updateObservables(tx, VersionIdChangeReason.Other); - if (e.reason === CursorChangeReason.Explicit || e.source === 'api') { - this.model.get()?.stop(tx); - } - }))); - - this._register(editor.onDidType(() => transaction(tx => { - /** @description InlineCompletionsController.onDidType */ - this.updateObservables(tx, VersionIdChangeReason.Other); + this._register(reactToChange(this._editorObs.onDidType, (_value, _changes) => { if (this._enabled.get()) { - this.model.get()?.trigger(tx); + this.model.get()?.trigger(); } - }))); + })); this._register(this._commandService.onDidExecuteCommand((e) => { // These commands don't trigger onDidType. @@ -183,22 +148,28 @@ export class InlineCompletionsController extends Disposable { 'acceptSelectedSuggestion', ]); if (commands.has(e.commandId) && editor.hasTextFocus() && this._enabled.get()) { - transaction(tx => { + this._editorObs.forceUpdate(tx => { /** @description onDidExecuteCommand */ this.model.get()?.trigger(tx); }); } })); + this._register(reactToChange(this._editorObs.selections, (_value, changes) => { + if (changes.some(e => e.reason === CursorChangeReason.Explicit || e.source === 'api')) { + this.model.get()?.stop(); + } + })); + this._register(this.editor.onDidBlurEditorWidget(() => { // This is a hidden setting very useful for debugging - if (this._contextKeyService.getContextKeyValue('accessibleViewIsShown') || this._configurationService.getValue('editor.inlineSuggest.keepOnBlur') || - editor.getOption(EditorOption.inlineSuggest).keepOnBlur) { - return; - } - if (InlineSuggestionHintsContentWidget.dropDownVisible) { + if (this._contextKeyService.getContextKeyValue('accessibleViewIsShown') + || this._configurationService.getValue('editor.inlineSuggest.keepOnBlur') + || editor.getOption(EditorOption.inlineSuggest).keepOnBlur + || InlineSuggestionHintsContentWidget.dropDownVisible) { return; } + transaction(tx => { /** @description InlineCompletionsController.onDidBlurEditorWidget */ this.model.get()?.stop(tx); @@ -220,43 +191,48 @@ export class InlineCompletionsController extends Disposable { this._suggestWidgetAdaptor.stopForceRenderingAbove(); })); - const cancellationStore = this._register(new DisposableStore()); - let lastInlineCompletionId: string | undefined = undefined; - this._register(autorunHandleChanges({ - handleChange: (context, changeSummary) => { - if (context.didChange(this._playAccessibilitySignal)) { - lastInlineCompletionId = undefined; - } - return true; - }, - }, async (reader, _) => { - /** @description InlineCompletionsController.playAccessibilitySignalAndReadSuggestion */ - this._playAccessibilitySignal.read(reader); - + const currentInlineCompletionBySemanticId = derivedObservableWithCache(this, (reader, last) => { const model = this.model.read(reader); const state = model?.state.read(reader); - if (!model || !state || !state.inlineCompletion) { - lastInlineCompletionId = undefined; - return; + if (this._suggestWidgetSelectedItem.get()) { + return last; } + return state?.inlineCompletion?.semanticId; + }); + this._register(reactToChangeWithStore(derived(reader => { + this._playAccessibilitySignal.read(reader); + currentInlineCompletionBySemanticId.read(reader); + return {}; + }), async (_value, _deltas, store) => { + /** @description InlineCompletionsController.playAccessibilitySignalAndReadSuggestion */ + const model = this.model.get(); + const state = model?.state.get(); + if (!state || !model) { return; } + const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber); - if (state.inlineCompletion.semanticId !== lastInlineCompletionId) { - cancellationStore.clear(); - lastInlineCompletionId = state.inlineCompletion.semanticId; - const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber); - - await timeout(50, cancelOnDispose(cancellationStore)); - await waitForState(this._suggestWidgetAdaptor.selectedItem, isUndefined, () => false, cancelOnDispose(cancellationStore)); - - await this._accessibilitySignalService.playSignal(AccessibilitySignal.inlineSuggestion); + await timeout(50, cancelOnDispose(store)); + await waitForState(this._suggestWidgetSelectedItem, isUndefined, () => false, cancelOnDispose(store)); - if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { - this.provideScreenReaderUpdate(state.primaryGhostText.renderForScreenReader(lineText)); - } + await this._accessibilitySignalService.playSignal(AccessibilitySignal.inlineSuggestion); + if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { + this._provideScreenReaderUpdate(state.primaryGhostText.renderForScreenReader(lineText)); } })); this._register(new InlineCompletionsHintsWidget(this.editor, this.model, this._instantiationService)); + + this._register(createStyleSheetFromObservable(derived(reader => { + const fontFamily = this._fontFamily.read(reader); + if (fontFamily === '' || fontFamily === 'default') { return ''; } + return ` +.monaco-editor .ghost-text-decoration, +.monaco-editor .ghost-text-decoration-preview, +.monaco-editor .ghost-text { + font-family: ${fontFamily}; +}`; + }))); + + // TODO@hediet this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('accessibility.verbosity.inlineCompletions')) { this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this._configurationService.getValue('accessibility.verbosity.inlineCompletions') }); @@ -269,25 +245,14 @@ export class InlineCompletionsController extends Disposable { this._playAccessibilitySignal.trigger(tx); } - private provideScreenReaderUpdate(content: string): void { + private _provideScreenReaderUpdate(content: string): void { const accessibleViewShowing = this._contextKeyService.getContextKeyValue('accessibleViewIsShown'); const accessibleViewKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleView'); let hint: string | undefined; if (!accessibleViewShowing && accessibleViewKeybinding && this.editor.getOption(EditorOption.inlineCompletionsAccessibilityVerbose)) { hint = localize('showAccessibleViewHint', "Inspect this in the accessible view ({0})", accessibleViewKeybinding.getAriaLabel()); } - hint ? alert(content + ', ' + hint) : alert(content); - } - - /** - * Copies over the relevant state from the text model to observables. - * This solves all kind of eventing issues, as we make sure we always operate on the latest state, - * regardless of who calls into us. - */ - private updateObservables(tx: ITransaction, changeReason: VersionIdChangeReason): void { - const newModel = this.editor.getModel(); - this._textModelVersionId.set(newModel?.getVersionId() ?? -1, tx, changeReason); - this._positions.set(this.editor.getSelections()?.map(selection => selection.getPosition()) ?? [new Position(1, 1)], tx); + alert(hint ? content + ', ' + hint : content); } public shouldShowHoverAt(range: Range) { diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts similarity index 81% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts index 90d4ffcb..c2007e20 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts @@ -12,9 +12,9 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IModelDecoration } from 'vs/editor/common/model'; -import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; -import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget'; +import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; +import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget'; import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import * as nls from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -94,8 +94,8 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan return []; } - renderHoverParts(context: IEditorHoverRenderContext, hoverParts: InlineCompletionsHover[]): IDisposable { - const disposableStore = new DisposableStore(); + renderHoverParts(context: IEditorHoverRenderContext, hoverParts: InlineCompletionsHover[]): IRenderedHoverParts { + const disposables = new DisposableStore(); const part = hoverParts[0]; this._telemetryService.publicLog2<{}, { @@ -104,7 +104,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan }>('inlineCompletionHover.shown'); if (this.accessibilityService.isScreenReaderOptimized() && !this._editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { - this.renderScreenReaderText(context, part, disposableStore); + disposables.add(this.renderScreenReaderText(context, part)); } const model = part.controller.model.get()!; @@ -115,32 +115,42 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan model.inlineCompletionsCount, model.activeCommands, ); - context.fragment.appendChild(w.getDomNode()); + const widgetNode: HTMLElement = w.getDomNode(); + context.fragment.appendChild(widgetNode); model.triggerExplicitly(); - disposableStore.add(w); + disposables.add(w); + const renderedHoverPart: IRenderedHoverPart = { + hoverPart: part, + hoverElement: widgetNode, + dispose() { disposables.dispose(); } + }; + return new RenderedHoverParts([renderedHoverPart]); + } - return disposableStore; + getAccessibleContent(hoverPart: InlineCompletionsHover): string { + return nls.localize('hoverAccessibilityStatusBar', 'There are inline completions here'); } - private renderScreenReaderText(context: IEditorHoverRenderContext, part: InlineCompletionsHover, disposableStore: DisposableStore) { + private renderScreenReaderText(context: IEditorHoverRenderContext, part: InlineCompletionsHover): IDisposable { + const disposables = new DisposableStore(); const $ = dom.$; const markdownHoverElement = $('div.hover-row.markdown-hover'); const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents', { ['aria-live']: 'assertive' })); - const renderer = disposableStore.add(new MarkdownRenderer({ editor: this._editor }, this._languageService, this._openerService)); + const renderer = disposables.add(new MarkdownRenderer({ editor: this._editor }, this._languageService, this._openerService)); const render = (code: string) => { - disposableStore.add(renderer.onDidRenderAsync(() => { + disposables.add(renderer.onDidRenderAsync(() => { hoverContentsElement.className = 'hover-contents code-hover-contents'; context.onContentsChanged(); })); const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Suggestion:"); - const renderedContents = disposableStore.add(renderer.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code))); + const renderedContents = disposables.add(renderer.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code))); hoverContentsElement.replaceChildren(renderedContents.element); }; - disposableStore.add(autorun(reader => { + disposables.add(autorun(reader => { /** @description update hover */ const ghostText = part.controller.model.read(reader)?.primaryGhostText.read(reader); if (ghostText) { @@ -152,5 +162,6 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan })); context.fragment.appendChild(markdownHoverElement); + return disposables; } } diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.css b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.css similarity index 100% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.css rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.css diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts similarity index 98% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts index 9f7d3d17..51b8f92e 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts @@ -21,8 +21,8 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Command, InlineCompletionTriggerKind } from 'vs/editor/common/languages'; import { PositionAffinity } from 'vs/editor/common/model'; -import { showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId } from 'vs/editor/contrib/inlineCompletions/browser/commandIds'; -import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; +import { showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId } from 'vs/editor/contrib/inlineCompletions/browser/controller/commandIds'; +import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel'; import { localize } from 'vs/nls'; import { MenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -36,7 +36,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; export class InlineCompletionsHintsWidget extends Disposable { - private readonly alwaysShowToolbar = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).showToolbar === 'always'); + private readonly alwaysShowToolbar = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).showToolbar === 'always'); private sessionPosition: Position | undefined = undefined; diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts index 2819af72..25670498 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts @@ -5,10 +5,10 @@ import { EditorContributionInstantiation, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes'; -import { TriggerInlineSuggestionAction, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction, AcceptNextWordOfInlineCompletion, AcceptInlineCompletion, HideInlineCompletion, ToggleAlwaysShowInlineSuggestionToolbar, AcceptNextLineOfInlineCompletion } from 'vs/editor/contrib/inlineCompletions/browser/commands'; -import { InlineCompletionsHoverParticipant } from 'vs/editor/contrib/inlineCompletions/browser/hoverParticipant'; +import { TriggerInlineSuggestionAction, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction, AcceptNextWordOfInlineCompletion, AcceptInlineCompletion, HideInlineCompletion, ToggleAlwaysShowInlineSuggestionToolbar, AcceptNextLineOfInlineCompletion } from 'vs/editor/contrib/inlineCompletions/browser/controller/commands'; +import { InlineCompletionsHoverParticipant } from 'vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant'; import { InlineCompletionsAccessibleView } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { registerAction2 } from 'vs/platform/actions/common/actions'; diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView.ts index 6182681a..48226d2a 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView.ts @@ -3,60 +3,77 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; -import { AccessibleViewType, AccessibleViewProviderId } from 'vs/platform/accessibility/browser/accessibleView'; +import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; +import { AccessibleViewType, AccessibleViewProviderId, IAccessibleViewContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel'; -export class InlineCompletionsAccessibleView extends Disposable implements IAccessibleViewImplentation { +export class InlineCompletionsAccessibleView implements IAccessibleViewImplentation { readonly type = AccessibleViewType.View; readonly priority = 95; readonly name = 'inline-completions'; readonly when = ContextKeyExpr.and(InlineCompletionContextKeys.inlineSuggestionVisible); getProvider(accessor: ServicesAccessor) { const codeEditorService = accessor.get(ICodeEditorService); - function resolveProvider() { - const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); - if (!editor) { - return; - } - const model = InlineCompletionsController.get(editor)?.model.get(); - const state = model?.state.get(); - if (!model || !state) { - return; - } - const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber); - const ghostText = state.primaryGhostText.renderForScreenReader(lineText); - if (!ghostText) { - return; - } - const language = editor.getModel()?.getLanguageId() ?? undefined; - return { - id: AccessibleViewProviderId.InlineCompletions, - verbositySettingKey: 'accessibility.verbosity.inlineCompletions', - provideContent() { return lineText + ghostText; }, - onClose() { - model.stop(); - editor.focus(); - }, - next() { - model.next(); - setTimeout(() => resolveProvider(), 50); - }, - previous() { - model.previous(); - setTimeout(() => resolveProvider(), 50); - }, - options: { language, type: AccessibleViewType.View } - }; + const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); + if (!editor) { + return; + } + + const model = InlineCompletionsController.get(editor)?.model.get(); + if (!model?.state.get()) { + return; } - return resolveProvider(); + + return new InlineCompletionsAccessibleViewContentProvider(editor, model); } - constructor() { +} + +class InlineCompletionsAccessibleViewContentProvider extends Disposable implements IAccessibleViewContentProvider { + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + public readonly onDidChangeContent: Event = this._onDidChangeContent.event; + constructor( + private readonly _editor: ICodeEditor, + private readonly _model: InlineCompletionsModel, + ) { super(); } + + public readonly id = AccessibleViewProviderId.InlineCompletions; + public readonly verbositySettingKey = 'accessibility.verbosity.inlineCompletions'; + public readonly options = { language: this._editor.getModel()?.getLanguageId() ?? undefined, type: AccessibleViewType.View }; + + public provideContent(): string { + const state = this._model.state.get(); + if (!state) { + throw new Error('Inline completion is visible but state is not available'); + } + const lineText = this._model.textModel.getLineContent(state.primaryGhostText.lineNumber); + const ghostText = state.primaryGhostText.renderForScreenReader(lineText); + if (!ghostText) { + throw new Error('Inline completion is visible but ghost text is not available'); + } + return lineText + ghostText; + } + public provideNextContent(): string | undefined { + // asynchronously update the model and fire the event + this._model.next().then((() => this._onDidChangeContent.fire())); + return; + } + public providePreviousContent(): string | undefined { + // asynchronously update the model and fire the event + this._model.previous().then((() => this._onDidChangeContent.fire())); + return; + } + public onClose(): void { + this._model.stop(); + this._editor.focus(); + } } diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/ghostText.ts similarity index 100% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/ghostText.ts diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts similarity index 92% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index bc10c23e..de33bdea 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Permutation } from 'vs/base/common/arrays'; +import { compareBy, Permutation } from 'vs/base/common/arrays'; import { mapFindFirst } from 'vs/base/common/arraysFind'; import { itemsEquals } from 'vs/base/common/equals'; import { BugIndicatingError, onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; @@ -23,26 +23,20 @@ import { Command, InlineCompletionContext, InlineCompletionTriggerKind, PartialA import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; -import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostTextsOrReplacementsEqual } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; -import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; -import { computeGhostText, singleTextEditAugments, singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; -import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; +import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; +import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostTextsOrReplacementsEqual } from 'vs/editor/contrib/inlineCompletions/browser/model/ghostText'; +import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource'; +import { computeGhostText, singleTextEditAugments, singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/model/singleTextEdit'; +import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdaptor'; import { addPositions, subtractPositions } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -export enum VersionIdChangeReason { - Undo, - Redo, - AcceptWord, - Other, -} - export class InlineCompletionsModel extends Disposable { - private readonly _source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this.textModelVersionId, this._debounceValue)); - private readonly _isActive = observableValue(this, false); - readonly _forceUpdateExplicitlySignal = observableSignal(this); + private readonly _source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this._textModelVersionId, this._debounceValue)); + private readonly _isActive = observableValue(this, false); + private readonly _forceUpdateExplicitlySignal = observableSignal(this); // We use a semantic id to keep the same inline completion selected even if the provider reorders the completions. private readonly _selectedInlineCompletionId = observableValue(this, undefined); @@ -54,7 +48,7 @@ export class InlineCompletionsModel extends Disposable { constructor( public readonly textModel: ITextModel, public readonly selectedSuggestItem: IObservable, - public readonly textModelVersionId: IObservable, + public readonly _textModelVersionId: IObservable, private readonly _positions: IObservable, private readonly _debounceValue: IFeatureDebounceInformation, private readonly _suggestPreviewEnabled: IObservable, @@ -91,6 +85,13 @@ export class InlineCompletionsModel extends Disposable { VersionIdChangeReason.AcceptWord, ]); + private _getReason(e: IModelContentChangedEvent | undefined): VersionIdChangeReason { + if (e?.isUndoing) { return VersionIdChangeReason.Undo; } + if (e?.isRedoing) { return VersionIdChangeReason.Redo; } + if (this.isAcceptingPartially) { return VersionIdChangeReason.AcceptWord; } + return VersionIdChangeReason.Other; + } + private readonly _fetchInlineCompletionsPromise = derivedHandleChanges({ owner: this, createEmptyChangeSummary: () => ({ @@ -99,7 +100,7 @@ export class InlineCompletionsModel extends Disposable { }), handleChange: (ctx, changeSummary) => { /** @description fetch inline completions */ - if (ctx.didChange(this.textModelVersionId) && this._preserveCurrentCompletionReasons.has(ctx.change)) { + if (ctx.didChange(this._textModelVersionId) && this._preserveCurrentCompletionReasons.has(this._getReason(ctx.change))) { changeSummary.preserveCurrentCompletion = true; } else if (ctx.didChange(this._forceUpdateExplicitlySignal)) { changeSummary.inlineCompletionTriggerKind = InlineCompletionTriggerKind.Explicit; @@ -114,7 +115,7 @@ export class InlineCompletionsModel extends Disposable { return undefined; } - this.textModelVersionId.read(reader); // Refetch on text change + this._textModelVersionId.read(reader); // Refetch on text change const suggestWidgetInlineCompletions = this._source.suggestWidgetInlineCompletions.get(); const suggestItem = this.selectedSuggestItem.read(reader); @@ -264,26 +265,24 @@ export class InlineCompletionsModel extends Disposable { const augmentedCompletion = mapFindFirst(candidateInlineCompletions, completion => { let r = completion.toSingleTextEdit(reader); - r = singleTextRemoveCommonPrefix(r, model, Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition())); + r = singleTextRemoveCommonPrefix( + r, + model, + Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition()) + ); return singleTextEditAugments(r, suggestCompletion) ? { completion, edit: r } : undefined; }); return augmentedCompletion; } - public readonly ghostTexts = derivedOpts({ - owner: this, - equalsFn: ghostTextsOrReplacementsEqual - }, reader => { + public readonly ghostTexts = derivedOpts({ owner: this, equalsFn: ghostTextsOrReplacementsEqual }, reader => { const v = this.state.read(reader); if (!v) { return undefined; } return v.ghostTexts; }); - public readonly primaryGhostText = derivedOpts({ - owner: this, - equalsFn: ghostTextOrReplacementEquals - }, reader => { + public readonly primaryGhostText = derivedOpts({ owner: this, equalsFn: ghostTextOrReplacementEquals }, reader => { const v = this.state.read(reader); if (!v) { return undefined; } return v?.primaryGhostText; @@ -320,6 +319,11 @@ export class InlineCompletionsModel extends Disposable { } const completion = state.inlineCompletion.toInlineCompletion(undefined); + if (completion.command) { + // Make sure the completion list will not be disposed. + completion.source.addRef(); + } + editor.pushUndoStop(); if (completion.snippetInfo) { editor.executeEdits( @@ -341,18 +345,8 @@ export class InlineCompletionsModel extends Disposable { editor.setSelections(selections, 'inlineCompletionAccept'); } - if (completion.command) { - // Make sure the completion list will not be disposed. - completion.source.addRef(); - } - - // Reset before invoking the command, since the command might cause a follow up trigger. - transaction(tx => { - this._source.clear(tx); - // Potentially, isActive will get set back to true by the typing or accept inline suggest event - // if automatic inline suggestions are enabled. - this._isActive.set(false, tx); - }); + // Reset before invoking the command, as the command might cause a follow up trigger (which we don't want to reset). + this.stop(); if (completion.command) { await this._commandService @@ -458,9 +452,7 @@ export class InlineCompletionsModel extends Disposable { completion.source.inlineCompletions, completion.sourceInlineCompletion, text.length, - { - kind, - } + { kind, } ); } } finally { @@ -485,6 +477,13 @@ export class InlineCompletionsModel extends Disposable { } } +export enum VersionIdChangeReason { + Undo, + Redo, + AcceptWord, + Other, +} + export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { if (positions.length === 1) { // No secondary cursor positions @@ -527,7 +526,7 @@ function substringPos(text: string, pos: Position): string { } function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] { - const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); + const sortPerm = Permutation.createSortPermutation(edits, compareBy(e => e.range, Range.compareRangesUsingStarts)); const edit = new TextEdit(sortPerm.apply(edits)); const sortedNewRanges = edit.getNewRanges(); const newRanges = sortPerm.inverse().apply(sortedNewRanges); diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts similarity index 95% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 32b77dd2..7886532f 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -17,8 +17,8 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { InlineCompletionItem, InlineCompletionProviderResult, provideInlineCompletions } from 'vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions'; -import { singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { InlineCompletionItem, InlineCompletionProviderResult, provideInlineCompletions } from 'vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions'; +import { singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/model/singleTextEdit'; export class InlineCompletionsSource extends Disposable { private readonly _updateOperation = this._register(new MutableDisposable()); @@ -27,7 +27,7 @@ export class InlineCompletionsSource extends Disposable { constructor( private readonly textModel: ITextModel, - private readonly versionId: IObservable, + private readonly versionId: IObservable, private readonly _debounceValue: IFeatureDebounceInformation, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService, @@ -62,7 +62,7 @@ export class InlineCompletionsSource extends Disposable { await wait(this._debounceValue.get(this.textModel), source.token); } - if (source.token.isCancellationRequested || this.textModel.getVersionId() !== request.versionId) { + if (source.token.isCancellationRequested || this._store.isDisposed || this.textModel.getVersionId() !== request.versionId) { return false; } @@ -76,7 +76,7 @@ export class InlineCompletionsSource extends Disposable { this.languageConfigurationService ); - if (source.token.isCancellationRequested || this.textModel.getVersionId() !== request.versionId) { + if (source.token.isCancellationRequested || this._store.isDisposed || this.textModel.getVersionId() !== request.versionId) { return false; } @@ -182,7 +182,7 @@ export class UpToDateInlineCompletions implements IDisposable { private readonly inlineCompletionProviderResult: InlineCompletionProviderResult, public readonly request: UpdateRequest, private readonly _textModel: ITextModel, - private readonly _versionId: IObservable, + private readonly _versionId: IObservable, ) { const ids = _textModel.deltaDecorations([], inlineCompletionProviderResult.completions.map(i => ({ range: i.range, @@ -254,7 +254,7 @@ export class InlineCompletionWithUpdatedRange { public readonly inlineCompletion: InlineCompletionItem, public readonly decorationId: string, private readonly _textModel: ITextModel, - private readonly _modelVersion: IObservable, + private readonly _modelVersion: IObservable, ) { } diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts similarity index 95% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index 28052040..b1139b42 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/provideInlineCompletions.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -18,19 +18,19 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import { ITextModel } from 'vs/editor/common/model'; import { fixBracketsInLine } from 'vs/editor/common/model/bracketPairsTextModelPart/fixBrackets'; import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; -import { getReadonlyEmptyArray } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { getReadonlyEmptyArray } from '../utils'; import { SnippetParser, Text } from 'vs/editor/contrib/snippet/browser/snippetParser'; export async function provideInlineCompletions( registry: LanguageFeatureRegistry, - position: Position, + positionOrRange: Position | Range, model: ITextModel, context: InlineCompletionContext, token: CancellationToken = CancellationToken.None, languageConfigurationService?: ILanguageConfigurationService, ): Promise { // Important: Don't use position after the await calls, as the model could have been changed in the meantime! - const defaultReplaceRange = getDefaultRange(position, model); + const defaultReplaceRange = positionOrRange instanceof Position ? getDefaultRange(positionOrRange, model) : positionOrRange; const providers = registry.all(model); const multiMap = new SetMap>(); @@ -100,8 +100,13 @@ export async function provideInlineCompletions( } try { - const completions = await provider.provideInlineCompletions(model, position, context, token); - return completions; + if (positionOrRange instanceof Position) { + const completions = await provider.provideInlineCompletions(model, positionOrRange, context, token); + return completions; + } else { + const completions = await provider.provideInlineEdits?.(model, positionOrRange, context, token); + return completions; + } } catch (e) { onUnexpectedExternalError(e); return undefined; diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/singleTextEdit.ts similarity index 99% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/singleTextEdit.ts index 750eb459..1cd4b90a 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/singleTextEdit.ts @@ -10,7 +10,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextLength } from 'vs/editor/common/core/textLength'; import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; -import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/model/ghostText'; export function singleTextRemoveCommonPrefix(edit: SingleTextEdit, model: ITextModel, validModelRange?: Range): SingleTextEdit { const modelRange = validModelRange ? edit.range.intersectRanges(validModelRange) : edit.range; diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdaptor.ts similarity index 91% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdaptor.ts index 95d135ce..a4167b43 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdaptor.ts @@ -3,39 +3,36 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; +import { compareBy, numberComparator } from 'vs/base/common/arrays'; +import { findFirstMax } from 'vs/base/common/arraysFind'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { CompletionItemInsertTextRule, CompletionItemKind, SelectedSuggestionInfo } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; +import { singleTextEditAugments, singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/model/singleTextEdit'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { SnippetSession } from 'vs/editor/contrib/snippet/browser/snippetSession'; import { CompletionItem } from 'vs/editor/contrib/suggest/browser/suggest'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; -import { IObservable, ITransaction, observableValue, transaction } from 'vs/base/common/observable'; -import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; -import { ITextModel } from 'vs/editor/common/model'; -import { compareBy, numberComparator } from 'vs/base/common/arrays'; -import { findFirstMax } from 'vs/base/common/arraysFind'; -import { singleTextEditAugments, singleTextRemoveCommonPrefix } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; export class SuggestWidgetAdaptor extends Disposable { private isSuggestWidgetVisible: boolean = false; private isShiftKeyPressed = false; private _isActive = false; private _currentSuggestItemInfo: SuggestItemInfo | undefined = undefined; - - private readonly _selectedItem = observableValue(this, undefined as SuggestItemInfo | undefined); - - public get selectedItem(): IObservable { - return this._selectedItem; + public get selectedItem(): SuggestItemInfo | undefined { + return this._currentSuggestItemInfo; } + private _onDidSelectedItemChange = this._register(new Emitter()); + public readonly onDidSelectedItemChange: Event = this._onDidSelectedItemChange.event; constructor( private readonly editor: ICodeEditor, private readonly suggestControllerPreselector: () => SingleTextEdit | undefined, - private readonly checkModelVersion: (tx: ITransaction) => void, private readonly onWillAccept: (item: SuggestItemInfo) => void, ) { super(); @@ -59,8 +56,6 @@ export class SuggestWidgetAdaptor extends Disposable { this._register(suggestController.registerSelector({ priority: 100, select: (model, pos, suggestItems) => { - transaction(tx => this.checkModelVersion(tx)); - const textModel = this.editor.getModel(); if (!textModel) { // Should not happen @@ -142,11 +137,7 @@ export class SuggestWidgetAdaptor extends Disposable { this._isActive = newActive; this._currentSuggestItemInfo = newInlineCompletion; - transaction(tx => { - /** @description Update state from suggest widget */ - this.checkModelVersion(tx); - this._selectedItem.set(this._isActive ? this._currentSuggestItemInfo : undefined, tx); - }); + this._onDidSelectedItemChange.fire(); } } diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/ghostText.css b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/view/ghostTextView.css similarity index 100% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/ghostText.css rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/view/ghostTextView.css diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/view/ghostTextView.ts similarity index 97% rename from patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts rename to patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/view/ghostTextView.ts index 0c93d846..843a13ec 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/browser/view/ghostTextView.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorun, derived, observableFromEvent, observableSignalFromEvent, observableValue } from 'vs/base/common/observable'; import * as strings from 'vs/base/common/strings'; -import 'vs/css!./ghostText'; +import 'vs/css!./ghostTextView'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorFontLigatures, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -22,7 +22,7 @@ import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { InlineDecorationType } from 'vs/editor/common/viewModel'; -import { GhostText, GhostTextReplacement } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { GhostText, GhostTextReplacement } from 'vs/editor/contrib/inlineCompletions/browser/model/ghostText'; import { ColumnRange, applyObservableDecorations } from 'vs/editor/contrib/inlineCompletions/browser/utils'; export const GHOST_TEXT_DESCRIPTION = 'ghost-text'; @@ -32,9 +32,9 @@ export interface IGhostTextWidgetModel { readonly minReservedLineCount: IObservable; } -export class GhostTextWidget extends Disposable { +export class GhostTextView extends Disposable { private readonly isDisposed = observableValue(this, false); - private readonly currentTextModel = observableFromEvent(this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel()); + private readonly currentTextModel = observableFromEvent(this, this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel()); constructor( private readonly editor: ICodeEditor, diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts index 648f5940..852b9d33 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts @@ -2,9 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Position } from 'vs/editor/common/core/position'; -import { getSecondaryEdits } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; +import { getSecondaryEdits } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel'; import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index e160c3da..089471c4 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; @@ -13,8 +13,8 @@ import { InlineCompletionsProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; -import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; +import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel'; import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; import { GhostTextContext, MockInlineCompletionsProvider } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; import { ITestCodeEditor, TestCodeEditorInstantiationOptions, withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; @@ -22,7 +22,7 @@ import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { Selection } from 'vs/editor/common/core/selection'; -import { computeGhostText } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { computeGhostText } from 'vs/editor/contrib/inlineCompletions/browser/model/singleTextEdit'; suite('Inline Completions', () => { ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts index 354fa36b..11f88ae2 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts @@ -25,13 +25,13 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import * as assert from 'assert'; +import assert from 'assert'; import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; +import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; import { autorun } from 'vs/base/common/observable'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; diff --git a/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 11c24b0b..1c1d7724 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -11,7 +11,7 @@ import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { InlineCompletion, InlineCompletionContext, InlineCompletionsProvider } from 'vs/editor/common/languages'; import { ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; -import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; +import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel'; import { autorun } from 'vs/base/common/observable'; export class MockInlineCompletionsProvider implements InlineCompletionsProvider { diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts index 298fcd44..ca3dc5bc 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts @@ -13,9 +13,10 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { IModelDeltaDecoration, ITextModel, InjectedTextCursorStops } from 'vs/editor/common/model'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { InlineDecorationType } from 'vs/editor/common/viewModel'; -import { AdditionalLinesWidget, LineData } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextWidget'; -import { GhostText } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { AdditionalLinesWidget, LineData } from 'vs/editor/contrib/inlineCompletions/browser/view/ghostTextView'; +import { GhostText } from 'vs/editor/contrib/inlineCompletions/browser/model/ghostText'; import { ColumnRange, applyObservableDecorations } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { diffDeleteDecoration, diffLineDeleteDecorationBackgroundWithIndicator } from 'vs/editor/browser/widget/diffEditor/registrations.contribution'; export const INLINE_EDIT_DESCRIPTION = 'inline-edit'; export interface IGhostTextWidgetModel { @@ -23,12 +24,11 @@ export interface IGhostTextWidgetModel { readonly ghostText: IObservable; readonly minReservedLineCount: IObservable; readonly range: IObservable; - readonly backgroundColoring: IObservable; } export class GhostTextWidget extends Disposable { private readonly isDisposed = observableValue(this, false); - private readonly currentTextModel = observableFromEvent(this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel()); + private readonly currentTextModel = observableFromEvent(this, this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel()); constructor( private readonly editor: ICodeEditor, @@ -92,7 +92,7 @@ export class GhostTextWidget extends Disposable { let hiddenTextStartColumn: number | undefined = undefined; let lastIdx = 0; - if (!isPureRemove) { + if (!isPureRemove && (isSingleLine || !range)) { for (const part of ghostText.parts) { let lines = part.lines; //If remove range is set, we want to push all new liens to virtual area @@ -140,7 +140,6 @@ export class GhostTextWidget extends Disposable { range, isSingleLine, isPureRemove, - backgroundColoring: this.model.backgroundColoring.read(reader) }; }); @@ -164,7 +163,7 @@ export class GhostTextWidget extends Disposable { if (uiState.isSingleLine) { ranges.push(uiState.range); } - else if (uiState.isPureRemove) { + else if (!uiState.isPureRemove) { const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; for (let i = 0; i < lines; i++) { const line = uiState.range.startLineNumber + i; @@ -174,24 +173,22 @@ export class GhostTextWidget extends Disposable { ranges.push(range); } } - else { - const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; - for (let i = 0; i < lines; i++) { - const line = uiState.range.startLineNumber + i; - const firstNonWhitespace = uiState.targetTextModel.getLineFirstNonWhitespaceColumn(line); - const lastNonWhitespace = uiState.targetTextModel.getLineLastNonWhitespaceColumn(line); - const range = new Range(line, firstNonWhitespace, line, lastNonWhitespace); - ranges.push(range); - } - } - const className = uiState.backgroundColoring ? 'inline-edit-remove backgroundColoring' : 'inline-edit-remove'; for (const range of ranges) { decorations.push({ range, - options: { inlineClassName: className, description: 'inline-edit-remove', } + options: diffDeleteDecoration }); } } + if (uiState.range && !uiState.isSingleLine && uiState.isPureRemove) { + const r = new Range(uiState.range.startLineNumber, 1, uiState.range.endLineNumber - 1, 1); + + decorations.push({ + range: r, + options: diffLineDeleteDecorationBackgroundWithIndicator + }); + + } for (const p of uiState.inlineTexts) { @@ -215,7 +212,7 @@ export class GhostTextWidget extends Disposable { derived(reader => { /** @description lines */ const uiState = this.uiState.read(reader); - return uiState && !uiState.isPureRemove ? { + return uiState && !uiState.isPureRemove && (uiState.isSingleLine || !uiState.range) ? { lineNumber: uiState.lineNumber, additionalLines: uiState.additionalLines, minReservedLineCount: uiState.additionalReservedLineCount, diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts index 6c1c7337..cfacc773 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts @@ -3,17 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { constObservable } from 'vs/base/common/observable'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration } from 'vs/editor/common/model'; -import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { InlineEditController } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; import { InlineEditHintsContentWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget'; +import * as nls from 'vs/nls'; export class InlineEditHover implements IHoverPart { constructor( @@ -86,8 +87,8 @@ export class InlineEditHoverParticipant implements IEditorHoverParticipant { + const disposables = new DisposableStore(); this._telemetryService.publicLog2<{}, { owner: 'hediet'; @@ -97,9 +98,17 @@ export class InlineEditHoverParticipant implements IEditorHoverParticipant = { + hoverPart: hoverParts[0], + hoverElement: widgetNode, + dispose: () => disposables.dispose() + }; + return new RenderedHoverParts([renderedHoverPart]); + } - return disposableStore; + getAccessibleContent(hoverPart: InlineEditHover): string { + return nls.localize('hoverAccessibilityInlineEdits', 'There are inline edits here.'); } } diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts index 7196773a..0be0d125 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { EditorContributionInstantiation, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes'; +// import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { AcceptInlineEdit, JumpBackInlineEdit, JumpToInlineEdit, RejectInlineEdit, TriggerInlineEdit } from 'vs/editor/contrib/inlineEdit/browser/commands'; -import { InlineEditHoverParticipant } from 'vs/editor/contrib/inlineEdit/browser/hoverParticipant'; +// import { InlineEditHoverParticipant } from 'vs/editor/contrib/inlineEdit/browser/hoverParticipant'; import { InlineEditController } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; registerEditorAction(AcceptInlineEdit); @@ -17,4 +17,4 @@ registerEditorAction(TriggerInlineEdit); registerEditorContribution(InlineEditController.ID, InlineEditController, EditorContributionInstantiation.Eventually); -HoverParticipantRegistry.register(InlineEditHoverParticipant); +// HoverParticipantRegistry.register(InlineEditHoverParticipant); diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css index d6d15654..aced5d62 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css +++ b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css @@ -6,11 +6,6 @@ .monaco-editor .inline-edit-remove { background-color: var(--vscode-editorGhostText-background); font-style: italic; - text-decoration: line-through; -} - -.monaco-editor .inline-edit-remove.backgroundColoring { - background-color: var(--vscode-diffEditor-removedLineBackground); } .monaco-editor .inline-edit-hidden { diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts index 283e72be..3f47517c 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { ISettableObservable, autorun, constObservable, disposableObservableValue, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { ISettableObservable, autorun, constObservable, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; @@ -15,46 +15,66 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IInlineEdit, InlineEditTriggerKind } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/model/ghostText'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { InlineEditHintsWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { createStyleSheet2 } from 'vs/base/browser/dom'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; - -export class InlineEditWidget implements IDisposable { - constructor(public readonly widget: GhostTextWidget, public readonly edit: IInlineEdit) { } - - dispose(): void { - this.widget.dispose(); - } -} +import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; +import { InlineEditSideBySideWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget'; +import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; +import { IModelService } from 'vs/editor/common/services/model'; export class InlineEditController extends Disposable { static ID = 'editor.contrib.inlineEditController'; public static readonly inlineEditVisibleKey = 'inlineEditVisible'; - public static readonly inlineEditVisibleContext = new RawContextKey(InlineEditController.inlineEditVisibleKey, false); + public static readonly inlineEditVisibleContext = new RawContextKey(this.inlineEditVisibleKey, false); private _isVisibleContext = InlineEditController.inlineEditVisibleContext.bindTo(this.contextKeyService); public static readonly cursorAtInlineEditKey = 'cursorAtInlineEdit'; - public static readonly cursorAtInlineEditContext = new RawContextKey(InlineEditController.cursorAtInlineEditKey, false); + public static readonly cursorAtInlineEditContext = new RawContextKey(this.cursorAtInlineEditKey, false); private _isCursorAtInlineEditContext = InlineEditController.cursorAtInlineEditContext.bindTo(this.contextKeyService); public static get(editor: ICodeEditor): InlineEditController | null { return editor.getContribution(InlineEditController.ID); } - private _currentEdit: ISettableObservable = this._register(disposableObservableValue(this, undefined)); + private _currentEdit: ISettableObservable = observableValue(this, undefined); + private _currentWidget = derivedDisposable(this._currentEdit, (reader) => { + const edit = this._currentEdit.read(reader); + if (!edit) { + return undefined; + } + const line = edit.range.endLineNumber; + const column = edit.range.endColumn; + const textToDisplay = edit.text.endsWith('\n') && !(edit.range.startLineNumber === edit.range.endLineNumber && edit.range.startColumn === edit.range.endColumn) ? edit.text.slice(0, -1) : edit.text; + const ghostText = new GhostText(line, [new GhostTextPart(column, textToDisplay, false)]); + //only show ghost text for single line edits + //unless it is a pure removal + //multi line edits are shown in the side by side widget + const isSingleLine = edit.range.startLineNumber === edit.range.endLineNumber && ghostText.parts.length === 1 && ghostText.parts[0].lines.length === 1; + const isPureRemoval = edit.text === ''; + if (!isSingleLine && !isPureRemoval) { + return undefined; + } + const instance = this.instantiationService.createInstance(GhostTextWidget, this.editor, { + ghostText: constObservable(ghostText), + minReservedLineCount: constObservable(0), + targetTextModel: constObservable(this.editor.getModel() ?? undefined), + range: constObservable(edit.range) + }); + return instance; + }); private _currentRequestCts: CancellationTokenSource | undefined; private _jumpBackPosition: Position | undefined; private _isAccepting: ISettableObservable = observableValue(this, false); - private readonly _enabled = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).enabled); - private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).fontFamily); - private readonly _backgroundColoring = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).backgroundColoring); + private readonly _enabled = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).enabled); + private readonly _fontFamily = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).fontFamily); constructor( @@ -64,6 +84,8 @@ export class InlineEditController extends Disposable { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @ICommandService private readonly _commandService: ICommandService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IDiffProviderFactoryService private readonly _diffProviderFactoryService: IDiffProviderFactoryService, + @IModelService private readonly _modelService: IModelService, ) { super(); @@ -84,7 +106,7 @@ export class InlineEditController extends Disposable { })); //Check if the cursor is at the ghost text - const cursorPosition = observableFromEvent(editor.onDidChangeCursorPosition, () => editor.getPosition()); + const cursorPosition = observableFromEvent(this, editor.onDidChangeCursorPosition, () => editor.getPosition()); this._register(autorun(reader => { /** @description InlineEditController.cursorPositionChanged model */ if (!this._enabled.read(reader)) { @@ -154,7 +176,8 @@ export class InlineEditController extends Disposable { }`); })); - this._register(new InlineEditHintsWidget(this.editor, this._currentEdit, this.instantiationService)); + this._register(new InlineEditHintsWidget(this.editor, this._currentWidget, this.instantiationService)); + this._register(new InlineEditSideBySideWidget(this.editor, this._currentEdit, this.instantiationService, this._diffProviderFactoryService, this._modelService)); } private checkCursorPosition(position: Position) { @@ -162,7 +185,7 @@ export class InlineEditController extends Disposable { this._isCursorAtInlineEditContext.set(false); return; } - const gt = this._currentEdit.get()?.edit; + const gt = this._currentEdit.get(); if (!gt) { this._isCursorAtInlineEditContext.set(false); return; @@ -231,18 +254,7 @@ export class InlineEditController extends Disposable { if (!edit) { return; } - const line = edit.range.endLineNumber; - const column = edit.range.endColumn; - const textToDisplay = edit.text.endsWith('\n') && !(edit.range.startLineNumber === edit.range.endLineNumber && edit.range.startColumn === edit.range.endColumn) ? edit.text.slice(0, -1) : edit.text; - const ghostText = new GhostText(line, [new GhostTextPart(column, textToDisplay, false)]); - const instance = this.instantiationService.createInstance(GhostTextWidget, this.editor, { - ghostText: constObservable(ghostText), - minReservedLineCount: constObservable(0), - targetTextModel: constObservable(this.editor.getModel() ?? undefined), - range: constObservable(edit.range), - backgroundColoring: this._backgroundColoring - }); - this._currentEdit.set(new InlineEditWidget(instance, edit), undefined); + this._currentEdit.set(edit, undefined); } public async trigger() { @@ -260,7 +272,7 @@ export class InlineEditController extends Disposable { public async accept() { this._isAccepting.set(true, undefined); - const data = this._currentEdit.get()?.edit; + const data = this._currentEdit.get(); if (!data) { return; } @@ -287,7 +299,7 @@ export class InlineEditController extends Disposable { public jumpToCurrent(): void { this._jumpBackPosition = this.editor.getSelection()?.getStartPosition(); - const data = this._currentEdit.get()?.edit; + const data = this._currentEdit.get(); if (!data) { return; } @@ -298,7 +310,7 @@ export class InlineEditController extends Disposable { } public async clear(sendRejection: boolean = true) { - const edit = this._currentEdit.get()?.edit; + const edit = this._currentEdit.get(); if (edit && edit?.rejected && sendRejection) { await this._commandService .executeCommand(edit.rejected.id, ...(edit.rejected.arguments || [])) @@ -324,11 +336,15 @@ export class InlineEditController extends Disposable { public shouldShowHoverAt(range: Range) { const currentEdit = this._currentEdit.get(); + const currentWidget = this._currentWidget.get(); if (!currentEdit) { return false; } - const edit = currentEdit.edit; - const model = currentEdit.widget.model; + if (!currentWidget) { + return false; + } + const edit = currentEdit; + const model = currentWidget.model; const overReplaceRange = Range.containsPosition(edit.range, range.getStartPosition()) || Range.containsPosition(edit.range, range.getEndPosition()); if (overReplaceRange) { return true; @@ -341,7 +357,7 @@ export class InlineEditController extends Disposable { } public shouldShowHoverAtViewZone(viewZoneId: string): boolean { - return this._currentEdit.get()?.widget.ownsViewZone(viewZoneId) ?? false; + return this._currentWidget.get()?.ownsViewZone(viewZoneId) ?? false; } } diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts index ee0b537a..cb2453ab 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts @@ -15,7 +15,7 @@ import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentW import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { PositionAffinity } from 'vs/editor/common/model'; -import { InlineEditWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; +import { GhostTextWidget } from 'vs/editor/contrib/inlineEdit/browser/ghostTextWidget'; import { MenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; @@ -27,12 +27,12 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class InlineEditHintsWidget extends Disposable { - private readonly alwaysShowToolbar = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).showToolbar === 'always'); + private readonly alwaysShowToolbar = observableFromEvent(this, this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).showToolbar === 'always'); private sessionPosition: Position | undefined = undefined; private readonly position = derived(this, reader => { - const ghostText = this.model.read(reader)?.widget.model.ghostText.read(reader); + const ghostText = this.model.read(reader)?.model.ghostText.read(reader); if (!this.alwaysShowToolbar.read(reader) || !ghostText || ghostText.parts.length === 0) { this.sessionPosition = undefined; @@ -51,7 +51,7 @@ export class InlineEditHintsWidget extends Disposable { constructor( private readonly editor: ICodeEditor, - private readonly model: IObservable, + private readonly model: IObservable, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget.css b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget.css new file mode 100644 index 00000000..bc7e553e --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget.css @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .inlineEditSideBySide { + z-index: 39; + color: var(--vscode-editorHoverWidget-foreground); + background-color: var(--vscode-editorHoverWidget-background); + border: 1px solid var(--vscode-editorHoverWidget-border); + white-space: pre; +} diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget.ts b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget.ts new file mode 100644 index 00000000..777a8b15 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdit/browser/inlineEditSideBySideWidget.ts @@ -0,0 +1,355 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { $ } from 'vs/base/browser/dom'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, ObservablePromise, autorun, autorunWithStore, derived, observableSignalFromEvent } from 'vs/base/common/observable'; +import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; +import { URI } from 'vs/base/common/uri'; +import 'vs/css!./inlineEditSideBySideWidget'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { observableCodeEditor } from 'vs/editor/browser/observableCodeEditor'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; +import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; +import { diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackgroundWithIndicator, diffWholeLineAddDecoration, diffWholeLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditor/registrations.contribution'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { IInlineEdit } from 'vs/editor/common/languages'; +import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +function* range(start: number, end: number, step = 1) { + if (end === undefined) { [end, start] = [start, 0]; } + for (let n = start; n < end; n += step) { yield n; } +} + +function removeIndentation(lines: string[]) { + const indentation = lines[0].match(/^\s*/)?.[0] ?? ''; + const length = indentation.length; + + return { + text: lines.map(l => l.replace(new RegExp('^' + indentation), '')), + shift: length + }; +} + +type Pos = { + top: number; + left: Position; +}; + +export class InlineEditSideBySideWidget extends Disposable { + private static _modelId = 0; + private static _createUniqueUri(): URI { + return URI.from({ scheme: 'inline-edit-widget', path: new Date().toString() + String(InlineEditSideBySideWidget._modelId++) }); + } + + private readonly _position = derived(this, reader => { + const ghostText = this._model.read(reader); + + if (!ghostText || ghostText.text.length === 0) { + return null; + } + if (ghostText.range.startLineNumber === ghostText.range.endLineNumber && !(ghostText.range.startColumn === ghostText.range.endColumn && ghostText.range.startColumn === 1)) { + //for inner-line suggestions we still want to use minimal ghost text + return null; + } + const editorModel = this._editor.getModel(); + if (!editorModel) { + return null; + } + const lines = Array.from(range(ghostText.range.startLineNumber, ghostText.range.endLineNumber + 1)); + const lengths = lines.map(lineNumber => editorModel.getLineLastNonWhitespaceColumn(lineNumber)); + const maxColumn = Math.max(...lengths); + const lineOfMaxColumn = lines[lengths.indexOf(maxColumn)]; + + const position = new Position(lineOfMaxColumn, maxColumn); + const pos = { + top: ghostText.range.startLineNumber, + left: position + }; + + return pos; + }); + + private readonly _text = derived(this, reader => { + const ghostText = this._model.read(reader); + if (!ghostText) { + return { text: '', shift: 0 }; + } + const t = removeIndentation(ghostText.text.split('\n')); + return { + text: t.text.join('\n'), + shift: t.shift + }; + }); + + + private readonly _originalModel = derivedDisposable(() => this._modelService.createModel('', null, InlineEditSideBySideWidget._createUniqueUri())).keepObserved(this._store); + private readonly _modifiedModel = derivedDisposable(() => this._modelService.createModel('', null, InlineEditSideBySideWidget._createUniqueUri())).keepObserved(this._store); + + private readonly _diff = derived(this, reader => { + return this._diffPromise.read(reader)?.promiseResult.read(reader)?.data; + }); + + private readonly _diffPromise = derived(this, reader => { + const ghostText = this._model.read(reader); + if (!ghostText) { + return; + } + const editorModel = this._editor.getModel(); + if (!editorModel) { + return; + } + const originalText = removeIndentation(editorModel.getValueInRange(ghostText.range).split('\n')).text.join('\n'); + const modifiedText = removeIndentation(ghostText.text.split('\n')).text.join('\n'); + this._originalModel.get().setValue(originalText); + this._modifiedModel.get().setValue(modifiedText); + const d = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' }); + return ObservablePromise.fromFn(async () => { + const result = await d.computeDiff(this._originalModel.get(), this._modifiedModel.get(), { + computeMoves: false, + ignoreTrimWhitespace: false, + maxComputationTimeMs: 1000, + }, CancellationToken.None); + + if (result.identical) { + return undefined; + } + + return result.changes; + }); + }); + + constructor( + private readonly _editor: ICodeEditor, + private readonly _model: IObservable, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IDiffProviderFactoryService private readonly _diffProviderFactoryService: IDiffProviderFactoryService, + @IModelService private readonly _modelService: IModelService, + ) { + super(); + + this._register(autorunWithStore((reader, store) => { + /** @description setup content widget */ + const model = this._model.read(reader); + if (!model) { + return; + } + if (this._position.get() === null) { + return; + } + const contentWidget = store.add(this._instantiationService.createInstance( + InlineEditSideBySideContentWidget, + this._editor, + this._position, + this._text.map(t => t.text), + this._text.map(t => t.shift), + this._diff + )); + _editor.addOverlayWidget(contentWidget); + store.add(toDisposable(() => _editor.removeOverlayWidget(contentWidget))); + })); + } +} + +class InlineEditSideBySideContentWidget extends Disposable implements IOverlayWidget { + private static _dropDownVisible = false; + public static get dropDownVisible() { return this._dropDownVisible; } + + private static id = 0; + + private readonly id = `InlineEditSideBySideContentWidget${InlineEditSideBySideContentWidget.id++}`; + public readonly allowEditorOverflow = false; + + private readonly _nodes = $('div.inlineEditSideBySide', undefined); + + private readonly _scrollChanged = observableSignalFromEvent('editor.onDidScrollChange', this._editor.onDidScrollChange); + + private readonly _previewEditor = this._register(this._instantiationService.createInstance( + EmbeddedCodeEditorWidget, + this._nodes, + { + glyphMargin: false, + lineNumbers: 'off', + minimap: { enabled: false }, + guides: { + indentation: false, + bracketPairs: false, + bracketPairsHorizontal: false, + highlightActiveIndentation: false, + }, + folding: false, + selectOnLineNumbers: false, + selectionHighlight: false, + columnSelection: false, + overviewRulerBorder: false, + overviewRulerLanes: 0, + lineDecorationsWidth: 0, + lineNumbersMinChars: 0, + scrollbar: { vertical: 'hidden', horizontal: 'hidden', alwaysConsumeMouseWheel: false, handleMouseWheel: false }, + readOnly: true, + wordWrap: 'off', + wordWrapOverride1: 'off', + wordWrapOverride2: 'off', + wrappingIndent: 'none', + wrappingStrategy: undefined, + }, + { contributions: [], isSimpleWidget: true }, + this._editor + )); + + private readonly _previewEditorObs = observableCodeEditor(this._previewEditor); + private readonly _editorObs = observableCodeEditor(this._editor); + + private readonly _previewTextModel = this._register(this._instantiationService.createInstance( + TextModel, + '', + this._editor.getModel()?.getLanguageId() ?? PLAINTEXT_LANGUAGE_ID, + TextModel.DEFAULT_CREATION_OPTIONS, + null + )); + + private readonly _setText = derived(reader => { + const edit = this._text.read(reader); + if (!edit) { return; } + this._previewTextModel.setValue(edit); + }).recomputeInitiallyAndOnChange(this._store); + + + private readonly _decorations = derived(this, (reader) => { + this._setText.read(reader); + const position = this._position.read(reader); + if (!position) { return { org: [], mod: [] }; } + const diff = this._diff.read(reader); + if (!diff) { return { org: [], mod: [] }; } + + const originalDecorations: IModelDeltaDecoration[] = []; + const modifiedDecorations: IModelDeltaDecoration[] = []; + + if (diff.length === 1 && diff[0].innerChanges![0].modifiedRange.equalsRange(this._previewTextModel.getFullModelRange())) { + return { org: [], mod: [] }; + } + const shift = this._shift.get(); + + const moveRange = (range: IRange) => { + return new Range(range.startLineNumber + position.top - 1, range.startColumn + shift, range.endLineNumber + position.top - 1, range.endColumn + shift); + }; + + for (const m of diff) { + if (!m.original.isEmpty) { + originalDecorations.push({ range: moveRange(m.original.toInclusiveRange()!), options: diffLineDeleteDecorationBackgroundWithIndicator }); + } + if (!m.modified.isEmpty) { + modifiedDecorations.push({ range: m.modified.toInclusiveRange()!, options: diffLineAddDecorationBackgroundWithIndicator }); + } + + if (m.modified.isEmpty || m.original.isEmpty) { + if (!m.original.isEmpty) { + originalDecorations.push({ range: moveRange(m.original.toInclusiveRange()!), options: diffWholeLineDeleteDecoration }); + } + if (!m.modified.isEmpty) { + modifiedDecorations.push({ range: m.modified.toInclusiveRange()!, options: diffWholeLineAddDecoration }); + } + } else { + for (const i of m.innerChanges || []) { + // Don't show empty markers outside the line range + if (m.original.contains(i.originalRange.startLineNumber)) { + originalDecorations.push({ range: moveRange(i.originalRange), options: i.originalRange.isEmpty() ? diffDeleteDecorationEmpty : diffDeleteDecoration }); + } + if (m.modified.contains(i.modifiedRange.startLineNumber)) { + modifiedDecorations.push({ range: i.modifiedRange, options: i.modifiedRange.isEmpty() ? diffAddDecorationEmpty : diffAddDecoration }); + } + } + } + } + + return { org: originalDecorations, mod: modifiedDecorations }; + }); + + private readonly _originalDecorations = derived(this, reader => { + return this._decorations.read(reader).org; + }); + + private readonly _modifiedDecorations = derived(this, reader => { + return this._decorations.read(reader).mod; + }); + + constructor( + private readonly _editor: ICodeEditor, + private readonly _position: IObservable, + private readonly _text: IObservable, + private readonly _shift: IObservable, + private readonly _diff: IObservable, + + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + super(); + + this._previewEditor.setModel(this._previewTextModel); + + this._register(this._editorObs.setDecorations(this._originalDecorations)); + this._register(this._previewEditorObs.setDecorations(this._modifiedDecorations)); + + this._register(autorun(reader => { + const width = this._previewEditorObs.contentWidth.read(reader); + const lines = this._text.read(reader).split('\n').length - 1; + const height = this._editor.getOption(EditorOption.lineHeight) * lines; + if (width <= 0) { + return; + } + this._previewEditor.layout({ height: height, width: width }); + })); + + this._register(autorun(reader => { + /** @description update position */ + this._position.read(reader); + this._editor.layoutOverlayWidget(this); + })); + + this._register(autorun(reader => { + /** @description scroll change */ + this._scrollChanged.read(reader); + const position = this._position.read(reader); + if (!position) { + return; + } + this._editor.layoutOverlayWidget(this); + })); + } + + getId(): string { return this.id; } + + getDomNode(): HTMLElement { + return this._nodes; + } + + getPosition(): IOverlayWidgetPosition | null { + const position = this._position.get(); + if (!position) { + return null; + } + const layoutInfo = this._editor.getLayoutInfo(); + const visibPos = this._editor.getScrolledVisiblePosition(new Position(position.top, 1)); + if (!visibPos) { + return null; + } + const top = visibPos.top - 1; //-1 to offset the border width + const offset = this._editor.getOffsetForColumn(position.left.lineNumber, position.left.column); + const left = layoutInfo.contentLeft + offset + 10; + return { + preference: { + left, + top, + } + }; + } +} diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/commands.ts b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/commands.ts new file mode 100644 index 00000000..ec7255fd --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/commands.ts @@ -0,0 +1,185 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from 'vs/base/common/codicons'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { transaction } from 'vs/base/common/observable'; +import { asyncTransaction } from 'vs/base/common/observableInternal/base'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { inlineEditAcceptId, inlineEditVisible, showNextInlineEditActionId, showPreviousInlineEditActionId } from 'vs/editor/contrib/inlineEdits/browser/consts'; +import { InlineEditsController } from 'vs/editor/contrib/inlineEdits/browser/inlineEditsController'; +import * as nls from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; + + +function labelAndAlias(str: nls.ILocalizedString): { label: string; alias: string } { + return { + label: str.value, + alias: str.original, + }; +} + +export class ShowNextInlineEditAction extends EditorAction { + public static ID = showNextInlineEditActionId; + constructor() { + super({ + id: ShowNextInlineEditAction.ID, + ...labelAndAlias(nls.localize2('action.inlineEdits.showNext', "Show Next Inline Edit")), + precondition: ContextKeyExpr.and(EditorContextKeys.writable, inlineEditVisible), + kbOpts: { + weight: 100, + primary: KeyMod.Alt | KeyCode.BracketRight, + }, + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditsController.get(editor); + controller?.model.get()?.next(); + } +} + +export class ShowPreviousInlineEditAction extends EditorAction { + public static ID = showPreviousInlineEditActionId; + constructor() { + super({ + id: ShowPreviousInlineEditAction.ID, + ...labelAndAlias(nls.localize2('action.inlineEdits.showPrevious', "Show Previous Inline Edit")), + precondition: ContextKeyExpr.and(EditorContextKeys.writable, inlineEditVisible), + kbOpts: { + weight: 100, + primary: KeyMod.Alt | KeyCode.BracketLeft, + }, + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditsController.get(editor); + controller?.model.get()?.previous(); + } +} + +export class TriggerInlineEditAction extends EditorAction { + constructor() { + super({ + id: 'editor.action.inlineEdits.trigger', + ...labelAndAlias(nls.localize2('action.inlineEdits.trigger', "Trigger Inline Edit")), + precondition: EditorContextKeys.writable + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditsController.get(editor); + await asyncTransaction(async tx => { + /** @description triggerExplicitly from command */ + await controller?.model.get()?.triggerExplicitly(tx); + }); + } +} + +export class AcceptInlineEdit extends EditorAction { + constructor() { + super({ + id: inlineEditAcceptId, + ...labelAndAlias(nls.localize2('action.inlineEdits.accept', "Accept Inline Edit")), + precondition: inlineEditVisible, + menuOpts: { + menuId: MenuId.InlineEditsActions, + title: nls.localize('inlineEditsActions', "Accept Inline Edit"), + group: 'primary', + order: 1, + icon: Codicon.check, + }, + kbOpts: { + primary: KeyMod.CtrlCmd | KeyCode.Space, + weight: 20000, + kbExpr: inlineEditVisible, + } + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + if (editor instanceof EmbeddedCodeEditorWidget) { + editor = editor.getParentEditor(); + } + const controller = InlineEditsController.get(editor); + if (controller) { + controller.model.get()?.accept(controller.editor); + controller.editor.focus(); + } + } +} + +/* +TODO@hediet +export class PinInlineEdit extends EditorAction { + constructor() { + super({ + id: 'editor.action.inlineEdits.pin', + ...labelAndAlias(nls.localize2('action.inlineEdits.pin', "Pin Inline Edit")), + precondition: undefined, + kbOpts: { + primary: KeyMod.Shift | KeyCode.Space, + weight: 20000, + } + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditsController.get(editor); + if (controller) { + controller.model.get()?.togglePin(); + } + } +} + +MenuRegistry.appendMenuItem(MenuId.InlineEditsActions, { + command: { + id: 'editor.action.inlineEdits.pin', + title: nls.localize('Pin', "Pin"), + icon: Codicon.pin, + }, + group: 'primary', + order: 1, + when: isPinnedContextKey.negate(), +}); + +MenuRegistry.appendMenuItem(MenuId.InlineEditsActions, { + command: { + id: 'editor.action.inlineEdits.unpin', + title: nls.localize('Unpin', "Unpin"), + icon: Codicon.pinned, + }, + group: 'primary', + order: 1, + when: isPinnedContextKey, +});*/ + +export class HideInlineEdit extends EditorAction { + public static ID = 'editor.action.inlineEdits.hide'; + + constructor() { + super({ + id: HideInlineEdit.ID, + ...labelAndAlias(nls.localize2('action.inlineEdits.hide', "Hide Inline Edit")), + precondition: inlineEditVisible, + kbOpts: { + weight: 100, + primary: KeyCode.Escape, + } + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditsController.get(editor); + transaction(tx => { + controller?.model.get()?.stop(tx); + }); + } +} diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/consts.ts b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/consts.ts new file mode 100644 index 00000000..9ad19e98 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/consts.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const inlineEditAcceptId = 'editor.action.inlineEdits.accept'; + +export const showPreviousInlineEditActionId = 'editor.action.inlineEdits.showPrevious'; + +export const showNextInlineEditActionId = 'editor.action.inlineEdits.showNext'; + +export const inlineEditVisible = new RawContextKey('inlineEditsVisible', false, localize('inlineEditsVisible', "Whether an inline edit is visible")); +export const isPinnedContextKey = new RawContextKey('inlineEditsIsPinned', false, localize('isPinned', "Whether an inline edit is visible")); diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEdits.contribution.ts b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEdits.contribution.ts new file mode 100644 index 00000000..ae8b7182 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEdits.contribution.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorContributionInstantiation, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { + TriggerInlineEditAction, ShowNextInlineEditAction, ShowPreviousInlineEditAction, + AcceptInlineEdit, HideInlineEdit, +} from 'vs/editor/contrib/inlineEdits/browser/commands'; +import { InlineEditsController } from 'vs/editor/contrib/inlineEdits/browser/inlineEditsController'; + +registerEditorContribution(InlineEditsController.ID, InlineEditsController, EditorContributionInstantiation.Eventually); + +registerEditorAction(TriggerInlineEditAction); +registerEditorAction(ShowNextInlineEditAction); +registerEditorAction(ShowPreviousInlineEditAction); +registerEditorAction(AcceptInlineEdit); +registerEditorAction(HideInlineEdit); diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsController.ts b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsController.ts new file mode 100644 index 00000000..9055ec56 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsController.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { derived, derivedObservableWithCache, IReader, ISettableObservable, observableValue } from 'vs/base/common/observable'; +import { derivedDisposable, derivedWithSetter } from 'vs/base/common/observableInternal/derived'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { observableCodeEditor } from 'vs/editor/browser/observableCodeEditor'; +import { readHotReloadableExport } from 'vs/base/common/hotReloadHelpers'; +import { Selection } from 'vs/editor/common/core/selection'; +import { ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { inlineEditVisible, isPinnedContextKey } from 'vs/editor/contrib/inlineEdits/browser/consts'; +import { InlineEditsModel } from 'vs/editor/contrib/inlineEdits/browser/inlineEditsModel'; +import { InlineEditsWidget } from 'vs/editor/contrib/inlineEdits/browser/inlineEditsWidget'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { bindContextKey, observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils'; + +export class InlineEditsController extends Disposable { + static ID = 'editor.contrib.inlineEditsController'; + + public static get(editor: ICodeEditor): InlineEditsController | null { + return editor.getContribution(InlineEditsController.ID); + } + + private readonly _enabled = observableConfigValue('editor.inlineEdits.enabled', false, this._configurationService); + private readonly _editorObs = observableCodeEditor(this.editor); + private readonly _selection = derived(this, reader => this._editorObs.cursorSelection.read(reader) ?? new Selection(1, 1, 1, 1)); + + private readonly _debounceValue = this._debounceService.for( + this._languageFeaturesService.inlineCompletionsProvider, + 'InlineEditsDebounce', + { min: 50, max: 50 } + ); + + public readonly model = derivedDisposable(this, reader => { + if (!this._enabled.read(reader)) { + return undefined; + } + if (this._editorObs.isReadonly.read(reader)) { return undefined; } + const textModel = this._editorObs.model.read(reader); + if (!textModel) { return undefined; } + + const model: InlineEditsModel = this._instantiationService.createInstance( + readHotReloadableExport(InlineEditsModel, reader), + textModel, + this._editorObs.versionId, + this._selection, + this._debounceValue, + ); + + return model; + }); + + private readonly _hadInlineEdit = derivedObservableWithCache(this, (reader, lastValue) => lastValue || this.model.read(reader)?.inlineEdit.read(reader) !== undefined); + + protected readonly _widget = derivedDisposable(this, reader => { + if (!this._hadInlineEdit.read(reader)) { return undefined; } + + return this._instantiationService.createInstance( + readHotReloadableExport(InlineEditsWidget, reader), + this.editor, + this.model.map((m, reader) => m?.inlineEdit.read(reader)), + flattenSettableObservable((reader) => this.model.read(reader)?.userPrompt ?? observableValue('empty', '')), + ); + }); + + constructor( + public readonly editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @ILanguageFeatureDebounceService private readonly _debounceService: ILanguageFeatureDebounceService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + super(); + + this._register(bindContextKey(inlineEditVisible, this._contextKeyService, r => !!this.model.read(r)?.inlineEdit.read(r))); + this._register(bindContextKey(isPinnedContextKey, this._contextKeyService, r => !!this.model.read(r)?.isPinned.read(r))); + + this.model.recomputeInitiallyAndOnChange(this._store); + this._widget.recomputeInitiallyAndOnChange(this._store); + } +} + +function flattenSettableObservable(fn: (reader: IReader | undefined) => ISettableObservable): ISettableObservable { + return derivedWithSetter(undefined, reader => { + const obs = fn(reader); + return obs.read(reader); + }, (value, tx) => { + fn(undefined).set(value, tx); + }); +} diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsModel.ts b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsModel.ts new file mode 100644 index 00000000..9c03abe6 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsModel.ts @@ -0,0 +1,289 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { timeout } from 'vs/base/common/async'; +import { CancellationToken, cancelOnDispose } from 'vs/base/common/cancellation'; +import { itemsEquals, structuralEquals } from 'vs/base/common/equals'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, ISettableObservable, ITransaction, ObservablePromise, derived, derivedHandleChanges, derivedOpts, disposableObservableValue, observableSignal, observableValue, recomputeInitiallyAndOnChange, subtransaction } from 'vs/base/common/observable'; +import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; +import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { Command, InlineCompletionContext, InlineCompletionTriggerKind } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; +import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; +import { InlineCompletionItem, InlineCompletionProviderResult, provideInlineCompletions } from 'vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions'; +import { InlineEdit } from 'vs/editor/contrib/inlineEdits/browser/inlineEditsWidget'; + +export class InlineEditsModel extends Disposable { + private static _modelId = 0; + private static _createUniqueUri(): URI { + return URI.from({ scheme: 'inline-edits', path: new Date().toString() + String(InlineEditsModel._modelId++) }); + } + + private readonly _forceUpdateExplicitlySignal = observableSignal(this); + + // We use a semantic id to keep the same inline completion selected even if the provider reorders the completions. + private readonly _selectedInlineCompletionId = observableValue(this, undefined); + + private readonly _isActive = observableValue(this, false); + + private readonly _originalModel = derivedDisposable(() => this._modelService.createModel('', null, InlineEditsModel._createUniqueUri())).keepObserved(this._store); + private readonly _modifiedModel = derivedDisposable(() => this._modelService.createModel('', null, InlineEditsModel._createUniqueUri())).keepObserved(this._store); + + private readonly _pinnedRange = new TrackedRange(this.textModel, this._textModelVersionId); + + public readonly isPinned = this._pinnedRange.range.map(range => !!range); + + public readonly userPrompt: ISettableObservable = observableValue(this, undefined); + + constructor( + public readonly textModel: ITextModel, + public readonly _textModelVersionId: IObservable, + private readonly _selection: IObservable, + protected readonly _debounceValue: IFeatureDebounceInformation, + @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, + @IDiffProviderFactoryService private readonly _diffProviderFactoryService: IDiffProviderFactoryService, + @IModelService private readonly _modelService: IModelService, + ) { + super(); + + this._register(recomputeInitiallyAndOnChange(this._fetchInlineEditsPromise)); + } + + public readonly inlineEdit = derived(this, reader => { + return this._inlineEdit.read(reader)?.promiseResult.read(reader)?.data; + }); + + public readonly _inlineEdit = derived | undefined>(this, reader => { + const edit = this.selectedInlineEdit.read(reader); + if (!edit) { return undefined; } + const range = edit.inlineCompletion.range; + if (edit.inlineCompletion.insertText.trim() === '') { + return undefined; + } + + let newLines = edit.inlineCompletion.insertText.split(/\r\n|\r|\n/); + + function removeIndentation(lines: string[]): string[] { + const indentation = lines[0].match(/^\s*/)?.[0] ?? ''; + return lines.map(l => l.replace(new RegExp('^' + indentation), '')); + } + newLines = removeIndentation(newLines); + + const existing = this.textModel.getValueInRange(range); + let existingLines = existing.split(/\r\n|\r|\n/); + existingLines = removeIndentation(existingLines); + this._originalModel.get().setValue(existingLines.join('\n')); + this._modifiedModel.get().setValue(newLines.join('\n')); + + const d = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' }); + return ObservablePromise.fromFn(async () => { + const result = await d.computeDiff(this._originalModel.get(), this._modifiedModel.get(), { + computeMoves: false, + ignoreTrimWhitespace: false, + maxComputationTimeMs: 1000, + }, CancellationToken.None); + + if (result.identical) { + return undefined; + } + + return new InlineEdit(LineRange.fromRangeInclusive(range), removeIndentation(newLines), result.changes); + }); + }); + + private readonly _fetchStore = this._register(new DisposableStore()); + + private readonly _inlineEditsFetchResult = disposableObservableValue(this, undefined); + private readonly _inlineEdits = derivedOpts({ owner: this, equalsFn: structuralEquals }, reader => { + return this._inlineEditsFetchResult.read(reader)?.completions.map(c => new InlineEditData(c)) ?? []; + }); + + private readonly _fetchInlineEditsPromise = derivedHandleChanges({ + owner: this, + createEmptyChangeSummary: () => ({ + inlineCompletionTriggerKind: InlineCompletionTriggerKind.Automatic + }), + handleChange: (ctx, changeSummary) => { + /** @description fetch inline completions */ + if (ctx.didChange(this._forceUpdateExplicitlySignal)) { + changeSummary.inlineCompletionTriggerKind = InlineCompletionTriggerKind.Explicit; + } + return true; + }, + }, async (reader, changeSummary) => { + this._fetchStore.clear(); + this._forceUpdateExplicitlySignal.read(reader); + /*if (!this._isActive.read(reader)) { + return undefined; + }*/ + this._textModelVersionId.read(reader); + + function mapValue(value: T, fn: (value: T) => TOut): TOut { + return fn(value); + } + + const selection = this._pinnedRange.range.read(reader) ?? mapValue(this._selection.read(reader), v => v.isEmpty() ? undefined : v); + if (!selection) { + this._inlineEditsFetchResult.set(undefined, undefined); + this.userPrompt.set(undefined, undefined); + return undefined; + } + const context: InlineCompletionContext = { + triggerKind: changeSummary.inlineCompletionTriggerKind, + selectedSuggestionInfo: undefined, + userPrompt: this.userPrompt.read(reader), + }; + + const token = cancelOnDispose(this._fetchStore); + await timeout(200, token); + const result = await provideInlineCompletions(this.languageFeaturesService.inlineCompletionsProvider, selection, this.textModel, context, token); + if (token.isCancellationRequested) { + return; + } + + this._inlineEditsFetchResult.set(result, undefined); + }); + + public async trigger(tx?: ITransaction): Promise { + this._isActive.set(true, tx); + await this._fetchInlineEditsPromise.get(); + } + + public async triggerExplicitly(tx?: ITransaction): Promise { + subtransaction(tx, tx => { + this._isActive.set(true, tx); + this._forceUpdateExplicitlySignal.trigger(tx); + }); + await this._fetchInlineEditsPromise.get(); + } + + public stop(tx?: ITransaction): void { + subtransaction(tx, tx => { + this.userPrompt.set(undefined, tx); + this._isActive.set(false, tx); + this._inlineEditsFetchResult.set(undefined, tx); + this._pinnedRange.setRange(undefined, tx); + //this._source.clear(tx); + }); + } + + private readonly _filteredInlineEditItems = derivedOpts({ owner: this, equalsFn: itemsEquals() }, reader => { + return this._inlineEdits.read(reader); + }); + + public readonly selectedInlineCompletionIndex = derived(this, (reader) => { + const selectedInlineCompletionId = this._selectedInlineCompletionId.read(reader); + const filteredCompletions = this._filteredInlineEditItems.read(reader); + const idx = this._selectedInlineCompletionId === undefined ? -1 + : filteredCompletions.findIndex(v => v.semanticId === selectedInlineCompletionId); + if (idx === -1) { + // Reset the selection so that the selection does not jump back when it appears again + this._selectedInlineCompletionId.set(undefined, undefined); + return 0; + } + return idx; + }); + + public readonly selectedInlineEdit = derived(this, (reader) => { + const filteredCompletions = this._filteredInlineEditItems.read(reader); + const idx = this.selectedInlineCompletionIndex.read(reader); + return filteredCompletions[idx]; + }); + + public readonly activeCommands = derivedOpts({ owner: this, equalsFn: itemsEquals() }, + r => this.selectedInlineEdit.read(r)?.inlineCompletion.source.inlineCompletions.commands ?? [] + ); + + private async _deltaSelectedInlineCompletionIndex(delta: 1 | -1): Promise { + await this.triggerExplicitly(); + + const completions = this._filteredInlineEditItems.get() || []; + if (completions.length > 0) { + const newIdx = (this.selectedInlineCompletionIndex.get() + delta + completions.length) % completions.length; + this._selectedInlineCompletionId.set(completions[newIdx].semanticId, undefined); + } else { + this._selectedInlineCompletionId.set(undefined, undefined); + } + } + + public async next(): Promise { + await this._deltaSelectedInlineCompletionIndex(1); + } + + public async previous(): Promise { + await this._deltaSelectedInlineCompletionIndex(-1); + } + + public togglePin(): void { + if (this.isPinned.get()) { + this._pinnedRange.setRange(undefined, undefined); + } else { + this._pinnedRange.setRange(this._selection.get(), undefined); + } + } + + public async accept(editor: ICodeEditor): Promise { + if (editor.getModel() !== this.textModel) { + throw new BugIndicatingError(); + } + const edit = this.selectedInlineEdit.get(); + if (!edit) { + return; + } + + editor.pushUndoStop(); + editor.executeEdits( + 'inlineSuggestion.accept', + [ + edit.inlineCompletion.toSingleTextEdit().toSingleEditOperation() + ] + ); + this.stop(); + } +} + +class InlineEditData { + public readonly semanticId = this.inlineCompletion.hash(); + + constructor(public readonly inlineCompletion: InlineCompletionItem) { + + } +} + +class TrackedRange extends Disposable { + private readonly _decorations = observableValue(this, []); + + constructor( + private readonly _textModel: ITextModel, + private readonly _versionId: IObservable, + ) { + super(); + this._register(toDisposable(() => { + this._textModel.deltaDecorations(this._decorations.get(), []); + })); + } + + setRange(range: Range | undefined, tx: ITransaction | undefined): void { + this._decorations.set(this._textModel.deltaDecorations(this._decorations.get(), range ? [{ range, options: { description: 'trackedRange' } }] : []), tx); + } + + public readonly range = derived(this, reader => { + this._versionId.read(reader); + const deco = this._decorations.read(reader)[0]; + if (!deco) { return null; } + + return this._textModel.getDecorationRange(deco) ?? null; + }); +} diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsWidget.css b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsWidget.css new file mode 100644 index 00000000..68910c88 --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsWidget.css @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor div.inline-edits-widget { + --widget-color: var(--vscode-notifications-background); + + .promptEditor .monaco-editor { + --vscode-editor-placeholder-foreground: var(--vscode-editorGhostText-foreground); + } + + .toolbar, .promptEditor { + opacity: 0; + transition: opacity 0.2s ease-in-out; + } + &:hover, &.focused { + .toolbar, .promptEditor { + opacity: 1; + } + } + + .preview .monaco-editor { + + .mtk1 { + /*color: rgba(215, 215, 215, 0.452);*/ + color: var(--vscode-editorGhostText-foreground); + } + .view-overlays .current-line-exact { + border: none; + } + + .current-line-margin { + border: none; + } + + --vscode-editor-background: var(--widget-color); + } + + svg { + .gradient-start { + stop-color: var(--vscode-editor-background); + } + + .gradient-stop { + stop-color: var(--widget-color); + } + } +} diff --git a/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsWidget.ts b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsWidget.ts new file mode 100644 index 00000000..331520cf --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/inlineEdits/browser/inlineEditsWidget.ts @@ -0,0 +1,400 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { h, svgElem } from 'vs/base/browser/dom'; +import { DEFAULT_FONT_FAMILY } from 'vs/base/browser/fonts'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { autorun, constObservable, derived, IObservable, ISettableObservable } from 'vs/base/common/observable'; +import { derivedWithSetter } from 'vs/base/common/observableInternal/derived'; +import 'vs/css!./inlineEditsWidget'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { observableCodeEditor } from 'vs/editor/browser/observableCodeEditor'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; +import { diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackgroundWithIndicator, diffWholeLineAddDecoration, diffWholeLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditor/registrations.contribution'; +import { appendRemoveOnDispose, applyStyle } from 'vs/editor/browser/widget/diffEditor/utils'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { LineRange } from 'vs/editor/common/core/lineRange'; +import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; +import { PlaceholderTextContribution } from '../../placeholderText/browser/placeholderTextContribution'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export class InlineEdit { + constructor( + public readonly range: LineRange, + public readonly newLines: string[], + public readonly changes: readonly DetailedLineRangeMapping[], + ) { + + } +} + +export class InlineEditsWidget extends Disposable { + private readonly _editorObs = observableCodeEditor(this._editor); + + private readonly _elements = h('div.inline-edits-widget', { + style: { + position: 'absolute', + overflow: 'visible', + top: '0px', + left: '0px', + }, + }, [ + h('div@editorContainer', { style: { position: 'absolute', top: '0px', left: '0px', width: '500px', height: '500px', } }, [ + h('div.toolbar@toolbar', { style: { position: 'absolute', top: '-25px', left: '0px' } }), + h('div.promptEditor@promptEditor', { style: { position: 'absolute', top: '-25px', left: '80px', width: '300px', height: '22px' } }), + h('div.preview@editor', { style: { position: 'absolute', top: '0px', left: '0px' } }), + ]), + svgElem('svg', { style: { overflow: 'visible', pointerEvents: 'none' }, }, [ + svgElem('defs', [ + svgElem('linearGradient', { + id: 'Gradient2', + x1: '0', + y1: '0', + x2: '1', + y2: '0', + }, [ + /*svgElem('stop', { offset: '0%', class: 'gradient-start', }), + svgElem('stop', { offset: '0%', class: 'gradient-start', }), + svgElem('stop', { offset: '20%', class: 'gradient-stop', }),*/ + svgElem('stop', { offset: '0%', class: 'gradient-stop', }), + svgElem('stop', { offset: '100%', class: 'gradient-stop', }), + ]), + ]), + svgElem('path@path', { + d: '', + fill: 'url(#Gradient2)', + }), + ]), + ]); + + protected readonly _toolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.toolbar, MenuId.InlineEditsActions, { + toolbarOptions: { + primaryGroup: g => g.startsWith('primary'), + }, + })); + private readonly _previewTextModel = this._register(this._instantiationService.createInstance( + TextModel, + '', + PLAINTEXT_LANGUAGE_ID, + TextModel.DEFAULT_CREATION_OPTIONS, + null + )); + + private readonly _setText = derived(reader => { + const edit = this._edit.read(reader); + if (!edit) { return; } + this._previewTextModel.setValue(edit.newLines.join('\n')); + }).recomputeInitiallyAndOnChange(this._store); + + + private readonly _promptTextModel = this._register(this._instantiationService.createInstance( + TextModel, + '', + PLAINTEXT_LANGUAGE_ID, + TextModel.DEFAULT_CREATION_OPTIONS, + null + )); + private readonly _promptEditor = this._register(this._instantiationService.createInstance( + EmbeddedCodeEditorWidget, + this._elements.promptEditor, + { + glyphMargin: false, + lineNumbers: 'off', + minimap: { enabled: false }, + guides: { + indentation: false, + bracketPairs: false, + bracketPairsHorizontal: false, + highlightActiveIndentation: false, + }, + folding: false, + selectOnLineNumbers: false, + selectionHighlight: false, + columnSelection: false, + overviewRulerBorder: false, + overviewRulerLanes: 0, + lineDecorationsWidth: 0, + lineNumbersMinChars: 0, + placeholder: 'Describe the change you want...', + fontFamily: DEFAULT_FONT_FAMILY, + }, + { + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + SuggestController.ID, + PlaceholderTextContribution.ID, + ContextMenuController.ID, + ]), + isSimpleWidget: true + }, + this._editor + )); + + private readonly _previewEditor = this._register(this._instantiationService.createInstance( + EmbeddedCodeEditorWidget, + this._elements.editor, + { + glyphMargin: false, + lineNumbers: 'off', + minimap: { enabled: false }, + guides: { + indentation: false, + bracketPairs: false, + bracketPairsHorizontal: false, + highlightActiveIndentation: false, + }, + folding: false, + selectOnLineNumbers: false, + selectionHighlight: false, + columnSelection: false, + overviewRulerBorder: false, + overviewRulerLanes: 0, + lineDecorationsWidth: 0, + lineNumbersMinChars: 0, + }, + { contributions: [], }, + this._editor + )); + + private readonly _previewEditorObs = observableCodeEditor(this._previewEditor); + + private readonly _decorations = derived(this, (reader) => { + this._setText.read(reader); + const diff = this._edit.read(reader)?.changes; + if (!diff) { return []; } + + const originalDecorations: IModelDeltaDecoration[] = []; + const modifiedDecorations: IModelDeltaDecoration[] = []; + + if (diff.length === 1 && diff[0].innerChanges![0].modifiedRange.equalsRange(this._previewTextModel.getFullModelRange())) { + return []; + } + + for (const m of diff) { + if (!m.original.isEmpty) { + originalDecorations.push({ range: m.original.toInclusiveRange()!, options: diffLineDeleteDecorationBackgroundWithIndicator }); + } + if (!m.modified.isEmpty) { + modifiedDecorations.push({ range: m.modified.toInclusiveRange()!, options: diffLineAddDecorationBackgroundWithIndicator }); + } + + if (m.modified.isEmpty || m.original.isEmpty) { + if (!m.original.isEmpty) { + originalDecorations.push({ range: m.original.toInclusiveRange()!, options: diffWholeLineDeleteDecoration }); + } + if (!m.modified.isEmpty) { + modifiedDecorations.push({ range: m.modified.toInclusiveRange()!, options: diffWholeLineAddDecoration }); + } + } else { + for (const i of m.innerChanges || []) { + // Don't show empty markers outside the line range + if (m.original.contains(i.originalRange.startLineNumber)) { + originalDecorations.push({ range: i.originalRange, options: i.originalRange.isEmpty() ? diffDeleteDecorationEmpty : diffDeleteDecoration }); + } + if (m.modified.contains(i.modifiedRange.startLineNumber)) { + modifiedDecorations.push({ range: i.modifiedRange, options: i.modifiedRange.isEmpty() ? diffAddDecorationEmpty : diffAddDecoration }); + } + } + } + } + + return modifiedDecorations; + }); + + private readonly _layout1 = derived(this, reader => { + const model = this._editor.getModel()!; + const inlineEdit = this._edit.read(reader); + if (!inlineEdit) { return null; } + + const range = inlineEdit.range; + + let maxLeft = 0; + for (let i = range.startLineNumber; i < range.endLineNumberExclusive; i++) { + const column = model.getLineMaxColumn(i); + const left = this._editor.getOffsetForColumn(i, column); + maxLeft = Math.max(maxLeft, left); + } + + const layoutInfo = this._editor.getLayoutInfo(); + const contentLeft = layoutInfo.contentLeft; + + return { left: contentLeft + maxLeft }; + }); + + private readonly _layout = derived(this, (reader) => { + const inlineEdit = this._edit.read(reader); + if (!inlineEdit) { return null; } + + const range = inlineEdit.range; + + const scrollLeft = this._editorObs.scrollLeft.read(reader); + + const left = this._layout1.read(reader)!.left + 20 - scrollLeft; + + const selectionTop = this._editor.getTopForLineNumber(range.startLineNumber) - this._editorObs.scrollTop.read(reader); + const selectionBottom = this._editor.getTopForLineNumber(range.endLineNumberExclusive) - this._editorObs.scrollTop.read(reader); + + const topCode = new Point(left, selectionTop); + const bottomCode = new Point(left, selectionBottom); + const codeHeight = selectionBottom - selectionTop; + + const codeEditDist = 50; + const editHeight = this._editor.getOption(EditorOption.lineHeight) * inlineEdit.newLines.length; + const difference = codeHeight - editHeight; + const topEdit = new Point(left + codeEditDist, selectionTop + (difference / 2)); + const bottomEdit = new Point(left + codeEditDist, selectionBottom - (difference / 2)); + + return { + topCode, + bottomCode, + codeHeight, + topEdit, + bottomEdit, + editHeight, + }; + }); + + constructor( + private readonly _editor: ICodeEditor, + private readonly _edit: IObservable, + private readonly _userPrompt: ISettableObservable, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + super(); + const visible = derived(this, reader => this._edit.read(reader) !== undefined || this._userPrompt.read(reader) !== undefined); + this._register(applyStyle(this._elements.root, { + display: derived(this, reader => visible.read(reader) ? 'block' : 'none') + })); + + this._register(appendRemoveOnDispose(this._editor.getDomNode()!, this._elements.root)); + + this._register(observableCodeEditor(_editor).createOverlayWidget({ + domNode: this._elements.root, + position: constObservable(null), + allowEditorOverflow: false, + minContentWidthInPx: derived(reader => { + const x = this._layout1.read(reader)?.left; + if (x === undefined) { return 0; } + const width = this._previewEditorObs.contentWidth.read(reader); + return x + width; + }), + })); + + this._previewEditor.setModel(this._previewTextModel); + + this._register(this._previewEditorObs.setDecorations(this._decorations)); + + this._register(autorun(reader => { + const layoutInfo = this._layout.read(reader); + if (!layoutInfo) { return; } + + const { topCode, bottomCode, topEdit, bottomEdit, editHeight } = layoutInfo; + + const straightWidthCode = 10; + const straightWidthEdit = 0; + const bezierDist = 40; + + const path = new PathBuilder() + .moveTo(topCode) + .lineTo(topCode.deltaX(straightWidthCode)) + .curveTo( + topCode.deltaX(straightWidthCode + bezierDist), + topEdit.deltaX(-bezierDist - straightWidthEdit), + topEdit.deltaX(-straightWidthEdit), + ) + .lineTo(topEdit) + .lineTo(bottomEdit) + .lineTo(bottomEdit.deltaX(-straightWidthEdit)) + .curveTo( + bottomEdit.deltaX(-bezierDist - straightWidthEdit), + bottomCode.deltaX(straightWidthCode + bezierDist), + bottomCode.deltaX(straightWidthCode), + ) + .lineTo(bottomCode) + .build(); + + + this._elements.path.setAttribute('d', path); + + this._elements.editorContainer.style.top = `${topEdit.y}px`; + this._elements.editorContainer.style.left = `${topEdit.x}px`; + this._elements.editorContainer.style.height = `${editHeight}px`; + + const width = this._previewEditorObs.contentWidth.read(reader); + this._previewEditor.layout({ height: editHeight, width }); + })); + + this._promptEditor.setModel(this._promptTextModel); + this._promptEditor.layout(); + this._register(createTwoWaySync(mapSettableObservable(this._userPrompt, v => v ?? '', v => v), observableCodeEditor(this._promptEditor).value)); + + this._register(autorun(reader => { + const isFocused = observableCodeEditor(this._promptEditor).isFocused.read(reader); + this._elements.root.classList.toggle('focused', isFocused); + })); + } +} + +function mapSettableObservable(obs: ISettableObservable, fn1: (value: T) => T1, fn2: (value: T1) => T): ISettableObservable { + return derivedWithSetter(undefined, reader => fn1(obs.read(reader)), (value, tx) => obs.set(fn2(value), tx)); +} + +class Point { + constructor( + public readonly x: number, + public readonly y: number, + ) { } + + public add(other: Point): Point { + return new Point(this.x + other.x, this.y + other.y); + } + + public deltaX(delta: number): Point { + return new Point(this.x + delta, this.y); + } +} + +class PathBuilder { + private _data: string = ''; + + public moveTo(point: Point): this { + this._data += `M ${point.x} ${point.y} `; + return this; + } + + public lineTo(point: Point): this { + this._data += `L ${point.x} ${point.y} `; + return this; + } + + public curveTo(cp1: Point, cp2: Point, to: Point): this { + this._data += `C ${cp1.x} ${cp1.y} ${cp2.x} ${cp2.y} ${to.x} ${to.y} `; + return this; + } + + public build(): string { + return this._data; + } +} + +function createTwoWaySync(main: ISettableObservable, target: ISettableObservable): IDisposable { + const store = new DisposableStore(); + store.add(autorun(reader => { + const value = main.read(reader); + target.set(value, undefined); + })); + store.add(autorun(reader => { + const value = target.read(reader); + main.set(value, undefined); + })); + return store; +} diff --git a/patched-vscode/src/vs/editor/contrib/inlineProgress/browser/inlineProgress.ts b/patched-vscode/src/vs/editor/contrib/inlineProgress/browser/inlineProgress.ts index db892ea6..25f273c1 100644 --- a/patched-vscode/src/vs/editor/contrib/inlineProgress/browser/inlineProgress.ts +++ b/patched-vscode/src/vs/editor/contrib/inlineProgress/browser/inlineProgress.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { CancelablePromise, disposableTimeout } from 'vs/base/common/async'; +import { disposableTimeout } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { noBreakWhitespace } from 'vs/base/common/strings'; @@ -114,13 +114,13 @@ export class InlineProgressManager extends Disposable { private readonly _showPromise = this._register(new MutableDisposable()); private readonly _currentDecorations: IEditorDecorationsCollection; - private readonly _currentWidget = new MutableDisposable(); + private readonly _currentWidget = this._register(new MutableDisposable()); private _operationIdPool = 0; private _currentOperation?: number; constructor( - readonly id: string, + private readonly id: string, private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { @@ -129,7 +129,12 @@ export class InlineProgressManager extends Disposable { this._currentDecorations = _editor.createDecorationsCollection(); } - public async showWhile(position: IPosition, title: string, promise: CancelablePromise): Promise { + public override dispose(): void { + super.dispose(); + this._currentDecorations.clear(); + } + + public async showWhile(position: IPosition, title: string, promise: Promise, delegate: InlineProgressDelegate, delayOverride?: number): Promise { const operationId = this._operationIdPool++; this._currentOperation = operationId; @@ -143,9 +148,9 @@ export class InlineProgressManager extends Disposable { }]); if (decorationIds.length > 0) { - this._currentWidget.value = this._instantiationService.createInstance(InlineProgressWidget, this.id, this._editor, range, title, promise); + this._currentWidget.value = this._instantiationService.createInstance(InlineProgressWidget, this.id, this._editor, range, title, delegate); } - }, this._showDelay); + }, delayOverride ?? this._showDelay); try { return await promise; diff --git a/patched-vscode/src/vs/editor/contrib/lineSelection/test/browser/lineSelection.test.ts b/patched-vscode/src/vs/editor/contrib/lineSelection/test/browser/lineSelection.test.ts index 4ef8e7d2..17b47434 100644 --- a/patched-vscode/src/vs/editor/contrib/lineSelection/test/browser/lineSelection.test.ts +++ b/patched-vscode/src/vs/editor/contrib/lineSelection/test/browser/lineSelection.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction } from 'vs/editor/browser/editorExtensions'; diff --git a/patched-vscode/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/patched-vscode/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 45b3fafb..60601d1f 100644 --- a/patched-vscode/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/patched-vscode/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -11,6 +11,7 @@ import { ReplaceCommand, ReplaceCommandThatPreservesSelection, ReplaceCommandTha import { TrimTrailingWhitespaceCommand } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { TypeOperations } from 'vs/editor/common/cursor/cursorTypeOperations'; +import { EnterOperation } from 'vs/editor/common/cursor/cursorTypeEditOperations'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -594,7 +595,7 @@ export class InsertLineBeforeAction extends EditorAction { return; } editor.pushUndoStop(); - editor.executeCommands(this.id, TypeOperations.lineInsertBefore(viewModel.cursorConfig, editor.getModel(), editor.getSelections())); + editor.executeCommands(this.id, EnterOperation.lineInsertBefore(viewModel.cursorConfig, editor.getModel(), editor.getSelections())); } } @@ -619,7 +620,7 @@ export class InsertLineAfterAction extends EditorAction { return; } editor.pushUndoStop(); - editor.executeCommands(this.id, TypeOperations.lineInsertAfter(viewModel.cursorConfig, editor.getModel(), editor.getSelections())); + editor.executeCommands(this.id, EnterOperation.lineInsertAfter(viewModel.cursorConfig, editor.getModel(), editor.getSelections())); } } diff --git a/patched-vscode/src/vs/editor/contrib/linesOperations/browser/moveLinesCommand.ts b/patched-vscode/src/vs/editor/contrib/linesOperations/browser/moveLinesCommand.ts index 09c8a4db..9a4f6800 100644 --- a/patched-vscode/src/vs/editor/contrib/linesOperations/browser/moveLinesCommand.ts +++ b/patched-vscode/src/vs/editor/contrib/linesOperations/browser/moveLinesCommand.ts @@ -169,6 +169,7 @@ export class MoveLinesCommand implements ICommand { tokenization: { getLineTokens: (lineNumber: number) => { if (lineNumber === s.startLineNumber) { + // TODO@aiday-mar: the tokens here don't correspond exactly to the corresponding content (after indentation adjustment), have to fix this. return model.tokenization.getLineTokens(movingLineNumber); } else if (lineNumber >= s.startLineNumber + 1 && lineNumber <= s.endLineNumber + 1) { return model.tokenization.getLineTokens(lineNumber - 1); diff --git a/patched-vscode/src/vs/editor/contrib/linesOperations/test/browser/copyLinesCommand.test.ts b/patched-vscode/src/vs/editor/contrib/linesOperations/test/browser/copyLinesCommand.test.ts index c53496fb..48348ae9 100644 --- a/patched-vscode/src/vs/editor/contrib/linesOperations/test/browser/copyLinesCommand.test.ts +++ b/patched-vscode/src/vs/editor/contrib/linesOperations/test/browser/copyLinesCommand.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { CopyLinesCommand } from 'vs/editor/contrib/linesOperations/browser/copyLinesCommand'; diff --git a/patched-vscode/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/patched-vscode/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index 5425697a..f99fb624 100644 --- a/patched-vscode/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/patched-vscode/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/patched-vscode/src/vs/editor/contrib/linkedEditing/test/browser/linkedEditing.test.ts b/patched-vscode/src/vs/editor/contrib/linkedEditing/test/browser/linkedEditing.test.ts index 982dc074..80d6ed93 100644 --- a/patched-vscode/src/vs/editor/contrib/linkedEditing/test/browser/linkedEditing.test.ts +++ b/patched-vscode/src/vs/editor/contrib/linkedEditing/test/browser/linkedEditing.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; diff --git a/patched-vscode/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts b/patched-vscode/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts index 64de23fc..008150e7 100644 --- a/patched-vscode/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts +++ b/patched-vscode/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/patched-vscode/src/vs/editor/contrib/parameterHints/browser/parameterHints.css b/patched-vscode/src/vs/editor/contrib/parameterHints/browser/parameterHints.css index 93758171..3efac6c1 100644 --- a/patched-vscode/src/vs/editor/contrib/parameterHints/browser/parameterHints.css +++ b/patched-vscode/src/vs/editor/contrib/parameterHints/browser/parameterHints.css @@ -69,6 +69,10 @@ border-bottom: 1px solid var(--vscode-editorHoverWidget-border); } +.monaco-editor .parameter-hints-widget .code { + font-family: var(--vscode-parameterHintsWidget-editorFontFamily), var(--vscode-parameterHintsWidget-editorFontFamilyDefault); +} + .monaco-editor .parameter-hints-widget .docs { padding: 0 10px 0 5px; white-space: pre-wrap; diff --git a/patched-vscode/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/patched-vscode/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index f5b8af52..32546cff 100644 --- a/patched-vscode/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -14,7 +14,7 @@ import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; import 'vs/css!./parameterHints'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions'; import * as languages from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; @@ -126,9 +126,13 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { if (!this.domNodes) { return; } + const fontInfo = this.editor.getOption(EditorOption.fontInfo); - this.domNodes.element.style.fontSize = `${fontInfo.fontSize}px`; - this.domNodes.element.style.lineHeight = `${fontInfo.lineHeight / fontInfo.fontSize}`; + const element = this.domNodes.element; + element.style.fontSize = `${fontInfo.fontSize}px`; + element.style.lineHeight = `${fontInfo.lineHeight / fontInfo.fontSize}`; + element.style.setProperty('--vscode-parameterHintsWidget-editorFontFamily', fontInfo.fontFamily); + element.style.setProperty('--vscode-parameterHintsWidget-editorFontFamilyDefault', EDITOR_FONT_DEFAULTS.fontFamily); }; updateFont(); @@ -203,10 +207,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { } const code = dom.append(this.domNodes.signature, $('.code')); - const fontInfo = this.editor.getOption(EditorOption.fontInfo); - code.style.fontSize = `${fontInfo.fontSize}px`; - code.style.fontFamily = fontInfo.fontFamily; - const hasParameters = signature.parameters.length > 0; const activeParameterIndex = signature.activeParameter ?? hints.activeParameter; @@ -387,4 +387,4 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { } } -registerColor('editorHoverWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hcDark: listHighlightForeground, hcLight: listHighlightForeground }, nls.localize('editorHoverWidgetHighlightForeground', 'Foreground color of the active item in the parameter hint.')); +registerColor('editorHoverWidget.highlightForeground', listHighlightForeground, nls.localize('editorHoverWidgetHighlightForeground', 'Foreground color of the active item in the parameter hint.')); diff --git a/patched-vscode/src/vs/editor/contrib/parameterHints/test/browser/parameterHintsModel.test.ts b/patched-vscode/src/vs/editor/contrib/parameterHints/test/browser/parameterHintsModel.test.ts index 6c84215b..10372b40 100644 --- a/patched-vscode/src/vs/editor/contrib/parameterHints/test/browser/parameterHintsModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/parameterHints/test/browser/parameterHintsModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { promiseWithResolvers } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/editor/contrib/peekView/browser/peekView.ts b/patched-vscode/src/vs/editor/contrib/peekView/browser/peekView.ts index a0f2dfd9..86f6cb1d 100644 --- a/patched-vscode/src/vs/editor/contrib/peekView/browser/peekView.ts +++ b/patched-vscode/src/vs/editor/contrib/peekView/browser/peekView.ts @@ -289,8 +289,8 @@ export const peekViewResultsFileForeground = registerColor('peekViewResult.fileF export const peekViewResultsSelectionBackground = registerColor('peekViewResult.selectionBackground', { dark: '#3399ff33', light: '#3399ff33', hcDark: null, hcLight: null }, nls.localize('peekViewResultsSelectionBackground', 'Background color of the selected entry in the peek view result list.')); export const peekViewResultsSelectionForeground = registerColor('peekViewResult.selectionForeground', { dark: Color.white, light: '#6C6C6C', hcDark: Color.white, hcLight: editorForeground }, nls.localize('peekViewResultsSelectionForeground', 'Foreground color of the selected entry in the peek view result list.')); export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hcDark: Color.black, hcLight: Color.white }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.')); -export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', { dark: peekViewEditorBackground, light: peekViewEditorBackground, hcDark: peekViewEditorBackground, hcLight: peekViewEditorBackground }, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.')); -export const peekViewEditorStickyScrollBackground = registerColor('peekViewEditorStickyScroll.background', { dark: peekViewEditorBackground, light: peekViewEditorBackground, hcDark: peekViewEditorBackground, hcLight: peekViewEditorBackground }, nls.localize('peekViewEditorStickScrollBackground', 'Background color of sticky scroll in the peek view editor.')); +export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', peekViewEditorBackground, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.')); +export const peekViewEditorStickyScrollBackground = registerColor('peekViewEditorStickyScroll.background', peekViewEditorBackground, nls.localize('peekViewEditorStickScrollBackground', 'Background color of sticky scroll in the peek view editor.')); export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hcDark: null, hcLight: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.')); export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hcDark: null, hcLight: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.')); diff --git a/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderText.contribution.ts b/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderText.contribution.ts new file mode 100644 index 00000000..e9994c4b --- /dev/null +++ b/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderText.contribution.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./placeholderText'; +import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { ghostTextForeground } from 'vs/editor/common/core/editorColorRegistry'; +import { localize } from 'vs/nls'; +import { registerColor } from 'vs/platform/theme/common/colorUtils'; +import { PlaceholderTextContribution } from './placeholderTextContribution'; +import { wrapInReloadableClass1 } from 'vs/platform/observable/common/wrapInReloadableClass'; + +registerEditorContribution(PlaceholderTextContribution.ID, wrapInReloadableClass1(() => PlaceholderTextContribution), EditorContributionInstantiation.Eager); + +registerColor('editor.placeholder.foreground', ghostTextForeground, localize('placeholderForeground', 'Foreground color of the placeholder text in the editor.')); diff --git a/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderText.css b/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderText.css index c42b21a3..043b6f15 100644 --- a/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderText.css +++ b/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderText.css @@ -3,6 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .placeholder-text { - color: var(--vscode-editorGhostText-foreground) !important; +.monaco-editor { + --vscode-editor-placeholder-foreground: var(--vscode-editorGhostText-foreground); + + .editorPlaceholder { + top: 0px; + position: absolute; + overflow: hidden; + text-overflow: ellipsis; + text-wrap: nowrap; + pointer-events: none; + + color: var(--vscode-editor-placeholder-foreground); + } } diff --git a/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderTextContribution.ts b/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderTextContribution.ts index 52c0edd1..f048991d 100644 --- a/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderTextContribution.ts +++ b/patched-vscode/src/vs/editor/contrib/placeholderText/browser/placeholderTextContribution.ts @@ -3,64 +3,80 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { h } from 'vs/base/browser/dom'; import { structuralEquals } from 'vs/base/common/equals'; import { Disposable } from 'vs/base/common/lifecycle'; -import { derived, derivedOpts, observableValue } from 'vs/base/common/observable'; -import 'vs/css!./placeholderText'; +import { autorun, constObservable, derivedObservableWithCache, derivedOpts, IObservable, IReader } from 'vs/base/common/observable'; +import { DebugOwner } from 'vs/base/common/observableInternal/debugName'; +import { derivedWithStore } from 'vs/base/common/observableInternal/derived'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { obsCodeEditor } from 'vs/editor/browser/observableUtilities'; -import { Range } from 'vs/editor/common/core/range'; +import { observableCodeEditor } from 'vs/editor/browser/observableCodeEditor'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration, InjectedTextCursorStops } from 'vs/editor/common/model'; +/** + * Use the editor option to set the placeholder text. +*/ export class PlaceholderTextContribution extends Disposable implements IEditorContribution { public static get(editor: ICodeEditor): PlaceholderTextContribution { return editor.getContribution(PlaceholderTextContribution.ID)!; } public static readonly ID = 'editor.contrib.placeholderText'; - private readonly _editorObs = obsCodeEditor(this._editor); + private readonly _editorObs = observableCodeEditor(this._editor); - private readonly _placeholderText = observableValue(this, undefined); + private readonly _placeholderText = this._editorObs.getOption(EditorOption.placeholder); - private readonly _decorationOptions = derivedOpts<{ placeholder: string } | undefined>({ owner: this, equalsFn: structuralEquals }, reader => { + private readonly _state = derivedOpts<{ placeholder: string } | undefined>({ owner: this, equalsFn: structuralEquals }, reader => { const p = this._placeholderText.read(reader); if (!p) { return undefined; } if (!this._editorObs.valueIsEmpty.read(reader)) { return undefined; } - return { placeholder: p }; }); - private readonly _decorations = derived(this, (reader) => { - const options = this._decorationOptions.read(reader); - if (!options) { return []; } + private readonly _shouldViewBeAlive = isOrWasTrue(this, reader => this._state.read(reader)?.placeholder !== undefined); + + private readonly _view = derivedWithStore((reader, store) => { + if (!this._shouldViewBeAlive.read(reader)) { return; } - return [{ - range: new Range(1, 1, 1, 1), - options: { - description: 'placeholder', - showIfCollapsed: true, - after: { - content: options.placeholder, - cursorStops: InjectedTextCursorStops.None, - inlineClassName: 'placeholder-text' - } - } - }]; + const element = h('div.editorPlaceholder'); + + store.add(autorun(reader => { + const data = this._state.read(reader); + const shouldBeVisibile = data?.placeholder !== undefined; + element.root.style.display = shouldBeVisibile ? 'block' : 'none'; + element.root.innerText = data?.placeholder ?? ''; + })); + store.add(autorun(reader => { + const info = this._editorObs.layoutInfo.read(reader); + element.root.style.left = `${info.contentLeft}px`; + element.root.style.width = (info.contentWidth - info.verticalScrollbarWidth) + 'px'; + element.root.style.top = `${this._editor.getTopForLineNumber(0)}px`; + })); + store.add(autorun(reader => { + element.root.style.fontFamily = this._editorObs.getOption(EditorOption.fontFamily).read(reader); + element.root.style.fontSize = this._editorObs.getOption(EditorOption.fontSize).read(reader) + 'px'; + element.root.style.lineHeight = this._editorObs.getOption(EditorOption.lineHeight).read(reader) + 'px'; + })); + store.add(this._editorObs.createOverlayWidget({ + allowEditorOverflow: false, + minContentWidthInPx: constObservable(0), + position: constObservable(null), + domNode: element.root, + })); }); constructor( private readonly _editor: ICodeEditor, ) { super(); - - this._register(this._editorObs.setDecorations(this._decorations)); - } - - public setPlaceholderText(placeholder: string): void { - this._placeholderText.set(placeholder, undefined); + this._view.recomputeInitiallyAndOnChange(this._store); } } -registerEditorContribution(PlaceholderTextContribution.ID, PlaceholderTextContribution, EditorContributionInstantiation.Lazy); +function isOrWasTrue(owner: DebugOwner, fn: (reader: IReader) => boolean): IObservable { + return derivedObservableWithCache(owner, (reader, lastValue) => { + if (lastValue === true) { return true; } + return fn(reader); + }); +} diff --git a/patched-vscode/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts b/patched-vscode/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts index 05c099c6..c809937f 100644 --- a/patched-vscode/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts +++ b/patched-vscode/src/vs/editor/contrib/quickAccess/browser/editorNavigationQuickAccess.ts @@ -52,7 +52,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu //#region Provider methods - provide(picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { + provide(picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { const disposables = new DisposableStore(); // Apply options if any @@ -78,7 +78,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu return disposables; } - private doProvide(picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { + private doProvide(picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { const disposables = new DisposableStore(); // With text control @@ -134,12 +134,12 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu /** * Subclasses to implement to provide picks for the picker when an editor is active. */ - protected abstract provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable; + protected abstract provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable; /** * Subclasses to implement to provide picks for the picker when no editor is active. */ - protected abstract provideWithoutTextEditor(picker: IQuickPick, token: CancellationToken): IDisposable; + protected abstract provideWithoutTextEditor(picker: IQuickPick, token: CancellationToken): IDisposable; protected gotoLocation({ editor }: IQuickAccessTextEditorContext, options: { range: IRange; keyMods: IKeyMods; forceSideBySide?: boolean; preserveFocus?: boolean }): void { editor.setSelection(options.range, TextEditorSelectionSource.JUMP); diff --git a/patched-vscode/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts b/patched-vscode/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts index 6db58ae6..b1ccfa95 100644 --- a/patched-vscode/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts +++ b/patched-vscode/src/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess.ts @@ -24,7 +24,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor super({ canAcceptInBackground: true }); } - protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); picker.items = [{ label }]; @@ -33,7 +33,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor return Disposable.None; } - protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken): IDisposable { + protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken): IDisposable { const editor = context.editor; const disposables = new DisposableStore(); diff --git a/patched-vscode/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts b/patched-vscode/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts index 46aeeda1..9ff33f41 100644 --- a/patched-vscode/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts +++ b/patched-vscode/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts @@ -48,7 +48,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit static PREFIX = '@'; static SCOPE_PREFIX = ':'; - static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; + static PREFIX_BY_CATEGORY = `${this.PREFIX}${this.SCOPE_PREFIX}`; protected override readonly options: IGotoSymbolQuickAccessProviderOptions; @@ -63,13 +63,13 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit this.options.canAcceptInBackground = true; } - protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { this.provideLabelPick(picker, localize('cannotRunGotoSymbolWithoutEditor', "To go to a symbol, first open a text editor with symbol information.")); return Disposable.None; } - protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { + protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { const editor = context.editor; const model = this.getModel(editor); if (!model) { @@ -87,7 +87,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return this.doProvideWithoutEditorSymbols(context, model, picker, token); } - private doProvideWithoutEditorSymbols(context: IQuickAccessTextEditorContext, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + private doProvideWithoutEditorSymbols(context: IQuickAccessTextEditorContext, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); // Generic pick for not having any symbol information @@ -110,7 +110,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return disposables; } - private provideLabelPick(picker: IQuickPick, label: string): void { + private provideLabelPick(picker: IQuickPick, label: string): void { picker.items = [{ label, index: 0, kind: SymbolKind.String }]; picker.ariaLabel = label; } @@ -137,7 +137,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return symbolProviderRegistryPromise.p; } - private doProvideWithEditorSymbols(context: IQuickAccessTextEditorContext, model: ITextModel, picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { + private doProvideWithEditorSymbols(context: IQuickAccessTextEditorContext, model: ITextModel, picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { const editor = context.editor; const disposables = new DisposableStore(); diff --git a/patched-vscode/src/vs/editor/contrib/rename/browser/renameWidget.ts b/patched-vscode/src/vs/editor/contrib/rename/browser/renameWidget.ts index 03257f28..1f6fcc14 100644 --- a/patched-vscode/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -6,7 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getBaseLayerHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate2'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; @@ -32,7 +32,6 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { NewSymbolName, NewSymbolNameTag, NewSymbolNameTriggerKind, ProviderResult } from 'vs/editor/common/languages'; import * as nls from 'vs/nls'; -import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILogService } from 'vs/platform/log/common/log'; @@ -55,8 +54,8 @@ const _sticky = false ; -export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false, localize('renameInputVisible', "Whether the rename input widget is visible")); -export const CONTEXT_RENAME_INPUT_FOCUSED = new RawContextKey('renameInputFocused', false, localize('renameInputFocused', "Whether the rename input widget is focused")); +export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false, nls.localize('renameInputVisible', "Whether the rename input widget is visible")); +export const CONTEXT_RENAME_INPUT_FOCUSED = new RawContextKey('renameInputFocused', false, nls.localize('renameInputFocused', "Whether the rename input widget is focused")); /** * "Source" of the new name: @@ -311,7 +310,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable beforeRender(): IDimension | null { const [accept, preview] = this._acceptKeybindings; - this._label!.innerText = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"'] }, "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); + this._label!.innerText = nls.localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"'] }, "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); this._domNode!.style.minWidth = `200px`; // to prevent from widening when candidates come in @@ -319,7 +318,8 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } afterRender(position: ContentWidgetPositionPreference | null): void { - this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); + // FIXME@ulugbekna: commenting trace log out until we start unmounting the widget from editor properly - https://github.com/microsoft/vscode/issues/226975 + // this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); if (position === null) { // cancel rename when input widget isn't rendered anymore this.cancelInput(true, 'afterRender (because position is null)'); @@ -364,7 +364,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } cancelInput(focusEditor: boolean, caller: string): void { - this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); + // this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); this._currentCancelInput?.(focusEditor); } @@ -750,7 +750,7 @@ class RenameCandidateListView { this._listContainer.style.height = `${height}px`; this._listContainer.style.width = `${width}px`; - aria.status(localize('renameSuggestionsReceivedAria', "Received {0} rename suggestions", candidates.length)); + aria.status(nls.localize('renameSuggestionsReceivedAria', "Received {0} rename suggestions", candidates.length)); } public clearCandidates(): void { @@ -900,7 +900,7 @@ class InputWithButton implements IDisposable { private _domNode: HTMLDivElement | undefined; private _inputNode: HTMLInputElement | undefined; private _buttonNode: HTMLElement | undefined; - private _buttonHover: IUpdatableHover | undefined; + private _buttonHover: IManagedHover | undefined; private _buttonGenHoverText: string | undefined; private _buttonCancelHoverText: string | undefined; private _sparkleIcon: HTMLElement | undefined; @@ -924,7 +924,7 @@ class InputWithButton implements IDisposable { this._inputNode.className = 'rename-input'; this._inputNode.type = 'text'; this._inputNode.style.border = 'none'; - this._inputNode.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); + this._inputNode.setAttribute('aria-label', nls.localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); this._domNode.appendChild(this._inputNode); @@ -934,7 +934,7 @@ class InputWithButton implements IDisposable { this._buttonGenHoverText = nls.localize('generateRenameSuggestionsButton', "Generate new name suggestions"); this._buttonCancelHoverText = nls.localize('cancelRenameSuggestionsButton', "Cancel"); - this._buttonHover = getBaseLayerHoverDelegate().setupUpdatableHover(getDefaultHoverDelegate('element'), this._buttonNode, this._buttonGenHoverText); + this._buttonHover = getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('element'), this._buttonNode, this._buttonGenHoverText); this._disposables.add(this._buttonHover); this._domNode.appendChild(this._buttonNode); diff --git a/patched-vscode/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts b/patched-vscode/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts index c2d9a57c..9eb8d2f0 100644 --- a/patched-vscode/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts +++ b/patched-vscode/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Barrier, timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -14,6 +14,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { Range } from 'vs/editor/common/core/range'; import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ITextModel } from 'vs/editor/common/model'; import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; @@ -29,6 +30,7 @@ import { TestTextResourcePropertiesService } from 'vs/editor/test/common/service import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -50,12 +52,14 @@ suite('ModelSemanticColoring', () => { languageFeaturesService = new LanguageFeaturesService(); languageService = disposables.add(new LanguageService(false)); const semanticTokensStylingService = disposables.add(new SemanticTokensStylingService(themeService, logService, languageService)); + const instantiationService = new TestInstantiationService(); + instantiationService.set(ILanguageService, languageService); + instantiationService.set(ILanguageConfigurationService, new TestLanguageConfigurationService()); modelService = disposables.add(new ModelService( configService, new TestTextResourcePropertiesService(configService), new UndoRedoService(new TestDialogService(), new TestNotificationService()), - languageService, - new TestLanguageConfigurationService(), + instantiationService )); const envService = new class extends mock() { override isBuilt: boolean = true; diff --git a/patched-vscode/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts b/patched-vscode/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts index da8dc2f2..6ac97ecd 100644 --- a/patched-vscode/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts +++ b/patched-vscode/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; import { DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts b/patched-vscode/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts index dc9e9767..1de9179c 100644 --- a/patched-vscode/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts +++ b/patched-vscode/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.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 * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetController2.old.test.ts b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetController2.old.test.ts index e0f11350..236ad70f 100644 --- a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetController2.old.test.ts +++ b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetController2.old.test.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 * as assert from 'assert'; +import assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts index 1c9c3a80..5b9a5e43 100644 --- a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts +++ b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.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 * as assert from 'assert'; +import assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts index ee2f1f1d..bbf24168 100644 --- a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts +++ b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Choice, FormatString, Marker, Placeholder, Scanner, SnippetParser, Text, TextmateSnippet, TokenType, Transform, Variable } from 'vs/editor/contrib/snippet/browser/snippetParser'; diff --git a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts index 61061846..0a9a3c76 100644 --- a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts +++ b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.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 * as assert from 'assert'; +import assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts index 53a9b272..b8ea4e52 100644 --- a/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.ts +++ b/patched-vscode/src/vs/editor/contrib/snippet/test/browser/snippetVariables.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { sep } from 'vs/base/common/path'; diff --git a/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css b/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css index 3bc52c6c..27608225 100644 --- a/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css +++ b/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css @@ -61,7 +61,7 @@ .monaco-editor .sticky-widget { width: 100%; - box-shadow: var(--vscode-editorStickyScroll-shadow) 0 3px 2px -2px; + box-shadow: var(--vscode-editorStickyScroll-shadow) 0 4px 2px -2px; z-index: 4; background-color: var(--vscode-editorStickyScroll-background); right: initial !important; diff --git a/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index bdc0542b..65824a12 100644 --- a/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -51,7 +51,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib private readonly _sessionStore: DisposableStore = new DisposableStore(); private _widgetState: StickyScrollWidgetState; - private _foldingModel: FoldingModel | null = null; + private _foldingModel: FoldingModel | undefined; private _maxStickyLines: number = Number.MAX_SAFE_INTEGER; private _stickyRangeProjectedOnEditor: IRange | undefined; @@ -67,7 +67,8 @@ export class StickyScrollController extends Disposable implements IEditorContrib private _positionRevealed = false; private _onMouseDown = false; private _endLineNumbers: number[] = []; - private _showEndForLine: number | null = null; + private _showEndForLine: number | undefined; + private _minRebuildFromLine: number | undefined; constructor( private readonly _editor: ICodeEditor, @@ -84,7 +85,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib this._register(this._stickyScrollWidget); this._register(this._stickyLineCandidateProvider); - this._widgetState = new StickyScrollWidgetState([], [], 0); + this._widgetState = StickyScrollWidgetState.Empty; this._onDidResize(); this._readConfiguration(); const stickyScrollDomNode = this._stickyScrollWidget.getDomNode(); @@ -291,14 +292,14 @@ export class StickyScrollController extends Disposable implements IEditorContrib this._renderStickyScroll(); return; } - if (this._showEndForLine !== null) { - this._showEndForLine = null; + if (this._showEndForLine !== undefined) { + this._showEndForLine = undefined; this._renderStickyScroll(); } })); this._register(dom.addDisposableListener(stickyScrollWidgetDomNode, dom.EventType.MOUSE_LEAVE, (e) => { - if (this._showEndForLine !== null) { - this._showEndForLine = null; + if (this._showEndForLine !== undefined) { + this._showEndForLine = undefined; this._renderStickyScroll(); } })); @@ -323,7 +324,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib let currentHTMLChild: HTMLElement; - getDefinitionsAtPosition(this._languageFeaturesService.definitionProvider, this._editor.getModel(), new Position(range.startLineNumber, range.startColumn + 1), cancellationToken.token).then((candidateDefinitions => { + getDefinitionsAtPosition(this._languageFeaturesService.definitionProvider, this._editor.getModel(), new Position(range.startLineNumber, range.startColumn + 1), false, cancellationToken.token).then((candidateDefinitions => { if (cancellationToken.token.isCancellationRequested) { return; } @@ -415,14 +416,14 @@ export class StickyScrollController extends Disposable implements IEditorContrib this._editor.addOverlayWidget(this._stickyScrollWidget); this._sessionStore.add(this._editor.onDidScrollChange((e) => { if (e.scrollTopChanged) { - this._showEndForLine = null; + this._showEndForLine = undefined; this._renderStickyScroll(); } })); this._sessionStore.add(this._editor.onDidLayoutChange(() => this._onDidResize())); this._sessionStore.add(this._editor.onDidChangeModelTokens((e) => this._onTokensChange(e))); this._sessionStore.add(this._stickyLineCandidateProvider.onDidChangeStickyScroll(() => { - this._showEndForLine = null; + this._showEndForLine = undefined; this._renderStickyScroll(); })); this._enabled = true; @@ -431,7 +432,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib const lineNumberOption = this._editor.getOption(EditorOption.lineNumbers); if (lineNumberOption.renderType === RenderLineNumbersType.Relative) { this._sessionStore.add(this._editor.onDidChangeCursorPosition(() => { - this._showEndForLine = null; + this._showEndForLine = undefined; this._renderStickyScroll(0); })); } @@ -479,32 +480,29 @@ export class StickyScrollController extends Disposable implements IEditorContrib this._maxStickyLines = Math.round(theoreticalLines * .25); } - private async _renderStickyScroll(rebuildFromLine?: number) { + private async _renderStickyScroll(rebuildFromLine?: number): Promise { const model = this._editor.getModel(); if (!model || model.isTooLargeForTokenization()) { - this._foldingModel = null; - this._stickyScrollWidget.setState(undefined, null); + this._resetState(); return; } - const stickyLineVersion = this._stickyLineCandidateProvider.getVersionId(); - if (stickyLineVersion === undefined || stickyLineVersion === model.getVersionId()) { - this._foldingModel = await FoldingController.get(this._editor)?.getFoldingModel() ?? null; - this._widgetState = this.findScrollWidgetState(); - this._stickyScrollVisibleContextKey.set(!(this._widgetState.startLineNumbers.length === 0)); - + const nextRebuildFromLine = this._updateAndGetMinRebuildFromLine(rebuildFromLine); + const stickyWidgetVersion = this._stickyLineCandidateProvider.getVersionId(); + const shouldUpdateState = stickyWidgetVersion === undefined || stickyWidgetVersion === model.getVersionId(); + if (shouldUpdateState) { if (!this._focused) { - this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine); + await this._updateState(nextRebuildFromLine); } else { // Suppose that previously the sticky scroll widget had height 0, then if there are visible lines, set the last line as focused if (this._focusedStickyElementIndex === -1) { - this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine); + await this._updateState(nextRebuildFromLine); this._focusedStickyElementIndex = this._stickyScrollWidget.lineNumberCount - 1; if (this._focusedStickyElementIndex !== -1) { this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex); } } else { const focusedStickyElementLineNumber = this._stickyScrollWidget.lineNumbers[this._focusedStickyElementIndex]; - this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine); + await this._updateState(nextRebuildFromLine); // Suppose that after setting the state, there are no sticky lines, set the focused index to -1 if (this._stickyScrollWidget.lineNumberCount === 0) { this._focusedStickyElementIndex = -1; @@ -523,6 +521,31 @@ export class StickyScrollController extends Disposable implements IEditorContrib } } + private _updateAndGetMinRebuildFromLine(rebuildFromLine: number | undefined): number | undefined { + if (rebuildFromLine !== undefined) { + const minRebuildFromLineOrInfinity = this._minRebuildFromLine !== undefined ? this._minRebuildFromLine : Infinity; + this._minRebuildFromLine = Math.min(rebuildFromLine, minRebuildFromLineOrInfinity); + } + return this._minRebuildFromLine; + } + + private async _updateState(rebuildFromLine?: number): Promise { + this._minRebuildFromLine = undefined; + this._foldingModel = await FoldingController.get(this._editor)?.getFoldingModel() ?? undefined; + this._widgetState = this.findScrollWidgetState(); + const stickyWidgetHasLines = this._widgetState.startLineNumbers.length > 0; + this._stickyScrollVisibleContextKey.set(stickyWidgetHasLines); + this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine); + } + + private async _resetState(): Promise { + this._minRebuildFromLine = undefined; + this._foldingModel = undefined; + this._widgetState = StickyScrollWidgetState.Empty; + this._stickyScrollVisibleContextKey.set(false); + this._stickyScrollWidget.setState(undefined, undefined); + } + findScrollWidgetState(): StickyScrollWidgetState { const lineHeight: number = this._editor.getOption(EditorOption.lineHeight); const maxNumberStickyLines = Math.min(this._maxStickyLines, this._editor.getOption(EditorOption.stickyScroll).maxLineCount); diff --git a/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index d0e8da4b..27d3d8e1 100644 --- a/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -35,6 +35,10 @@ export class StickyScrollWidgetState { && equals(this.startLineNumbers, other.startLineNumbers) && equals(this.endLineNumbers, other.endLineNumbers); } + + static get Empty() { + return new StickyScrollWidgetState([], [], 0); + } } const _ttPolicy = createTrustedTypesPolicy('stickyScrollViewLayer', { createHTML: value => value }); @@ -126,7 +130,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { return this._lineNumbers; } - setState(_state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | null, _rebuildFromLine?: number): void { + setState(_state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | undefined, _rebuildFromLine?: number): void { if (_rebuildFromLine === undefined && ((!this._previousState && !_state) || (this._previousState && this._previousState.equals(_state))) ) { @@ -205,7 +209,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } } - private async _renderRootNode(state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | null, rebuildFromLine: number): Promise { + private async _renderRootNode(state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | undefined, rebuildFromLine: number): Promise { this._clearStickyLinesFromLine(rebuildFromLine); if (!state) { return; @@ -258,7 +262,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { })); } - private _renderChildNode(index: number, line: number, foldingModel: FoldingModel | null, layoutInfo: EditorLayoutInfo): RenderedStickyLine | undefined { + private _renderChildNode(index: number, line: number, foldingModel: FoldingModel | undefined, layoutInfo: EditorLayoutInfo): RenderedStickyLine | undefined { const viewModel = this._editor._getViewModel(); if (!viewModel) { return; @@ -358,7 +362,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { return stickyLine; } - private _renderFoldingIconForLine(foldingModel: FoldingModel | null, line: number): StickyFoldingIcon | undefined { + private _renderFoldingIconForLine(foldingModel: FoldingModel | undefined, line: number): StickyFoldingIcon | undefined { const showFoldingControls: 'mouseover' | 'always' | 'never' = this._editor.getOption(EditorOption.showFoldingControls); if (!foldingModel || showFoldingControls === 'never') { return; diff --git a/patched-vscode/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts b/patched-vscode/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts index 04e85276..aaefcb65 100644 --- a/patched-vscode/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts +++ b/patched-vscode/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.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 * as assert from 'assert'; +import assert from 'assert'; import { withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { StickyScrollController } from 'vs/editor/contrib/stickyScroll/browser/stickyScrollController'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; diff --git a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggest.ts b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggest.ts index 0ca6b0e0..535c67c9 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggest.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggest.ts @@ -178,13 +178,13 @@ export class CompletionOptions { ) { } } -let _snippetSuggestSupport: languages.CompletionItemProvider; +let _snippetSuggestSupport: languages.CompletionItemProvider | undefined; -export function getSnippetSuggestSupport(): languages.CompletionItemProvider { +export function getSnippetSuggestSupport(): languages.CompletionItemProvider | undefined { return _snippetSuggestSupport; } -export function setSnippetSuggestSupport(support: languages.CompletionItemProvider): languages.CompletionItemProvider { +export function setSnippetSuggestSupport(support: languages.CompletionItemProvider | undefined): languages.CompletionItemProvider | undefined { const old = _snippetSuggestSupport; _snippetSuggestSupport = support; return old; diff --git a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestController.ts b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestController.ts index 31eb7c7e..254524bf 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -509,26 +509,42 @@ export class SuggestController implements IEditorContribution { // clear only now - after all tasks are done Promise.all(tasks).finally(() => { - this._reportSuggestionAcceptedTelemetry(item, model, isResolved, _commandExectionDuration, _additionalEditsAppliedAsync); + this._reportSuggestionAcceptedTelemetry(item, model, isResolved, _commandExectionDuration, _additionalEditsAppliedAsync, event.index, event.model.items); this.model.clear(); cts.dispose(); }); } - private _reportSuggestionAcceptedTelemetry(item: CompletionItem, model: ITextModel, itemResolved: boolean, commandExectionDuration: number, additionalEditsAppliedAsync: number) { - + private _reportSuggestionAcceptedTelemetry(item: CompletionItem, model: ITextModel, itemResolved: boolean, commandExectionDuration: number, additionalEditsAppliedAsync: number, index: number, completionItems: CompletionItem[]): void { if (Math.floor(Math.random() * 100) === 0) { // throttle telemetry event because accepting completions happens a lot return; } + const labelMap = new Map(); + + for (let i = 0; i < Math.min(30, completionItems.length); i++) { + const label = completionItems[i].textLabel; + + if (labelMap.has(label)) { + labelMap.get(label)!.push(i); + } else { + labelMap.set(label, [i]); + } + } + + const firstIndexArray = labelMap.get(item.textLabel); + const hasDuplicates = firstIndexArray && firstIndexArray.length > 1; + const firstIndex = hasDuplicates ? firstIndexArray[0] : -1; + type AcceptedSuggestion = { extensionId: string; providerId: string; fileExtension: string; languageId: string; basenameHash: string; kind: number; resolveInfo: number; resolveDuration: number; commandDuration: number; additionalEditsAsync: number; + index: number; firstIndex: number; }; type AcceptedSuggestionClassification = { owner: 'jrieken'; @@ -543,6 +559,8 @@ export class SuggestController implements IEditorContribution { resolveDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How long resolving took to finish' }; commandDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How long a completion item command took' }; additionalEditsAsync: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Info about asynchronously applying additional edits' }; + index: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The index of the completion item in the sorted list.' }; + firstIndex: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'When there are multiple completions, the index of the first instance.' }; }; this._telemetryService.publicLog2('suggest.acceptedSuggestion', { @@ -555,7 +573,9 @@ export class SuggestController implements IEditorContribution { resolveInfo: !item.provider.resolveCompletionItem ? -1 : itemResolved ? 1 : 0, resolveDuration: item.resolveDuration, commandDuration: commandExectionDuration, - additionalEditsAsync: additionalEditsAppliedAsync + additionalEditsAsync: additionalEditsAppliedAsync, + index, + firstIndex, }); } @@ -1005,13 +1025,13 @@ registerEditorCommand(new SuggestCommand({ group: 'right', order: 1, when: ContextKeyExpr.and(SuggestContext.DetailsVisible, SuggestContext.CanResolve), - title: nls.localize('detail.more', "show less") + title: nls.localize('detail.more', "Show Less") }, { menuId: suggestWidgetStatusbarMenu, group: 'right', order: 1, when: ContextKeyExpr.and(SuggestContext.DetailsVisible.toNegated(), SuggestContext.CanResolve), - title: nls.localize('detail.less', "show more") + title: nls.localize('detail.less', "Show More") }] })); diff --git a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestModel.ts b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestModel.ts index 0e4124b3..c287fb5d 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestModel.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestModel.ts @@ -29,7 +29,7 @@ import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { FuzzyScoreOptions } from 'vs/base/common/filters'; import { assertType } from 'vs/base/common/types'; -import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys'; +import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -230,7 +230,10 @@ export class SuggestModel implements IDisposable { let set = supportsByTriggerCharacter.get(ch); if (!set) { set = new Set(); - set.add(getSnippetSuggestSupport()); + const suggestSupport = getSnippetSuggestSupport(); + if (suggestSupport) { + set.add(suggestSupport); + } supportsByTriggerCharacter.set(ch, set); } set.add(support); diff --git a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 2eeb94d9..39c98256 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -39,15 +39,15 @@ import { status } from 'vs/base/browser/ui/aria/aria'; /** * Suggest widget colors */ -registerColor('editorSuggestWidget.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, nls.localize('editorSuggestWidgetBackground', 'Background color of the suggest widget.')); -registerColor('editorSuggestWidget.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, nls.localize('editorSuggestWidgetBorder', 'Border color of the suggest widget.')); -const editorSuggestWidgetForeground = registerColor('editorSuggestWidget.foreground', { dark: editorForeground, light: editorForeground, hcDark: editorForeground, hcLight: editorForeground }, nls.localize('editorSuggestWidgetForeground', 'Foreground color of the suggest widget.')); -registerColor('editorSuggestWidget.selectedForeground', { dark: quickInputListFocusForeground, light: quickInputListFocusForeground, hcDark: quickInputListFocusForeground, hcLight: quickInputListFocusForeground }, nls.localize('editorSuggestWidgetSelectedForeground', 'Foreground color of the selected entry in the suggest widget.')); -registerColor('editorSuggestWidget.selectedIconForeground', { dark: quickInputListFocusIconForeground, light: quickInputListFocusIconForeground, hcDark: quickInputListFocusIconForeground, hcLight: quickInputListFocusIconForeground }, nls.localize('editorSuggestWidgetSelectedIconForeground', 'Icon foreground color of the selected entry in the suggest widget.')); -export const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', { dark: quickInputListFocusBackground, light: quickInputListFocusBackground, hcDark: quickInputListFocusBackground, hcLight: quickInputListFocusBackground }, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.')); -registerColor('editorSuggestWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hcDark: listHighlightForeground, hcLight: listHighlightForeground }, nls.localize('editorSuggestWidgetHighlightForeground', 'Color of the match highlights in the suggest widget.')); -registerColor('editorSuggestWidget.focusHighlightForeground', { dark: listFocusHighlightForeground, light: listFocusHighlightForeground, hcDark: listFocusHighlightForeground, hcLight: listFocusHighlightForeground }, nls.localize('editorSuggestWidgetFocusHighlightForeground', 'Color of the match highlights in the suggest widget when an item is focused.')); -registerColor('editorSuggestWidgetStatus.foreground', { dark: transparent(editorSuggestWidgetForeground, .5), light: transparent(editorSuggestWidgetForeground, .5), hcDark: transparent(editorSuggestWidgetForeground, .5), hcLight: transparent(editorSuggestWidgetForeground, .5) }, nls.localize('editorSuggestWidgetStatusForeground', 'Foreground color of the suggest widget status.')); +registerColor('editorSuggestWidget.background', editorWidgetBackground, nls.localize('editorSuggestWidgetBackground', 'Background color of the suggest widget.')); +registerColor('editorSuggestWidget.border', editorWidgetBorder, nls.localize('editorSuggestWidgetBorder', 'Border color of the suggest widget.')); +const editorSuggestWidgetForeground = registerColor('editorSuggestWidget.foreground', editorForeground, nls.localize('editorSuggestWidgetForeground', 'Foreground color of the suggest widget.')); +registerColor('editorSuggestWidget.selectedForeground', quickInputListFocusForeground, nls.localize('editorSuggestWidgetSelectedForeground', 'Foreground color of the selected entry in the suggest widget.')); +registerColor('editorSuggestWidget.selectedIconForeground', quickInputListFocusIconForeground, nls.localize('editorSuggestWidgetSelectedIconForeground', 'Icon foreground color of the selected entry in the suggest widget.')); +export const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', quickInputListFocusBackground, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.')); +registerColor('editorSuggestWidget.highlightForeground', listHighlightForeground, nls.localize('editorSuggestWidgetHighlightForeground', 'Color of the match highlights in the suggest widget.')); +registerColor('editorSuggestWidget.focusHighlightForeground', listFocusHighlightForeground, nls.localize('editorSuggestWidgetFocusHighlightForeground', 'Color of the match highlights in the suggest widget when an item is focused.')); +registerColor('editorSuggestWidgetStatus.foreground', transparent(editorSuggestWidgetForeground, .5), nls.localize('editorSuggestWidgetStatusForeground', 'Foreground color of the suggest widget status.')); const enum State { Hidden, @@ -55,7 +55,8 @@ const enum State { Empty, Open, Frozen, - Details + Details, + onDetailsKeyDown } export interface ISelectedSuggestion { diff --git a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestWidgetStatus.ts b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestWidgetStatus.ts index 25d19c05..4a1df5b9 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestWidgetStatus.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/browser/suggestWidgetStatus.ts @@ -6,31 +6,12 @@ import * as dom from 'vs/base/browser/dom'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; -import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { TextOnlyMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -class StatusBarViewItem extends MenuEntryActionViewItem { - - protected override updateLabel() { - const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService); - if (!kb) { - return super.updateLabel(); - } - if (this.label) { - this.label.textContent = localize({ key: 'content', comment: ['A label', 'A keybinding'] }, '{0} ({1})', this._action.label, StatusBarViewItem.symbolPrintEnter(kb)); - } - } - - static symbolPrintEnter(kb: ResolvedKeybinding) { - return kb.getLabel()?.replace(/\benter\b/gi, '\u23CE'); - } -} - export class SuggestWidgetStatus { readonly element: HTMLElement; @@ -49,7 +30,7 @@ export class SuggestWidgetStatus { this.element = dom.append(container, dom.$('.suggest-status-bar')); const actionViewItemProvider = (action => { - return action instanceof MenuItemAction ? instantiationService.createInstance(StatusBarViewItem, action, undefined) : undefined; + return action instanceof MenuItemAction ? instantiationService.createInstance(TextOnlyMenuEntryActionViewItem, action, { useComma: true }) : undefined; }); this._leftActions = new ActionBar(this.element, { actionViewItemProvider }); this._rightActions = new ActionBar(this.element, { actionViewItemProvider }); diff --git a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts index cb08e56f..823d6865 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/completionModel.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditorOptions, InternalSuggestOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggest.test.ts b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggest.test.ts index d4df28ea..b2e9fc03 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggest.test.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggest.test.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 * as assert from 'assert'; +import assert from 'assert'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestController.test.ts b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestController.test.ts index 617019ff..64123de4 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestController.test.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestController.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestInlineCompletions.test.ts b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestInlineCompletions.test.ts index 1aaca84d..91d42374 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestInlineCompletions.test.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestInlineCompletions.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts index 3bbc8c58..57d0d030 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestMemory.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IPosition } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; diff --git a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts index 2bbfe524..5c673c0f 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.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 * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts index 04f7bce1..4fe23f5a 100644 --- a/patched-vscode/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts +++ b/patched-vscode/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -13,9 +13,8 @@ import { IRange } from 'vs/editor/common/core/range'; import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/core/wordHelper'; import * as languages from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { CompletionItem } from 'vs/editor/contrib/suggest/browser/suggest'; @@ -63,20 +62,20 @@ suite('suggest, word distance', function () { const service = new class extends EditorWorkerService { - private _worker = new EditorSimpleWorker(new class extends mock() { }, null); + private _worker = new BaseEditorSimpleWorker(); constructor() { - super(modelService, new class extends mock() { }, new NullLogService(), new TestLanguageConfigurationService(), new LanguageFeaturesService()); - this._worker.acceptNewModel({ + super(null!, modelService, new class extends mock() { }, new NullLogService(), new TestLanguageConfigurationService(), new LanguageFeaturesService()); + this._worker.$acceptNewModel({ url: model.uri.toString(), lines: model.getLinesContent(), EOL: model.getEOL(), versionId: model.getVersionId() }); - model.onDidChangeContent(e => this._worker.acceptModelChanged(model.uri.toString(), e)); + model.onDidChangeContent(e => this._worker.$acceptModelChanged(model.uri.toString(), e)); } override computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { - return this._worker.computeWordRanges(resource.toString(), range, DEFAULT_WORD_REGEXP.source, DEFAULT_WORD_REGEXP.flags); + return this._worker.$computeWordRanges(resource.toString(), range, DEFAULT_WORD_REGEXP.source, DEFAULT_WORD_REGEXP.flags); } }; diff --git a/patched-vscode/src/vs/editor/contrib/symbolIcons/browser/symbolIcons.ts b/patched-vscode/src/vs/editor/contrib/symbolIcons/browser/symbolIcons.ts index 64ccb1bc..7d6fe4b7 100644 --- a/patched-vscode/src/vs/editor/contrib/symbolIcons/browser/symbolIcons.ts +++ b/patched-vscode/src/vs/editor/contrib/symbolIcons/browser/symbolIcons.ts @@ -7,19 +7,9 @@ import 'vs/css!./symbolIcons'; import { localize } from 'vs/nls'; import { foreground, registerColor } from 'vs/platform/theme/common/colorRegistry'; -export const SYMBOL_ICON_ARRAY_FOREGROUND = registerColor('symbolIcon.arrayForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground, -}, localize('symbolIcon.arrayForeground', 'The foreground color for array symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_BOOLEAN_FOREGROUND = registerColor('symbolIcon.booleanForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground, -}, localize('symbolIcon.booleanForeground', 'The foreground color for boolean symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_ARRAY_FOREGROUND = registerColor('symbolIcon.arrayForeground', foreground, localize('symbolIcon.arrayForeground', 'The foreground color for array symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_BOOLEAN_FOREGROUND = registerColor('symbolIcon.booleanForeground', foreground, localize('symbolIcon.booleanForeground', 'The foreground color for boolean symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); export const SYMBOL_ICON_CLASS_FOREGROUND = registerColor('symbolIcon.classForeground', { dark: '#EE9D28', @@ -28,19 +18,9 @@ export const SYMBOL_ICON_CLASS_FOREGROUND = registerColor('symbolIcon.classForeg hcLight: '#D67E00' }, localize('symbolIcon.classForeground', 'The foreground color for class symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_COLOR_FOREGROUND = registerColor('symbolIcon.colorForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.colorForeground', 'The foreground color for color symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_COLOR_FOREGROUND = registerColor('symbolIcon.colorForeground', foreground, localize('symbolIcon.colorForeground', 'The foreground color for color symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_CONSTANT_FOREGROUND = registerColor('symbolIcon.constantForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.constantForeground', 'The foreground color for constant symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_CONSTANT_FOREGROUND = registerColor('symbolIcon.constantForeground', foreground, localize('symbolIcon.constantForeground', 'The foreground color for constant symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); export const SYMBOL_ICON_CONSTRUCTOR_FOREGROUND = registerColor('symbolIcon.constructorForeground', { dark: '#B180D7', @@ -77,19 +57,9 @@ export const SYMBOL_ICON_FIELD_FOREGROUND = registerColor('symbolIcon.fieldForeg hcLight: '#007ACC' }, localize('symbolIcon.fieldForeground', 'The foreground color for field symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_FILE_FOREGROUND = registerColor('symbolIcon.fileForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.fileForeground', 'The foreground color for file symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_FILE_FOREGROUND = registerColor('symbolIcon.fileForeground', foreground, localize('symbolIcon.fileForeground', 'The foreground color for file symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_FOLDER_FOREGROUND = registerColor('symbolIcon.folderForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.folderForeground', 'The foreground color for folder symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_FOLDER_FOREGROUND = registerColor('symbolIcon.folderForeground', foreground, localize('symbolIcon.folderForeground', 'The foreground color for folder symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); export const SYMBOL_ICON_FUNCTION_FOREGROUND = registerColor('symbolIcon.functionForeground', { dark: '#B180D7', @@ -105,19 +75,9 @@ export const SYMBOL_ICON_INTERFACE_FOREGROUND = registerColor('symbolIcon.interf hcLight: '#007ACC' }, localize('symbolIcon.interfaceForeground', 'The foreground color for interface symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_KEY_FOREGROUND = registerColor('symbolIcon.keyForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.keyForeground', 'The foreground color for key symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_KEY_FOREGROUND = registerColor('symbolIcon.keyForeground', foreground, localize('symbolIcon.keyForeground', 'The foreground color for key symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_KEYWORD_FOREGROUND = registerColor('symbolIcon.keywordForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.keywordForeground', 'The foreground color for keyword symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_KEYWORD_FOREGROUND = registerColor('symbolIcon.keywordForeground', foreground, localize('symbolIcon.keywordForeground', 'The foreground color for keyword symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); export const SYMBOL_ICON_METHOD_FOREGROUND = registerColor('symbolIcon.methodForeground', { dark: '#B180D7', @@ -126,110 +86,35 @@ export const SYMBOL_ICON_METHOD_FOREGROUND = registerColor('symbolIcon.methodFor hcLight: '#652D90' }, localize('symbolIcon.methodForeground', 'The foreground color for method symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); -export const SYMBOL_ICON_MODULE_FOREGROUND = registerColor('symbolIcon.moduleForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.moduleForeground', 'The foreground color for module symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_NAMESPACE_FOREGROUND = registerColor('symbolIcon.namespaceForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.namespaceForeground', 'The foreground color for namespace symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_NULL_FOREGROUND = registerColor('symbolIcon.nullForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.nullForeground', 'The foreground color for null symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_NUMBER_FOREGROUND = registerColor('symbolIcon.numberForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.numberForeground', 'The foreground color for number symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_OBJECT_FOREGROUND = registerColor('symbolIcon.objectForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.objectForeground', 'The foreground color for object symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_OPERATOR_FOREGROUND = registerColor('symbolIcon.operatorForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.operatorForeground', 'The foreground color for operator symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_PACKAGE_FOREGROUND = registerColor('symbolIcon.packageForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.packageForeground', 'The foreground color for package symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_PROPERTY_FOREGROUND = registerColor('symbolIcon.propertyForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.propertyForeground', 'The foreground color for property symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_REFERENCE_FOREGROUND = registerColor('symbolIcon.referenceForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.referenceForeground', 'The foreground color for reference symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_SNIPPET_FOREGROUND = registerColor('symbolIcon.snippetForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.snippetForeground', 'The foreground color for snippet symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_STRING_FOREGROUND = registerColor('symbolIcon.stringForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.stringForeground', 'The foreground color for string symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_STRUCT_FOREGROUND = registerColor('symbolIcon.structForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground, -}, localize('symbolIcon.structForeground', 'The foreground color for struct symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_TEXT_FOREGROUND = registerColor('symbolIcon.textForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.textForeground', 'The foreground color for text symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_TYPEPARAMETER_FOREGROUND = registerColor('symbolIcon.typeParameterForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.typeParameterForeground', 'The foreground color for type parameter symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); - -export const SYMBOL_ICON_UNIT_FOREGROUND = registerColor('symbolIcon.unitForeground', { - dark: foreground, - light: foreground, - hcDark: foreground, - hcLight: foreground -}, localize('symbolIcon.unitForeground', 'The foreground color for unit symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); +export const SYMBOL_ICON_MODULE_FOREGROUND = registerColor('symbolIcon.moduleForeground', foreground, localize('symbolIcon.moduleForeground', 'The foreground color for module symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_NAMESPACE_FOREGROUND = registerColor('symbolIcon.namespaceForeground', foreground, localize('symbolIcon.namespaceForeground', 'The foreground color for namespace symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_NULL_FOREGROUND = registerColor('symbolIcon.nullForeground', foreground, localize('symbolIcon.nullForeground', 'The foreground color for null symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_NUMBER_FOREGROUND = registerColor('symbolIcon.numberForeground', foreground, localize('symbolIcon.numberForeground', 'The foreground color for number symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_OBJECT_FOREGROUND = registerColor('symbolIcon.objectForeground', foreground, localize('symbolIcon.objectForeground', 'The foreground color for object symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_OPERATOR_FOREGROUND = registerColor('symbolIcon.operatorForeground', foreground, localize('symbolIcon.operatorForeground', 'The foreground color for operator symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_PACKAGE_FOREGROUND = registerColor('symbolIcon.packageForeground', foreground, localize('symbolIcon.packageForeground', 'The foreground color for package symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_PROPERTY_FOREGROUND = registerColor('symbolIcon.propertyForeground', foreground, localize('symbolIcon.propertyForeground', 'The foreground color for property symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_REFERENCE_FOREGROUND = registerColor('symbolIcon.referenceForeground', foreground, localize('symbolIcon.referenceForeground', 'The foreground color for reference symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_SNIPPET_FOREGROUND = registerColor('symbolIcon.snippetForeground', foreground, localize('symbolIcon.snippetForeground', 'The foreground color for snippet symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_STRING_FOREGROUND = registerColor('symbolIcon.stringForeground', foreground, localize('symbolIcon.stringForeground', 'The foreground color for string symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_STRUCT_FOREGROUND = registerColor('symbolIcon.structForeground', foreground, localize('symbolIcon.structForeground', 'The foreground color for struct symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_TEXT_FOREGROUND = registerColor('symbolIcon.textForeground', foreground, localize('symbolIcon.textForeground', 'The foreground color for text symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_TYPEPARAMETER_FOREGROUND = registerColor('symbolIcon.typeParameterForeground', foreground, localize('symbolIcon.typeParameterForeground', 'The foreground color for type parameter symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); + +export const SYMBOL_ICON_UNIT_FOREGROUND = registerColor('symbolIcon.unitForeground', foreground, localize('symbolIcon.unitForeground', 'The foreground color for unit symbols. These symbols appear in the outline, breadcrumb, and suggest widget.')); export const SYMBOL_ICON_VARIABLE_FOREGROUND = registerColor('symbolIcon.variableForeground', { dark: '#75BEFF', diff --git a/patched-vscode/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/patched-vscode/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index f44fc76e..eae47ac2 100644 --- a/patched-vscode/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/patched-vscode/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -7,7 +7,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CharCode } from 'vs/base/common/charCode'; import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { InvisibleCharacters, isBasicASCII } from 'vs/base/common/strings'; import 'vs/css!./unicodeHighlighter'; @@ -22,7 +22,7 @@ import { UnicodeHighlighterOptions, UnicodeHighlighterReason, UnicodeHighlighter import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { isModelDecorationInComment, isModelDecorationInString, isModelDecorationVisible } from 'vs/editor/common/viewModel/viewModelDecorations'; -import { HoverAnchor, HoverAnchorType, HoverParticipantRegistry, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { HoverAnchor, HoverAnchorType, HoverParticipantRegistry, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverParts } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { MarkdownHover, renderMarkdownHovers } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant'; import { BannerController } from 'vs/editor/contrib/unicodeHighlighter/browser/bannerController'; import * as nls from 'vs/nls'; @@ -506,9 +506,13 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa return result; } - public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: MarkdownHover[]): IDisposable { + public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: MarkdownHover[]): IRenderedHoverParts { return renderMarkdownHovers(context, hoverParts, this._editor, this._languageService, this._openerService); } + + public getAccessibleContent(hoverPart: MarkdownHover): string { + return hoverPart.contents.map(c => c.value).join('\n'); + } } function codePointToHex(codePoint: number): string { diff --git a/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/highlightDecorations.ts b/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/highlightDecorations.ts index b3825b0f..79ec7ad0 100644 --- a/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/highlightDecorations.ts +++ b/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/highlightDecorations.ts @@ -13,13 +13,13 @@ import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/ const wordHighlightBackground = registerColor('editor.wordHighlightBackground', { dark: '#575757B8', light: '#57575740', hcDark: null, hcLight: null }, nls.localize('wordHighlight', 'Background color of a symbol during read-access, like reading a variable. The color must not be opaque so as not to hide underlying decorations.'), true); registerColor('editor.wordHighlightStrongBackground', { dark: '#004972B8', light: '#0e639c40', hcDark: null, hcLight: null }, nls.localize('wordHighlightStrong', 'Background color of a symbol during write-access, like writing to a variable. The color must not be opaque so as not to hide underlying decorations.'), true); -registerColor('editor.wordHighlightTextBackground', { light: wordHighlightBackground, dark: wordHighlightBackground, hcDark: wordHighlightBackground, hcLight: wordHighlightBackground }, nls.localize('wordHighlightText', 'Background color of a textual occurrence for a symbol. The color must not be opaque so as not to hide underlying decorations.'), true); +registerColor('editor.wordHighlightTextBackground', wordHighlightBackground, nls.localize('wordHighlightText', 'Background color of a textual occurrence for a symbol. The color must not be opaque so as not to hide underlying decorations.'), true); const wordHighlightBorder = registerColor('editor.wordHighlightBorder', { light: null, dark: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('wordHighlightBorder', 'Border color of a symbol during read-access, like reading a variable.')); registerColor('editor.wordHighlightStrongBorder', { light: null, dark: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('wordHighlightStrongBorder', 'Border color of a symbol during write-access, like writing to a variable.')); -registerColor('editor.wordHighlightTextBorder', { light: wordHighlightBorder, dark: wordHighlightBorder, hcDark: wordHighlightBorder, hcLight: wordHighlightBorder }, nls.localize('wordHighlightTextBorder', "Border color of a textual occurrence for a symbol.")); -const overviewRulerWordHighlightForeground = registerColor('editorOverviewRuler.wordHighlightForeground', { dark: '#A0A0A0CC', light: '#A0A0A0CC', hcDark: '#A0A0A0CC', hcLight: '#A0A0A0CC' }, nls.localize('overviewRulerWordHighlightForeground', 'Overview ruler marker color for symbol highlights. The color must not be opaque so as not to hide underlying decorations.'), true); -const overviewRulerWordHighlightStrongForeground = registerColor('editorOverviewRuler.wordHighlightStrongForeground', { dark: '#C0A0C0CC', light: '#C0A0C0CC', hcDark: '#C0A0C0CC', hcLight: '#C0A0C0CC' }, nls.localize('overviewRulerWordHighlightStrongForeground', 'Overview ruler marker color for write-access symbol highlights. The color must not be opaque so as not to hide underlying decorations.'), true); -const overviewRulerWordHighlightTextForeground = registerColor('editorOverviewRuler.wordHighlightTextForeground', { dark: overviewRulerSelectionHighlightForeground, light: overviewRulerSelectionHighlightForeground, hcDark: overviewRulerSelectionHighlightForeground, hcLight: overviewRulerSelectionHighlightForeground }, nls.localize('overviewRulerWordHighlightTextForeground', 'Overview ruler marker color of a textual occurrence for a symbol. The color must not be opaque so as not to hide underlying decorations.'), true); +registerColor('editor.wordHighlightTextBorder', wordHighlightBorder, nls.localize('wordHighlightTextBorder', "Border color of a textual occurrence for a symbol.")); +const overviewRulerWordHighlightForeground = registerColor('editorOverviewRuler.wordHighlightForeground', '#A0A0A0CC', nls.localize('overviewRulerWordHighlightForeground', 'Overview ruler marker color for symbol highlights. The color must not be opaque so as not to hide underlying decorations.'), true); +const overviewRulerWordHighlightStrongForeground = registerColor('editorOverviewRuler.wordHighlightStrongForeground', '#C0A0C0CC', nls.localize('overviewRulerWordHighlightStrongForeground', 'Overview ruler marker color for write-access symbol highlights. The color must not be opaque so as not to hide underlying decorations.'), true); +const overviewRulerWordHighlightTextForeground = registerColor('editorOverviewRuler.wordHighlightTextForeground', overviewRulerSelectionHighlightForeground, nls.localize('overviewRulerWordHighlightTextForeground', 'Overview ruler marker color of a textual occurrence for a symbol. The color must not be opaque so as not to hide underlying decorations.'), true); const _WRITE_OPTIONS = ModelDecorationOptions.register({ description: 'word-highlight-strong', diff --git a/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider.ts b/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider.ts index 1d7a6569..adc31ecd 100644 --- a/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider.ts +++ b/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider.ts @@ -5,7 +5,7 @@ import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { DocumentHighlight, DocumentHighlightKind, MultiDocumentHighlightProvider, ProviderResult } from 'vs/editor/common/languages'; +import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProvider, MultiDocumentHighlightProvider, ProviderResult } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { Position } from 'vs/editor/common/core/position'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -14,10 +14,33 @@ import { ResourceMap } from 'vs/base/common/map'; import { LanguageFilter } from 'vs/editor/common/languageSelector'; -class TextualDocumentHighlightProvider implements MultiDocumentHighlightProvider { +class TextualDocumentHighlightProvider implements DocumentHighlightProvider, MultiDocumentHighlightProvider { selector: LanguageFilter = { language: '*' }; + provideDocumentHighlights(model: ITextModel, position: Position, token: CancellationToken): ProviderResult { + const result: DocumentHighlight[] = []; + + const word = model.getWordAtPosition({ + lineNumber: position.lineNumber, + column: position.column + }); + + if (!word) { + return Promise.resolve(result); + } + + if (model.isDisposed()) { + return; + } + + const matches = model.findMatches(word.word, true, false, true, USUAL_WORD_SEPARATORS, false); + return matches.map(m => ({ + range: m.range, + kind: DocumentHighlightKind.Text + })); + } + provideMultiDocumentHighlights(primaryModel: ITextModel, position: Position, otherModels: ITextModel[], token: CancellationToken): ProviderResult> { const result = new ResourceMap(); @@ -57,7 +80,7 @@ export class TextualMultiDocumentHighlightFeature extends Disposable { @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { super(); - + this._register(languageFeaturesService.documentHighlightProvider.register('*', new TextualDocumentHighlightProvider())); this._register(languageFeaturesService.multiDocumentHighlightProvider.register('*', new TextualDocumentHighlightProvider())); } } diff --git a/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index cae43742..2dfcc17a 100644 --- a/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/patched-vscode/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as arrays from 'vs/base/common/arrays'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { CancelablePromise, createCancelablePromise, first, timeout } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, Delayer, first } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -23,18 +22,19 @@ import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/commo import { IDiffEditor, IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProvider, MultiDocumentHighlightProvider } from 'vs/editor/common/languages'; +import { DocumentHighlight, DocumentHighlightProvider, MultiDocumentHighlightProvider } from 'vs/editor/common/languages'; import { IModelDeltaDecoration, ITextModel, shouldSynchronizeModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { getHighlightDecorationOptions } from 'vs/editor/contrib/wordHighlighter/browser/highlightDecorations'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { Schemas } from 'vs/base/common/network'; +import { matchesScheme, Schemas } from 'vs/base/common/network'; import { ResourceMap } from 'vs/base/common/map'; import { score } from 'vs/editor/common/languageSelector'; -// import { TextualMultiDocumentHighlightFeature } from 'vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider'; -// import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; +import { isEqual } from 'vs/base/common/resources'; +import { TextualMultiDocumentHighlightFeature } from 'vs/editor/contrib/wordHighlighter/browser/textualHighlightProvider'; +import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; const ctxHasWordHighlights = new RawContextKey('hasWordHighlights', false); @@ -43,11 +43,12 @@ export function getOccurrencesAtPosition(registry: LanguageFeatureRegistry(orderedByScore.map(provider => () => { return Promise.resolve(provider.provideDocumentHighlights(model, position, token)) .then(undefined, onUnexpectedExternalError); - }), arrays.isNonEmptyArray).then(result => { + }), (result): result is DocumentHighlight[] => result !== undefined && result !== null).then(result => { if (result) { const map = new ResourceMap(); map.set(model.uri, result); @@ -62,17 +63,17 @@ export function getOccurrencesAcrossMultipleModels(registry: LanguageFeatureRegi // in order of score ask the occurrences provider // until someone response with a good result - // (good = none empty array) + // (good = non undefined and non null ResourceMap) + // (result of size == 0 is valid, no highlights is a valid/expected result -- not a signal to fall back to other providers) return first | null | undefined>(orderedByScore.map(provider => () => { const filteredModels = otherModels.filter(otherModel => { return shouldSynchronizeModel(otherModel); }).filter(otherModel => { return score(provider.selector, otherModel.uri, otherModel.getLanguageId(), true, undefined, undefined) > 0; }); - return Promise.resolve(provider.provideMultiDocumentHighlights(model, position, filteredModels, token)) .then(undefined, onUnexpectedExternalError); - }), (t: ResourceMap | null | undefined): t is ResourceMap => t instanceof ResourceMap && t.size > 0); + }), (result): result is ResourceMap => result !== undefined && result !== null); } interface IOccurenceAtPositionRequest { @@ -183,76 +184,13 @@ class MultiModelOccurenceRequest extends OccurenceAtPositionRequest { } } -class TextualOccurenceRequest extends OccurenceAtPositionRequest { - - private readonly _otherModels: ITextModel[]; - private readonly _selectionIsEmpty: boolean; - private readonly _word: IWordAtPosition | null; - - constructor(model: ITextModel, selection: Selection, word: IWordAtPosition | null, wordSeparators: string, otherModels: ITextModel[]) { - super(model, selection, wordSeparators); - this._otherModels = otherModels; - this._selectionIsEmpty = selection.isEmpty(); - this._word = word; - } - - protected _compute(model: ITextModel, selection: Selection, wordSeparators: string, token: CancellationToken): Promise> { - return timeout(250, token).then(() => { - const result = new ResourceMap(); - - let wordResult; - if (this._word) { - wordResult = this._word; - } else { - wordResult = model.getWordAtPosition(selection.getPosition()); - } - - if (!wordResult) { - return new ResourceMap(); - } - - const allModels = [model, ...this._otherModels]; - - for (const otherModel of allModels) { - if (otherModel.isDisposed()) { - continue; - } - - const matches = otherModel.findMatches(wordResult.word, true, false, true, wordSeparators, false); - const highlights = matches.map(m => ({ - range: m.range, - kind: DocumentHighlightKind.Text - })); - - if (highlights) { - result.set(otherModel.uri, highlights); - } - } - return result; - }); - } - - public override isValid(model: ITextModel, selection: Selection, decorations: IEditorDecorationsCollection): boolean { - const currentSelectionIsEmpty = selection.isEmpty(); - if (this._selectionIsEmpty !== currentSelectionIsEmpty) { - return false; - } - return super.isValid(model, selection, decorations); - } -} function computeOccurencesAtPosition(registry: LanguageFeatureRegistry, model: ITextModel, selection: Selection, word: IWordAtPosition | null, wordSeparators: string): IOccurenceAtPositionRequest { - if (registry.has(model)) { - return new SemanticOccurenceAtPositionRequest(model, selection, wordSeparators, registry); - } - return new TextualOccurenceRequest(model, selection, word, wordSeparators, []); + return new SemanticOccurenceAtPositionRequest(model, selection, wordSeparators, registry); } function computeOccurencesMultiModel(registry: LanguageFeatureRegistry, model: ITextModel, selection: Selection, word: IWordAtPosition | null, wordSeparators: string, otherModels: ITextModel[]): IOccurenceAtPositionRequest { - if (registry.has(model)) { - return new MultiModelOccurenceRequest(model, selection, wordSeparators, registry, otherModels); - } - return new TextualOccurenceRequest(model, selection, word, wordSeparators, otherModels); + return new MultiModelOccurenceRequest(model, selection, wordSeparators, registry, otherModels); } registerModelAndPositionCommand('_executeDocumentHighlights', async (accessor, model, position) => { @@ -266,11 +204,11 @@ class WordHighlighter { private readonly editor: IActiveCodeEditor; private readonly providers: LanguageFeatureRegistry; private readonly multiDocumentProviders: LanguageFeatureRegistry; - private occurrencesHighlight: string; private readonly model: ITextModel; private readonly decorations: IEditorDecorationsCollection; private readonly toUnhook = new DisposableStore(); private readonly codeEditorService: ICodeEditorService; + private occurrencesHighlight: string; private workerRequestTokenId: number = 0; private workerRequest: IOccurenceAtPositionRequest | null; @@ -283,7 +221,9 @@ class WordHighlighter { private readonly _hasWordHighlights: IContextKey; private _ignorePositionChangeEvent: boolean; - private static storedDecorations: ResourceMap = new ResourceMap(); + private readonly runDelayer: Delayer = this.toUnhook.add(new Delayer(50)); + + private static storedDecorationIDs: ResourceMap = new ResourceMap(); private static query: IWordHighlighterQuery | null = null; constructor(editor: IActiveCodeEditor, providers: LanguageFeatureRegistry, multiProviders: LanguageFeatureRegistry, contextKeyService: IContextKeyService, @ICodeEditorService codeEditorService: ICodeEditorService) { @@ -307,7 +247,7 @@ class WordHighlighter { return; } - this._onPositionChanged(e); + this.runDelayer.trigger(() => { this._onPositionChanged(e); }); })); this.toUnhook.add(editor.onDidFocusEditorText((e) => { if (this.occurrencesHighlight === 'off') { @@ -316,11 +256,13 @@ class WordHighlighter { } if (!this.workerRequest) { - this._run(); + this.runDelayer.trigger(() => { this._run(); }); } })); this.toUnhook.add(editor.onDidChangeModelContent((e) => { - this._stopAll(); + if (!matchesScheme(this.model.uri, 'output')) { + this._stopAll(); + } })); this.toUnhook.add(editor.onDidChangeModel((e) => { if (!e.newModelUrl && e.oldModelUrl) { @@ -335,7 +277,22 @@ class WordHighlighter { const newValue = this.editor.getOption(EditorOption.occurrencesHighlight); if (this.occurrencesHighlight !== newValue) { this.occurrencesHighlight = newValue; - this._stopAll(); + switch (newValue) { + case 'off': + this._stopAll(); + break; + case 'singleFile': + this._stopAll(WordHighlighter.query?.modelInfo?.model); + break; + case 'multiFile': + if (WordHighlighter.query) { + this._run(true); + } + break; + default: + console.warn('Unknown occurrencesHighlight setting value:', newValue); + break; + } } })); @@ -361,6 +318,8 @@ class WordHighlighter { if (this.occurrencesHighlight === 'off') { return; } + + this.runDelayer.cancel(); this._run(); } @@ -423,13 +382,13 @@ class WordHighlighter { return; } - const currentDecorationIDs = WordHighlighter.storedDecorations.get(this.editor.getModel().uri); + const currentDecorationIDs = WordHighlighter.storedDecorationIDs.get(this.editor.getModel().uri); if (!currentDecorationIDs) { return; } this.editor.removeDecorations(currentDecorationIDs); - WordHighlighter.storedDecorations.delete(this.editor.getModel().uri); + WordHighlighter.storedDecorationIDs.delete(this.editor.getModel().uri); if (this.decorations.length > 0) { this.decorations.clear(); @@ -437,16 +396,16 @@ class WordHighlighter { } } - private _removeAllDecorations(): void { + private _removeAllDecorations(preservedModel?: ITextModel): void { const currentEditors = this.codeEditorService.listCodeEditors(); const deleteURI = []; // iterate over editors and store models in currentModels for (const editor of currentEditors) { - if (!editor.hasModel()) { + if (!editor.hasModel() || isEqual(editor.getModel().uri, preservedModel?.uri)) { continue; } - const currentDecorationIDs = WordHighlighter.storedDecorations.get(editor.getModel().uri); + const currentDecorationIDs = WordHighlighter.storedDecorationIDs.get(editor.getModel().uri); if (!currentDecorationIDs) { continue; } @@ -467,7 +426,7 @@ class WordHighlighter { } for (const uri of deleteURI) { - WordHighlighter.storedDecorations.delete(uri); + WordHighlighter.storedDecorationIDs.delete(uri); } } @@ -505,11 +464,11 @@ class WordHighlighter { } } - private _stopAll() { + private _stopAll(preservedModel?: ITextModel): void { // Remove any existing decorations // TODO: @Yoyokrazy -- this triggers as notebooks scroll, causing highlights to disappear momentarily. // maybe a nb type check? - this._removeAllDecorations(); + this._removeAllDecorations(preservedModel); // Cancel any renderDecorationsTimer if (this.renderDecorationsTimer !== -1) { @@ -623,13 +582,14 @@ class WordHighlighter { return currentModels; } - private _run(): void { + private _run(multiFileConfigChange?: boolean): void { let workerRequestIsValid; const hasTextFocus = this.editor.hasTextFocus(); if (!hasTextFocus) { // new nb cell scrolled in, didChangeModel fires if (!WordHighlighter.query) { // no previous query, nothing to highlight off of + this._stopAll(); return; } } else { // has text focus @@ -689,10 +649,22 @@ class WordHighlighter { this.renderDecorationsTimer = -1; this._beginRenderDecorations(); } - } else { + } else if (isEqual(this.editor.getModel().uri, WordHighlighter.query.modelInfo?.model.uri)) { // only trigger new worker requests from the primary model that initiated the query // case d) - // Stop all previous actions and start fresh - this._stopAll(); + + // check if the new queried word is contained in the range of a stored decoration for this model + if (!multiFileConfigChange) { + const currentModelDecorationRanges = this.decorations.getRanges(); + for (const storedRange of currentModelDecorationRanges) { + if (storedRange.containsPosition(this.editor.getPosition())) { + return; + } + } + } + + // stop all previous actions if new word is highlighted + // if we trigger the run off a setting change -> multifile highlighting, we do not want to remove decorations from this model + this._stopAll(multiFileConfigChange ? this.model : undefined); const myRequestId = ++this.workerRequestTokenId; this.workerRequestCompleted = false; @@ -703,7 +675,7 @@ class WordHighlighter { // 1) we have text focus, and a valid query was updated. // 2) we do not have text focus, and a valid query is cached. // the query will ALWAYS have the correct data for the current highlight request, so it can always be passed to the workerRequest safely - if (!WordHighlighter.query.modelInfo || WordHighlighter.query.modelInfo.model.isDisposed()) { + if (!WordHighlighter.query || !WordHighlighter.query.modelInfo || WordHighlighter.query.modelInfo.model.isDisposed()) { return; } this.workerRequest = this.computeWithModel(WordHighlighter.query.modelInfo.model, WordHighlighter.query.modelInfo.selection, WordHighlighter.query.word, otherModelsToHighlight); @@ -757,7 +729,7 @@ class WordHighlighter { const newDecorations: IModelDeltaDecoration[] = []; const uri = editor.getModel()?.uri; if (uri && this.workerRequestValue.has(uri)) { - const oldDecorationIDs: string[] | undefined = WordHighlighter.storedDecorations.get(uri); + const oldDecorationIDs: string[] | undefined = WordHighlighter.storedDecorationIDs.get(uri); const newDocumentHighlights = this.workerRequestValue.get(uri); if (newDocumentHighlights) { for (const highlight of newDocumentHighlights) { @@ -775,7 +747,7 @@ class WordHighlighter { editor.changeDecorations((changeAccessor) => { newDecorationIDs = changeAccessor.deltaDecorations(oldDecorationIDs ?? [], newDecorations); }); - WordHighlighter.storedDecorations = WordHighlighter.storedDecorations.set(uri, newDecorationIDs); + WordHighlighter.storedDecorationIDs = WordHighlighter.storedDecorationIDs.set(uri, newDecorationIDs); if (newDecorations.length > 0) { editorHighlighterContrib.wordHighlighter?.decorations.set(newDecorations); @@ -919,7 +891,7 @@ class TriggerWordHighlightAction extends EditorAction { id: 'editor.action.wordHighlight.trigger', label: nls.localize('wordHighlight.trigger.label', "Trigger Symbol Highlight"), alias: 'Trigger Symbol Highlight', - precondition: ctxHasWordHighlights.toNegated(), + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: 0, @@ -942,4 +914,4 @@ registerEditorContribution(WordHighlighterContribution.ID, WordHighlighterContri registerEditorAction(NextWordHighlightAction); registerEditorAction(PrevWordHighlightAction); registerEditorAction(TriggerWordHighlightAction); -// registerEditorFeature(TextualMultiDocumentHighlightFeature); +registerEditorFeature(TextualMultiDocumentHighlightFeature); diff --git a/patched-vscode/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts b/patched-vscode/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts index 388d4202..29381ecf 100644 --- a/patched-vscode/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts +++ b/patched-vscode/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts @@ -48,10 +48,10 @@ export abstract class MoveWordCommand extends EditorCommand { const wordSeparators = getMapForWordSeparators(editor.getOption(EditorOption.wordSeparators), editor.getOption(EditorOption.wordSegmenterLocales)); const model = editor.getModel(); const selections = editor.getSelections(); - + const hasMulticursor = selections.length > 1; const result = selections.map((sel) => { const inPosition = new Position(sel.positionLineNumber, sel.positionColumn); - const outPosition = this._move(wordSeparators, model, inPosition, this._wordNavigationType); + const outPosition = this._move(wordSeparators, model, inPosition, this._wordNavigationType, hasMulticursor); return this._moveTo(sel, outPosition, this._inSelectionMode); }); @@ -83,17 +83,17 @@ export abstract class MoveWordCommand extends EditorCommand { } } - protected abstract _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position; + protected abstract _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position; } export class WordLeftCommand extends MoveWordCommand { - protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { - return WordOperations.moveWordLeft(wordSeparators, model, position, wordNavigationType); + protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { + return WordOperations.moveWordLeft(wordSeparators, model, position, wordNavigationType, hasMulticursor); } } export class WordRightCommand extends MoveWordCommand { - protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { return WordOperations.moveWordRight(wordSeparators, model, position, wordNavigationType); } } @@ -187,8 +187,8 @@ export class CursorWordAccessibilityLeft extends WordLeftCommand { }); } - protected override _move(wordCharacterClassifier: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { - return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue, wordCharacterClassifier.intlSegmenterLocales), model, position, wordNavigationType); + protected override _move(wordCharacterClassifier: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { + return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue, wordCharacterClassifier.intlSegmenterLocales), model, position, wordNavigationType, hasMulticursor); } } @@ -202,8 +202,8 @@ export class CursorWordAccessibilityLeftSelect extends WordLeftCommand { }); } - protected override _move(wordCharacterClassifier: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { - return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue, wordCharacterClassifier.intlSegmenterLocales), model, position, wordNavigationType); + protected override _move(wordCharacterClassifier: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { + return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue, wordCharacterClassifier.intlSegmenterLocales), model, position, wordNavigationType, hasMulticursor); } } @@ -295,8 +295,8 @@ export class CursorWordAccessibilityRight extends WordRightCommand { }); } - protected override _move(wordCharacterClassifier: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { - return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue, wordCharacterClassifier.intlSegmenterLocales), model, position, wordNavigationType); + protected override _move(wordCharacterClassifier: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { + return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue, wordCharacterClassifier.intlSegmenterLocales), model, position, wordNavigationType, hasMulticursor); } } @@ -310,8 +310,8 @@ export class CursorWordAccessibilityRightSelect extends WordRightCommand { }); } - protected override _move(wordCharacterClassifier: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { - return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue, wordCharacterClassifier.intlSegmenterLocales), model, position, wordNavigationType); + protected override _move(wordCharacterClassifier: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { + return super._move(getMapForWordSeparators(EditorOptions.wordSeparators.defaultValue, wordCharacterClassifier.intlSegmenterLocales), model, position, wordNavigationType, hasMulticursor); } } diff --git a/patched-vscode/src/vs/editor/contrib/wordOperations/test/browser/wordOperations.test.ts b/patched-vscode/src/vs/editor/contrib/wordOperations/test/browser/wordOperations.test.ts index a06bf072..d90d44fd 100644 --- a/patched-vscode/src/vs/editor/contrib/wordOperations/test/browser/wordOperations.test.ts +++ b/patched-vscode/src/vs/editor/contrib/wordOperations/test/browser/wordOperations.test.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isFirefox } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -179,7 +180,11 @@ suite('WordOperations', () => { assert.deepStrictEqual(actual, EXPECTED); }); - test('cursorWordLeft - Recognize words', () => { + test('cursorWordLeft - Recognize words', function () { + if (isFirefox) { + // https://github.com/microsoft/vscode/issues/219843 + return this.skip(); + } const EXPECTED = [ '|/* |ã“ã‚Œ|ã¯|テスト|ã§ã™ |/*', ].join('\n'); @@ -217,6 +222,40 @@ suite('WordOperations', () => { assert.deepStrictEqual(actual, EXPECTED); }); + test('cursorWordLeft - issue #169904: cursors out of sync', () => { + const text = [ + '.grid1 {', + ' display: grid;', + ' grid-template-columns:', + ' [full-start] minmax(1em, 1fr)', + ' [main-start] minmax(0, 40em) [main-end]', + ' minmax(1em, 1fr) [full-end];', + '}', + '.grid2 {', + ' display: grid;', + ' grid-template-columns:', + ' [full-start] minmax(1em, 1fr)', + ' [main-start] minmax(0, 40em) [main-end] minmax(1em, 1fr) [full-end];', + '}', + ]; + withTestCodeEditor(text, {}, (editor) => { + editor.setSelections([ + new Selection(5, 44, 5, 44), + new Selection(6, 32, 6, 32), + new Selection(12, 44, 12, 44), + new Selection(12, 72, 12, 72), + ]); + cursorWordLeft(editor, false); + assert.deepStrictEqual(editor.getSelections(), [ + new Selection(5, 43, 5, 43), + new Selection(6, 31, 6, 31), + new Selection(12, 43, 12, 43), + new Selection(12, 71, 12, 71), + ]); + + }); + }); + test('cursorWordLeftSelect - issue #74369: cursorWordLeft and cursorWordLeftSelect do not behave consistently', () => { const EXPECTED = [ '|this.|is.|a.|test', @@ -365,7 +404,11 @@ suite('WordOperations', () => { assert.deepStrictEqual(actual, EXPECTED); }); - test('cursorWordRight - Recognize words', () => { + test('cursorWordRight - Recognize words', function () { + if (isFirefox) { + // https://github.com/microsoft/vscode/issues/219843 + return this.skip(); + } const EXPECTED = [ '/*| ã“ã‚Œ|ã¯|テスト|ã§ã™|/*|', ].join('\n'); diff --git a/patched-vscode/src/vs/editor/contrib/wordPartOperations/browser/wordPartOperations.ts b/patched-vscode/src/vs/editor/contrib/wordPartOperations/browser/wordPartOperations.ts index ebcabc41..5ed4d310 100644 --- a/patched-vscode/src/vs/editor/contrib/wordPartOperations/browser/wordPartOperations.ts +++ b/patched-vscode/src/vs/editor/contrib/wordPartOperations/browser/wordPartOperations.ts @@ -68,8 +68,8 @@ export class DeleteWordPartRight extends DeleteWordCommand { } export class WordPartLeftCommand extends MoveWordCommand { - protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { - return WordPartOperations.moveWordPartLeft(wordSeparators, model, position); + protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { + return WordPartOperations.moveWordPartLeft(wordSeparators, model, position, hasMulticursor); } } export class CursorWordPartLeft extends WordPartLeftCommand { @@ -111,7 +111,7 @@ export class CursorWordPartLeftSelect extends WordPartLeftCommand { CommandsRegistry.registerCommandAlias('cursorWordPartStartLeftSelect', 'cursorWordPartLeftSelect'); export class WordPartRightCommand extends MoveWordCommand { - protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType, hasMulticursor: boolean): Position { return WordPartOperations.moveWordPartRight(wordSeparators, model, position); } } diff --git a/patched-vscode/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts b/patched-vscode/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts index 12258f71..b3a72759 100644 --- a/patched-vscode/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts +++ b/patched-vscode/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand } from 'vs/editor/browser/editorExtensions'; diff --git a/patched-vscode/src/vs/editor/editor.all.ts b/patched-vscode/src/vs/editor/editor.all.ts index 8c50e8dc..e40c7056 100644 --- a/patched-vscode/src/vs/editor/editor.all.ts +++ b/patched-vscode/src/vs/editor/editor.all.ts @@ -42,7 +42,9 @@ import 'vs/editor/contrib/links/browser/links'; import 'vs/editor/contrib/longLinesHelper/browser/longLinesHelper'; import 'vs/editor/contrib/multicursor/browser/multicursor'; import 'vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution'; +import 'vs/editor/contrib/inlineEdits/browser/inlineEdits.contribution'; import 'vs/editor/contrib/parameterHints/browser/parameterHints'; +import 'vs/editor/contrib/placeholderText/browser/placeholderText.contribution'; import 'vs/editor/contrib/rename/browser/rename'; import 'vs/editor/contrib/sectionHeaders/browser/sectionHeaders'; import 'vs/editor/contrib/semanticTokens/browser/documentSemanticTokens'; diff --git a/patched-vscode/src/vs/editor/editor.worker.ts b/patched-vscode/src/vs/editor/editor.worker.ts index 7c53a609..a50ddd14 100644 --- a/patched-vscode/src/vs/editor/editor.worker.ts +++ b/patched-vscode/src/vs/editor/editor.worker.ts @@ -3,30 +3,4 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker'; -import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; - -let initialized = false; - -export function initialize(foreignModule: any) { - if (initialized) { - return; - } - initialized = true; - - const simpleWorker = new SimpleWorkerServer((msg) => { - globalThis.postMessage(msg); - }, (host: IEditorWorkerHost) => new EditorSimpleWorker(host, foreignModule)); - - globalThis.onmessage = (e: MessageEvent) => { - simpleWorker.onmessage(e.data); - }; -} - -globalThis.onmessage = (e: MessageEvent) => { - // Ignore first message in this case and initialize if not yet initialized - if (!initialized) { - initialize(null); - } -}; +export * from 'vs/editor/common/services/editorWorkerBootstrap'; diff --git a/patched-vscode/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts b/patched-vscode/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts index e251afa2..9700eed5 100644 --- a/patched-vscode/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts +++ b/patched-vscode/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts @@ -111,7 +111,7 @@ export class StandaloneQuickInputService implements IQuickInputService { ) { } - pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { + pick>(picks: Promise[]> | QuickPickInput[], options?: O, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { return (this.activeService as unknown as QuickInputController /* TS fail */).pick(picks, options, token); } @@ -119,8 +119,10 @@ export class StandaloneQuickInputService implements IQuickInputService { return this.activeService.input(options, token); } - createQuickPick(): IQuickPick { - return this.activeService.createQuickPick(); + createQuickPick(options: { useSeparators: true }): IQuickPick; + createQuickPick(options?: { useSeparators: boolean }): IQuickPick; + createQuickPick(options: { useSeparators: boolean } = { useSeparators: false }): IQuickPick { + return this.activeService.createQuickPick(options); } createInputBox(): IInputBox { diff --git a/patched-vscode/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/patched-vscode/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 84deb53c..3ccfa128 100644 --- a/patched-vscode/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/patched-vscode/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -283,7 +283,6 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon ) { const options = { ..._options }; options.ariaLabel = options.ariaLabel || StandaloneCodeEditorNLS.editorViewAccessibleLabel; - options.ariaLabel = options.ariaLabel + ';' + (StandaloneCodeEditorNLS.accessibilityHelpMessage); super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService); if (keybindingService instanceof StandaloneKeybindingService) { diff --git a/patched-vscode/src/vs/editor/standalone/browser/standaloneEditor.ts b/patched-vscode/src/vs/editor/standalone/browser/standaloneEditor.ts index d1a70eba..fbfe4e18 100644 --- a/patched-vscode/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/patched-vscode/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -12,7 +12,7 @@ import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from 'vs/editor/browser/services/webWorker'; +import { IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from 'vs/editor/standalone/browser/standaloneWebWorker'; import { ApplyUpdateResult, ConfigurationChangedEvent, EditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; @@ -21,7 +21,6 @@ import { IRange } from 'vs/editor/common/core/range'; import { EditorType, IDiffEditor } from 'vs/editor/common/editorCommon'; import * as languages from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { NullState, nullTokenize } from 'vs/editor/common/languages/nullTokenize'; import { FindMatch, ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model'; @@ -333,7 +332,7 @@ export function onDidChangeModelLanguage(listener: (e: { readonly model: ITextMo * Specify an AMD module to load that will `create` an object that will be proxied. */ export function createWebWorker(opts: IWebWorkerOptions): MonacoWebWorker { - return actualCreateWebWorker(StandaloneServices.get(IModelService), StandaloneServices.get(ILanguageConfigurationService), opts); + return actualCreateWebWorker(StandaloneServices.get(IModelService), opts); } /** diff --git a/patched-vscode/src/vs/editor/standalone/browser/standaloneServices.ts b/patched-vscode/src/vs/editor/standalone/browser/standaloneServices.ts index 5e15b129..e4f06ac4 100644 --- a/patched-vscode/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/patched-vscode/src/vs/editor/standalone/browser/standaloneServices.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/editor/common/languages/languageConfigurationRegistry'; import 'vs/editor/standalone/browser/standaloneCodeEditorService'; import 'vs/editor/standalone/browser/standaloneLayoutService'; import 'vs/platform/undoRedo/common/undoRedoService'; @@ -89,12 +88,17 @@ import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/com import { DefaultConfiguration } from 'vs/platform/configuration/common/configurations'; import { WorkspaceEdit } from 'vs/editor/common/languages'; import { AccessibilitySignal, AccessibilityModality, IAccessibilitySignalService, Sound } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { LogService } from 'vs/platform/log/common/logService'; import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionKind, IEnvironmentService, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; import { mainWindow } from 'vs/base/browser/window'; import { ResourceMap } from 'vs/base/common/map'; +import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; +import { StandaloneTreeSitterParserService } from 'vs/editor/standalone/browser/standaloneTreeSitterService'; +import { IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker'; class SimpleModel implements IResolvedTextEditorModel { @@ -251,7 +255,7 @@ class StandaloneDialogService implements IDialogService { return { confirmed, checkboxChecked: false // unsupported - } as IConfirmationResult; + }; } private doConfirm(message: string, detail?: string): boolean { @@ -1072,6 +1076,24 @@ class StandaloneContextMenuService extends ContextMenuService { } } +export const standaloneEditorWorkerDescriptor: IWorkerDescriptor = { + amdModuleId: 'vs/editor/common/services/editorSimpleWorker', + esmModuleLocation: undefined, + label: 'editorWorkerService' +}; + +class StandaloneEditorWorkerService extends EditorWorkerService { + constructor( + @IModelService modelService: IModelService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @ILogService logService: ILogService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + ) { + super(standaloneEditorWorkerDescriptor, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); + } +} + class StandaloneAccessbilitySignalService implements IAccessibilitySignalService { _serviceBrand: undefined; async playSignal(cue: AccessibilitySignal, options: {}): Promise { @@ -1130,7 +1152,7 @@ registerSingleton(IContextKeyService, ContextKeyService, InstantiationType.Eager registerSingleton(IProgressService, StandaloneProgressService, InstantiationType.Eager); registerSingleton(IEditorProgressService, StandaloneEditorProgressService, InstantiationType.Eager); registerSingleton(IStorageService, InMemoryStorageService, InstantiationType.Eager); -registerSingleton(IEditorWorkerService, EditorWorkerService, InstantiationType.Eager); +registerSingleton(IEditorWorkerService, StandaloneEditorWorkerService, InstantiationType.Eager); registerSingleton(IBulkEditService, StandaloneBulkEditService, InstantiationType.Eager); registerSingleton(IWorkspaceTrustManagementService, StandaloneWorkspaceTrustManagementService, InstantiationType.Eager); registerSingleton(ITextModelService, StandaloneTextModelService, InstantiationType.Eager); @@ -1145,6 +1167,7 @@ registerSingleton(IClipboardService, BrowserClipboardService, InstantiationType. registerSingleton(IContextMenuService, StandaloneContextMenuService, InstantiationType.Eager); registerSingleton(IMenuService, MenuService, InstantiationType.Eager); registerSingleton(IAccessibilitySignalService, StandaloneAccessbilitySignalService, InstantiationType.Eager); +registerSingleton(ITreeSitterParserService, StandaloneTreeSitterParserService, InstantiationType.Eager); /** * We don't want to eagerly instantiate services because embedders get a one time chance diff --git a/patched-vscode/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts b/patched-vscode/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts new file mode 100644 index 00000000..153a9b88 --- /dev/null +++ b/patched-vscode/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// eslint-disable-next-line local/code-import-patterns +import type { Parser } from '@vscode/tree-sitter-wasm'; +import { Event } from 'vs/base/common/event'; +import { ITextModel } from 'vs/editor/common/model'; +import { ITreeSitterParseResult, ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; + +/** + * The monaco build doesn't like the dynamic import of tree sitter in the real service. + * We use a dummy sertive here to make the build happy. + */ +export class StandaloneTreeSitterParserService implements ITreeSitterParserService { + readonly _serviceBrand: undefined; + onDidAddLanguage: Event<{ id: string; language: Parser.Language }> = Event.None; + + getOrInitLanguage(_languageId: string): Parser.Language | undefined { + return undefined; + } + getParseResult(textModel: ITextModel): ITreeSitterParseResult | undefined { + return undefined; + } +} diff --git a/patched-vscode/src/vs/editor/browser/services/webWorker.ts b/patched-vscode/src/vs/editor/standalone/browser/standaloneWebWorker.ts similarity index 81% rename from patched-vscode/src/vs/editor/browser/services/webWorker.ts rename to patched-vscode/src/vs/editor/standalone/browser/standaloneWebWorker.ts index b5757c58..74b1de91 100644 --- a/patched-vscode/src/vs/editor/browser/services/webWorker.ts +++ b/patched-vscode/src/vs/editor/standalone/browser/standaloneWebWorker.ts @@ -5,16 +5,17 @@ import { getAllMethodNames } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; +import { IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker'; import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IModelService } from 'vs/editor/common/services/model'; +import { standaloneEditorWorkerDescriptor } from 'vs/editor/standalone/browser/standaloneServices'; /** * Create a new web worker that has model syncing capabilities built in. * Specify an AMD module to load that will `create` an object that will be proxied. */ -export function createWebWorker(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker { - return new MonacoWebWorkerImpl(modelService, languageConfigurationService, opts); +export function createWebWorker(modelService: IModelService, opts: IWebWorkerOptions): MonacoWebWorker { + return new MonacoWebWorkerImpl(modelService, opts); } /** @@ -68,8 +69,13 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement private _foreignModuleCreateData: any | null; private _foreignProxy: Promise | null; - constructor(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions) { - super(modelService, opts.keepIdleModels || false, opts.label, languageConfigurationService); + constructor(modelService: IModelService, opts: IWebWorkerOptions) { + const workerDescriptor: IWorkerDescriptor = { + amdModuleId: standaloneEditorWorkerDescriptor.amdModuleId, + esmModuleLocation: standaloneEditorWorkerDescriptor.esmModuleLocation, + label: opts.label, + }; + super(workerDescriptor, opts.keepIdleModels || false, modelService); this._foreignModuleId = opts.moduleId; this._foreignModuleCreateData = opts.createData || null; this._foreignModuleHost = opts.host || null; @@ -93,11 +99,11 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement if (!this._foreignProxy) { this._foreignProxy = this._getProxy().then((proxy) => { const foreignHostMethods = this._foreignModuleHost ? getAllMethodNames(this._foreignModuleHost) : []; - return proxy.loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData, foreignHostMethods).then((foreignMethods) => { + return proxy.$loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData, foreignHostMethods).then((foreignMethods) => { this._foreignModuleCreateData = null; const proxyMethodRequest = (method: string, args: any[]): Promise => { - return proxy.fmr(method, args); + return proxy.$fmr(method, args); }; const createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => Promise): () => Promise => { @@ -124,6 +130,6 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement } public withSyncedResources(resources: URI[]): Promise { - return this._withSyncedResources(resources).then(_ => this.getProxy()); + return this.workerWithSyncedResources(resources).then(_ => this.getProxy()); } } diff --git a/patched-vscode/src/vs/editor/standalone/common/monarch/monarchCompile.ts b/patched-vscode/src/vs/editor/standalone/common/monarch/monarchCompile.ts index e9f5f393..599c89a0 100644 --- a/patched-vscode/src/vs/editor/standalone/common/monarch/monarchCompile.ts +++ b/patched-vscode/src/vs/editor/standalone/common/monarch/monarchCompile.ts @@ -434,21 +434,21 @@ export function compile(languageId: string, json: IMonarchLanguage): monarchComm } // Create our lexer - const lexer: monarchCommon.ILexer = {}; - lexer.languageId = languageId; - lexer.includeLF = bool(json.includeLF, false); - lexer.noThrow = false; // raise exceptions during compilation - lexer.maxStack = 100; - - // Set standard fields: be defensive about types - lexer.start = (typeof json.start === 'string' ? json.start : null); - lexer.ignoreCase = bool(json.ignoreCase, false); - lexer.unicode = bool(json.unicode, false); - - lexer.tokenPostfix = string(json.tokenPostfix, '.' + lexer.languageId); - lexer.defaultToken = string(json.defaultToken, 'source'); - - lexer.usesEmbedded = false; // becomes true if we find a nextEmbedded action + const lexer: monarchCommon.ILexer = { + languageId: languageId, + includeLF: bool(json.includeLF, false), + noThrow: false, // raise exceptions during compilation + maxStack: 100, + start: (typeof json.start === 'string' ? json.start : null), + ignoreCase: bool(json.ignoreCase, false), + unicode: bool(json.unicode, false), + tokenPostfix: string(json.tokenPostfix, '.' + languageId), + defaultToken: string(json.defaultToken, 'source'), + usesEmbedded: false, // becomes true if we find a nextEmbedded action + stateNames: {}, + tokenizer: {}, + brackets: [] + }; // For calling compileAction later on const lexerMin: monarchCommon.ILexerMin = json; diff --git a/patched-vscode/src/vs/editor/standalone/test/browser/monarch.test.ts b/patched-vscode/src/vs/editor/standalone/test/browser/monarch.test.ts index 12feefa2..7a1ba1d9 100644 --- a/patched-vscode/src/vs/editor/standalone/test/browser/monarch.test.ts +++ b/patched-vscode/src/vs/editor/standalone/test/browser/monarch.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Token, TokenizationRegistry } from 'vs/editor/common/languages'; @@ -233,7 +233,7 @@ suite('Monarch', () => { uselessReplaceKey2: '@uselessReplaceKey3', uselessReplaceKey3: '@uselessReplaceKey4', uselessReplaceKey4: '@uselessReplaceKey5', - uselessReplaceKey5: '@ham' || '', + uselessReplaceKey5: '@ham', tokenizer: { root: [ { diff --git a/patched-vscode/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/patched-vscode/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 92e38556..745c0075 100644 --- a/patched-vscode/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/patched-vscode/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Color } from 'vs/base/common/color'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/editor/standalone/test/browser/standaloneServices.test.ts b/patched-vscode/src/vs/editor/standalone/test/browser/standaloneServices.test.ts index e44de083..64978384 100644 --- a/patched-vscode/src/vs/editor/standalone/test/browser/standaloneServices.test.ts +++ b/patched-vscode/src/vs/editor/standalone/test/browser/standaloneServices.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/test/browser/commands/shiftCommand.test.ts b/patched-vscode/src/vs/editor/test/browser/commands/shiftCommand.test.ts index a769b21c..8d98e803 100644 --- a/patched-vscode/src/vs/editor/test/browser/commands/shiftCommand.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/commands/shiftCommand.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand'; diff --git a/patched-vscode/src/vs/editor/test/browser/commands/sideEditing.test.ts b/patched-vscode/src/vs/editor/test/browser/commands/sideEditing.test.ts index 6cdb2657..fbd9a36c 100644 --- a/patched-vscode/src/vs/editor/test/browser/commands/sideEditing.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/commands/sideEditing.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts b/patched-vscode/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts index 5b4d0994..15f9bb99 100644 --- a/patched-vscode/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TrimTrailingWhitespaceCommand, trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; diff --git a/patched-vscode/src/vs/editor/test/browser/config/editorConfiguration.test.ts b/patched-vscode/src/vs/editor/test/browser/config/editorConfiguration.test.ts index b507c546..911b7301 100644 --- a/patched-vscode/src/vs/editor/test/browser/config/editorConfiguration.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/config/editorConfiguration.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEnvConfiguration } from 'vs/editor/browser/config/editorConfiguration'; import { migrateOptions } from 'vs/editor/browser/config/migrateOptions'; diff --git a/patched-vscode/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts b/patched-vscode/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts index d44cc97a..a2e3ace5 100644 --- a/patched-vscode/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ComputedEditorOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorLayoutInfo, EditorLayoutInfoComputer, EditorMinimapOptions, EditorOption, EditorOptions, InternalEditorRenderLineNumbersOptions, InternalEditorScrollbarOptions, RenderLineNumbersType, RenderMinimap } from 'vs/editor/common/config/editorOptions'; diff --git a/patched-vscode/src/vs/editor/test/browser/controller/cursor.integrationTest.ts b/patched-vscode/src/vs/editor/test/browser/controller/cursor.integrationTest.ts index 6373a6fd..bd956a35 100644 --- a/patched-vscode/src/vs/editor/test/browser/controller/cursor.integrationTest.ts +++ b/patched-vscode/src/vs/editor/test/browser/controller/cursor.integrationTest.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Selection } from 'vs/editor/common/core/selection'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; diff --git a/patched-vscode/src/vs/editor/test/browser/controller/cursor.test.ts b/patched-vscode/src/vs/editor/test/browser/controller/cursor.test.ts index a86e95c1..bb2abffb 100644 --- a/patched-vscode/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/controller/cursor.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts b/patched-vscode/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts index 2e312c53..c4bee4b9 100644 --- a/patched-vscode/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreNavigationCommands } from 'vs/editor/browser/coreCommands'; import { Position } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/patched-vscode/src/vs/editor/test/browser/controller/textAreaInput.test.ts index 19fcf3b1..fd2e23e3 100644 --- a/patched-vscode/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { OperatingSystem } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/editor/test/browser/controller/textAreaState.test.ts b/patched-vscode/src/vs/editor/test/browser/controller/textAreaState.test.ts index 087e9931..2baa9ddd 100644 --- a/patched-vscode/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Disposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ITextAreaWrapper, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; diff --git a/patched-vscode/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/patched-vscode/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index b8eb27e6..9160c070 100644 --- a/patched-vscode/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/test/browser/services/openerService.test.ts b/patched-vscode/src/vs/editor/test/browser/services/openerService.test.ts index 26dea91e..d732776a 100644 --- a/patched-vscode/src/vs/editor/test/browser/services/openerService.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/services/openerService.test.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 * as assert from 'assert'; +import assert from 'assert'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/test/browser/services/treeSitterParserService.test.ts b/patched-vscode/src/vs/editor/test/browser/services/treeSitterParserService.test.ts new file mode 100644 index 00000000..ca9dcaac --- /dev/null +++ b/patched-vscode/src/vs/editor/test/browser/services/treeSitterParserService.test.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { TextModelTreeSitter, TreeSitterImporter, TreeSitterLanguages } from 'vs/editor/browser/services/treeSitter/treeSitterParserService'; +import type { Parser } from '@vscode/tree-sitter-wasm'; +import { createTextModel } from 'vs/editor/test/common/testTextModel'; +import { timeout } from 'vs/base/common/async'; +import { ConsoleMainLogger, ILogService } from 'vs/platform/log/common/log'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { LogService } from 'vs/platform/log/common/logService'; +import { mock } from 'vs/base/test/common/mock'; + +class MockParser implements Parser { + static async init(): Promise { } + delete(): void { } + parse(input: string | Parser.Input, oldTree?: Parser.Tree, options?: Parser.Options): Parser.Tree { + return new MockTree(); + } + getIncludedRanges(): Parser.Range[] { + return []; + } + getTimeoutMicros(): number { return 0; } + setTimeoutMicros(timeout: number): void { } + reset(): void { } + getLanguage(): Parser.Language { return {} as any; } + setLanguage(): void { } + getLogger(): Parser.Logger { + throw new Error('Method not implemented.'); + } + setLogger(logFunc?: Parser.Logger | false | null): void { + throw new Error('Method not implemented.'); + } +} + +class MockTreeSitterImporter extends TreeSitterImporter { + public override async getParserClass(): Promise { + return MockParser as any; + } +} + +class MockTree implements Parser.Tree { + editorLanguage: string = ''; + editorContents: string = ''; + rootNode: Parser.SyntaxNode = {} as any; + rootNodeWithOffset(offsetBytes: number, offsetExtent: Parser.Point): Parser.SyntaxNode { + throw new Error('Method not implemented.'); + } + copy(): Parser.Tree { + throw new Error('Method not implemented.'); + } + delete(): void { } + edit(edit: Parser.Edit): Parser.Tree { + return this; + } + walk(): Parser.TreeCursor { + throw new Error('Method not implemented.'); + } + getChangedRanges(other: Parser.Tree): Parser.Range[] { + throw new Error('Method not implemented.'); + } + getIncludedRanges(): Parser.Range[] { + throw new Error('Method not implemented.'); + } + getEditedRange(other: Parser.Tree): Parser.Range { + throw new Error('Method not implemented.'); + } + getLanguage(): Parser.Language { + throw new Error('Method not implemented.'); + } +} + +class MockLanguage implements Parser.Language { + version: number = 0; + fieldCount: number = 0; + stateCount: number = 0; + nodeTypeCount: number = 0; + fieldNameForId(fieldId: number): string | null { + throw new Error('Method not implemented.'); + } + fieldIdForName(fieldName: string): number | null { + throw new Error('Method not implemented.'); + } + idForNodeType(type: string, named: boolean): number { + throw new Error('Method not implemented.'); + } + nodeTypeForId(typeId: number): string | null { + throw new Error('Method not implemented.'); + } + nodeTypeIsNamed(typeId: number): boolean { + throw new Error('Method not implemented.'); + } + nodeTypeIsVisible(typeId: number): boolean { + throw new Error('Method not implemented.'); + } + nextState(stateId: number, typeId: number): number { + throw new Error('Method not implemented.'); + } + query(source: string): Parser.Query { + throw new Error('Method not implemented.'); + } + lookaheadIterator(stateId: number): Parser.LookaheadIterable | null { + throw new Error('Method not implemented.'); + } + languageId: string = ''; +} + +suite('TreeSitterParserService', function () { + const treeSitterImporter: TreeSitterImporter = new MockTreeSitterImporter(); + let logService: ILogService; + let telemetryService: ITelemetryService; + setup(function () { + logService = new LogService(new ConsoleMainLogger()); + telemetryService = new class extends mock() { + override async publicLog2() { + // + } + }; + }); + + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + test('TextModelTreeSitter race condition: first language is slow to load', async function () { + class MockTreeSitterLanguages extends TreeSitterLanguages { + private async _fetchJavascript(): Promise { + await timeout(200); + const language = new MockLanguage(); + language.languageId = 'javascript'; + this._onDidAddLanguage.fire({ id: 'javascript', language }); + } + public override getOrInitLanguage(languageId: string): Parser.Language | undefined { + if (languageId === 'javascript') { + this._fetchJavascript(); + return undefined; + } + const language = new MockLanguage(); + language.languageId = languageId; + return language; + } + } + + const treeSitterParser: TreeSitterLanguages = store.add(new MockTreeSitterLanguages(treeSitterImporter, {} as any, { isBuilt: false } as any, new Map())); + const textModel = store.add(createTextModel('console.log("Hello, world!");', 'javascript')); + const textModelTreeSitter = store.add(new TextModelTreeSitter(textModel, treeSitterParser, treeSitterImporter, logService, telemetryService)); + textModel.setLanguage('typescript'); + await timeout(300); + assert.strictEqual((textModelTreeSitter.parseResult?.language as MockLanguage).languageId, 'typescript'); + }); +}); diff --git a/patched-vscode/src/vs/editor/test/browser/testCodeEditor.ts b/patched-vscode/src/vs/editor/test/browser/testCodeEditor.ts index 5fcd0c1b..5a79e483 100644 --- a/patched-vscode/src/vs/editor/test/browser/testCodeEditor.ts +++ b/patched-vscode/src/vs/editor/test/browser/testCodeEditor.ts @@ -23,9 +23,11 @@ import { LanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/model'; import { ModelService } from 'vs/editor/common/services/modelService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/browser/config/testConfiguration'; import { TestCodeEditorService, TestCommandService } from 'vs/editor/test/browser/editorTestServices'; +import { TestTreeSitterParserService } from 'vs/editor/test/common/services/testTreeSitterService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { TestEditorWorkerService } from 'vs/editor/test/common/services/testEditorWorkerService'; import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; @@ -218,6 +220,7 @@ export function createCodeEditorServices(disposables: DisposableStore, services: }); define(ILanguageFeatureDebounceService, LanguageFeatureDebounceService); define(ILanguageFeaturesService, LanguageFeaturesService); + define(ITreeSitterParserService, TestTreeSitterParserService); const instantiationService = disposables.add(new TestInstantiationService(services, true)); disposables.add(toDisposable(() => { diff --git a/patched-vscode/src/vs/editor/test/browser/testCommand.ts b/patched-vscode/src/vs/editor/test/browser/testCommand.ts index 60abb6d3..e12d2cc5 100644 --- a/patched-vscode/src/vs/editor/test/browser/testCommand.ts +++ b/patched-vscode/src/vs/editor/test/browser/testCommand.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IRange } from 'vs/editor/common/core/range'; import { Selection, ISelection } from 'vs/editor/common/core/selection'; import { ICommand, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; diff --git a/patched-vscode/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts b/patched-vscode/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index ab0d682a..4fcfa5cf 100644 --- a/patched-vscode/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; diff --git a/patched-vscode/src/vs/editor/test/browser/view/viewLayer.test.ts b/patched-vscode/src/vs/editor/test/browser/view/viewLayer.test.ts index 0fcb0b1d..be227dce 100644 --- a/patched-vscode/src/vs/editor/test/browser/view/viewLayer.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/view/viewLayer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer'; @@ -45,7 +45,7 @@ suite('RenderedLinesCollection onLinesDeleted', () => { ensureNoDisposablesAreLeakedInTestSuite(); function testOnModelLinesDeleted(deleteFromLineNumber: number, deleteToLineNumber: number, expectedDeleted: string[], expectedState: ILinesCollectionState): void { - const col = new RenderedLinesCollection(() => new TestLine('new')); + const col = new RenderedLinesCollection({ createLine: () => new TestLine('new') }); col._set(6, [ new TestLine('old6'), new TestLine('old7'), @@ -322,7 +322,7 @@ suite('RenderedLinesCollection onLineChanged', () => { ensureNoDisposablesAreLeakedInTestSuite(); function testOnModelLineChanged(changedLineNumber: number, expectedPinged: boolean, expectedState: ILinesCollectionState): void { - const col = new RenderedLinesCollection(() => new TestLine('new')); + const col = new RenderedLinesCollection({ createLine: () => new TestLine('new') }); col._set(6, [ new TestLine('old6'), new TestLine('old7'), @@ -405,7 +405,7 @@ suite('RenderedLinesCollection onLinesInserted', () => { ensureNoDisposablesAreLeakedInTestSuite(); function testOnModelLinesInserted(insertFromLineNumber: number, insertToLineNumber: number, expectedDeleted: string[], expectedState: ILinesCollectionState): void { - const col = new RenderedLinesCollection(() => new TestLine('new')); + const col = new RenderedLinesCollection({ createLine: () => new TestLine('new') }); col._set(6, [ new TestLine('old6'), new TestLine('old7'), @@ -683,7 +683,7 @@ suite('RenderedLinesCollection onTokensChanged', () => { ensureNoDisposablesAreLeakedInTestSuite(); function testOnModelTokensChanged(changedFromLineNumber: number, changedToLineNumber: number, expectedPinged: boolean, expectedState: ILinesCollectionState): void { - const col = new RenderedLinesCollection(() => new TestLine('new')); + const col = new RenderedLinesCollection({ createLine: () => new TestLine('new') }); col._set(6, [ new TestLine('old6'), new TestLine('old7'), diff --git a/patched-vscode/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts b/patched-vscode/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts index 0974ab8d..292aa0ed 100644 --- a/patched-vscode/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; diff --git a/patched-vscode/src/vs/editor/test/browser/viewModel/testViewModel.ts b/patched-vscode/src/vs/editor/test/browser/viewModel/testViewModel.ts index fe7f0c6e..36749b71 100644 --- a/patched-vscode/src/vs/editor/test/browser/viewModel/testViewModel.ts +++ b/patched-vscode/src/vs/editor/test/browser/viewModel/testViewModel.ts @@ -22,6 +22,8 @@ export function testViewModel(text: string[], options: IEditorOptions, callback: const viewModel = new ViewModel(EDITOR_ID, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!, testLanguageConfigurationService, new TestThemeService(), { setVisibleLines(visibleLines, stabilized) { }, + }, { + batchChanges: (cb) => cb(), }); callback(viewModel, model); diff --git a/patched-vscode/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts b/patched-vscode/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts index 02786c05..a89fc01e 100644 --- a/patched-vscode/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts b/patched-vscode/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts index 4b515f97..ff16b570 100644 --- a/patched-vscode/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts b/patched-vscode/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts index 192b4b6d..a09d5c98 100644 --- a/patched-vscode/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts b/patched-vscode/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts index 1f08e089..53ecda34 100644 --- a/patched-vscode/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts +++ b/patched-vscode/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { UnchangedRegion } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { LineRange } from 'vs/editor/common/core/lineRange'; diff --git a/patched-vscode/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts b/patched-vscode/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts new file mode 100644 index 00000000..0a104966 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts @@ -0,0 +1,226 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from "assert"; +import { DisposableStore } from "vs/base/common/lifecycle"; +import { IObservable, derivedHandleChanges } from "vs/base/common/observable"; +import { ensureNoDisposablesAreLeakedInTestSuite } from "vs/base/test/common/utils"; +import { ICodeEditor } from "vs/editor/browser/editorBrowser"; +import { ObservableCodeEditor, observableCodeEditor } from "vs/editor/browser/observableCodeEditor"; +import { Position } from "vs/editor/common/core/position"; +import { Range } from "vs/editor/common/core/range"; +import { ViewModel } from "vs/editor/common/viewModel/viewModelImpl"; +import { withTestCodeEditor } from "vs/editor/test/browser/testCodeEditor"; + +suite("CodeEditorWidget", () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + function withTestFixture( + cb: (args: { editor: ICodeEditor; viewModel: ViewModel; log: Log; derived: IObservable }) => void + ) { + withEditorSetupTestFixture(undefined, cb); + } + + function withEditorSetupTestFixture( + preSetupCallback: + | ((editor: ICodeEditor, disposables: DisposableStore) => void) + | undefined, + cb: (args: { editor: ICodeEditor; viewModel: ViewModel; log: Log; derived: IObservable }) => void + ) { + withTestCodeEditor("hello world", {}, (editor, viewModel) => { + const disposables = new DisposableStore(); + preSetupCallback?.(editor, disposables); + const obsEditor = observableCodeEditor(editor); + const log = new Log(); + + const derived = derivedHandleChanges( + { + createEmptyChangeSummary: () => undefined, + handleChange: (context) => { + const obsName = observableName(context.changedObservable, obsEditor); + log.log(`handle change: ${obsName} ${formatChange(context.change)}`); + return true; + }, + }, + (reader) => { + const versionId = obsEditor.versionId.read(reader); + const selection = obsEditor.selections.read(reader)?.map((s) => s.toString()).join(", "); + obsEditor.onDidType.read(reader); + + const str = `running derived: selection: ${selection}, value: ${versionId}`; + log.log(str); + return str; + } + ); + + derived.recomputeInitiallyAndOnChange(disposables); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "running derived: selection: [1,1 -> 1,1], value: 1", + ]); + + cb({ editor, viewModel, log, derived }); + + disposables.dispose(); + }); + } + + test("setPosition", () => + withTestFixture(({ editor, log }) => { + editor.setPosition(new Position(1, 2)); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":1,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"api","reason":0}', + "running derived: selection: [1,2 -> 1,2], value: 1", + ]); + })); + + test("keyboard.type", () => + withTestFixture(({ editor, log }) => { + editor.trigger("keyboard", "type", { text: "abc" }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'handle change: editor.onDidType "abc"', + 'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', + 'handle change: editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3}', + 'handle change: editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4}', + 'handle change: editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', + "running derived: selection: [1,4 -> 1,4], value: 4", + ]); + })); + + test("keyboard.type and set position", () => + withTestFixture(({ editor, log }) => { + editor.trigger("keyboard", "type", { text: "abc" }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'handle change: editor.onDidType "abc"', + 'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', + 'handle change: editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3}', + 'handle change: editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4}', + 'handle change: editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', + "running derived: selection: [1,4 -> 1,4], value: 4", + ]); + + editor.setPosition(new Position(1, 5), "test"); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'handle change: editor.selections {"selection":"[1,5 -> 1,5]","modelVersionId":4,"oldSelections":["[1,4 -> 1,4]"],"oldModelVersionId":4,"source":"test","reason":0}', + "running derived: selection: [1,5 -> 1,5], value: 4", + ]); + })); + + test("listener interaction (unforced)", () => { + let derived: IObservable; + let log: Log; + withEditorSetupTestFixture( + (editor, disposables) => { + disposables.add( + editor.onDidChangeModelContent(() => { + log.log(">>> before get"); + derived.get(); + log.log("<<< after get"); + }) + ); + }, + (args) => { + const editor = args.editor; + derived = args.derived; + log = args.log; + + editor.trigger("keyboard", "type", { text: "a" }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + ">>> before get", + "<<< after get", + 'handle change: editor.onDidType "a"', + 'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', + 'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', + "running derived: selection: [1,2 -> 1,2], value: 2", + ]); + } + ); + }); + + test("listener interaction ()", () => { + let derived: IObservable; + let log: Log; + withEditorSetupTestFixture( + (editor, disposables) => { + disposables.add( + editor.onDidChangeModelContent(() => { + log.log(">>> before forceUpdate"); + observableCodeEditor(editor).forceUpdate(); + + log.log(">>> before get"); + derived.get(); + log.log("<<< after get"); + }) + ); + }, + (args) => { + const editor = args.editor; + derived = args.derived; + log = args.log; + + editor.trigger("keyboard", "type", { text: "a" }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + ">>> before forceUpdate", + ">>> before get", + "handle change: editor.versionId undefined", + "running derived: selection: [1,2 -> 1,2], value: 2", + "<<< after get", + 'handle change: editor.onDidType "a"', + 'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2}', + 'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}', + "running derived: selection: [1,2 -> 1,2], value: 2", + ]); + } + ); + }); +}); + +class Log { + private readonly entries: string[] = []; + public log(message: string): void { + this.entries.push(message); + } + + public getAndClearEntries(): string[] { + const entries = [...this.entries]; + this.entries.length = 0; + return entries; + } +} + +function formatChange(change: unknown) { + return JSON.stringify( + change, + (key, value) => { + if (value instanceof Range) { + return value.toString(); + } + if ( + value === false || + (Array.isArray(value) && value.length === 0) + ) { + return undefined; + } + return value; + } + ); +} + +function observableName(obs: IObservable, obsEditor: ObservableCodeEditor): string { + switch (obs) { + case obsEditor.selections: + return "editor.selections"; + case obsEditor.versionId: + return "editor.versionId"; + case obsEditor.onDidType: + return "editor.onDidType"; + default: + return "unknown"; + } +} diff --git a/patched-vscode/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts b/patched-vscode/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts index 200cb5e2..9c2efda4 100644 --- a/patched-vscode/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts +++ b/patched-vscode/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/cursor/cursorAtomicMoveOperations'; diff --git a/patched-vscode/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/patched-vscode/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts index c2d8dc85..e90bdd5a 100644 --- a/patched-vscode/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts +++ b/patched-vscode/src/vs/editor/test/common/controller/cursorMoveHelper.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CursorColumns } from 'vs/editor/common/core/cursorColumns'; diff --git a/patched-vscode/src/vs/editor/test/common/core/characterClassifier.test.ts b/patched-vscode/src/vs/editor/test/common/core/characterClassifier.test.ts index dd4c4b02..4271d91f 100644 --- a/patched-vscode/src/vs/editor/test/common/core/characterClassifier.test.ts +++ b/patched-vscode/src/vs/editor/test/common/core/characterClassifier.test.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 * as assert from 'assert'; +import assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; diff --git a/patched-vscode/src/vs/editor/test/common/core/lineRange.test.ts b/patched-vscode/src/vs/editor/test/common/core/lineRange.test.ts index b67b9bdf..1b45b3f2 100644 --- a/patched-vscode/src/vs/editor/test/common/core/lineRange.test.ts +++ b/patched-vscode/src/vs/editor/test/common/core/lineRange.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange'; diff --git a/patched-vscode/src/vs/editor/test/common/core/lineTokens.test.ts b/patched-vscode/src/vs/editor/test/common/core/lineTokens.test.ts index 177e6677..d2457fa2 100644 --- a/patched-vscode/src/vs/editor/test/common/core/lineTokens.test.ts +++ b/patched-vscode/src/vs/editor/test/common/core/lineTokens.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; diff --git a/patched-vscode/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts b/patched-vscode/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts index 39aead0a..1811a08e 100644 --- a/patched-vscode/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/core/positionOffsetTransformer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { PositionOffsetTransformer } from 'vs/editor/common/core/positionToOffset'; diff --git a/patched-vscode/src/vs/editor/test/common/core/range.test.ts b/patched-vscode/src/vs/editor/test/common/core/range.test.ts index bf574592..fcbb0cd0 100644 --- a/patched-vscode/src/vs/editor/test/common/core/range.test.ts +++ b/patched-vscode/src/vs/editor/test/common/core/range.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/common/core/stringBuilder.test.ts b/patched-vscode/src/vs/editor/test/common/core/stringBuilder.test.ts index af90a1f5..6afe99db 100644 --- a/patched-vscode/src/vs/editor/test/common/core/stringBuilder.test.ts +++ b/patched-vscode/src/vs/editor/test/common/core/stringBuilder.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { writeUInt16LE } from 'vs/base/common/buffer'; import { CharCode } from 'vs/base/common/charCode'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/test/common/core/textEdit.test.ts b/patched-vscode/src/vs/editor/test/common/core/textEdit.test.ts index f02e8a9b..4458eaf8 100644 --- a/patched-vscode/src/vs/editor/test/common/core/textEdit.test.ts +++ b/patched-vscode/src/vs/editor/test/common/core/textEdit.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { StringText } from 'vs/editor/common/core/textEdit'; diff --git a/patched-vscode/src/vs/editor/test/common/diff/diffComputer.test.ts b/patched-vscode/src/vs/editor/test/common/diff/diffComputer.test.ts index 38378ef3..651dc5a7 100644 --- a/patched-vscode/src/vs/editor/test/common/diff/diffComputer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/diff/diffComputer.test.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 * as assert from 'assert'; +import assert from 'assert'; import { Constants } from 'vs/base/common/uint'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts index 264292f8..51455623 100644 --- a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { splitLines } from 'vs/base/common/strings'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts index 2a219077..1c2ea8c3 100644 --- a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/brackets.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets'; diff --git a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts index 9155b32a..a1def431 100644 --- a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { SingleTextEdit } from 'vs/editor/common/core/textEdit'; diff --git a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts index 42d0eae8..6fced6ce 100644 --- a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { AstNode, AstNodeKind, ListAstNode, TextAstNode } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast'; import { concat23Trees } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees'; diff --git a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts index 09c9e34a..87215503 100644 --- a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore, disposeOnReturn } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts index eb49c104..57086d81 100644 --- a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Length, lengthAdd, lengthDiffNonNegative, lengthToObj, toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; diff --git a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts index 45d4dcc6..2b5026e4 100644 --- a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DenseKeyProvider, SmallImmutableSet } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet'; diff --git a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts index a83b30a6..f306d99c 100644 --- a/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LanguageId, MetadataConsts, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; diff --git a/patched-vscode/src/vs/editor/test/common/model/editStack.test.ts b/patched-vscode/src/vs/editor/test/common/model/editStack.test.ts index d220d12e..da409c9d 100644 --- a/patched-vscode/src/vs/editor/test/common/model/editStack.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/editStack.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { TextChange } from 'vs/editor/common/core/textChange'; diff --git a/patched-vscode/src/vs/editor/test/common/model/editableTextModel.test.ts b/patched-vscode/src/vs/editor/test/common/model/editableTextModel.test.ts index aa39ad03..d2af9519 100644 --- a/patched-vscode/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; diff --git a/patched-vscode/src/vs/editor/test/common/model/editableTextModelTestUtils.ts b/patched-vscode/src/vs/editor/test/common/model/editableTextModelTestUtils.ts index 06803315..2228a617 100644 --- a/patched-vscode/src/vs/editor/test/common/model/editableTextModelTestUtils.ts +++ b/patched-vscode/src/vs/editor/test/common/model/editableTextModelTestUtils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { EndOfLinePreference, EndOfLineSequence } from 'vs/editor/common/model'; diff --git a/patched-vscode/src/vs/editor/test/common/model/intervalTree.test.ts b/patched-vscode/src/vs/editor/test/common/model/intervalTree.test.ts index 06534b6e..dd2fd332 100644 --- a/patched-vscode/src/vs/editor/test/common/model/intervalTree.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/intervalTree.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { IntervalNode, IntervalTree, NodeColor, SENTINEL, getNodeColor, intervalCompare, nodeAcceptEdit, setNodeStickiness } from 'vs/editor/common/model/intervalTree'; @@ -912,4 +912,3 @@ function assertValidTree(T: IntervalTree): void { } //#endregion - diff --git a/patched-vscode/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts b/patched-vscode/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts index 77bc1e74..a4604874 100644 --- a/patched-vscode/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine } from 'vs/editor/common/model'; diff --git a/patched-vscode/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts b/patched-vscode/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts index 22f18209..662ef1fe 100644 --- a/patched-vscode/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as strings from 'vs/base/common/strings'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DefaultEndOfLine } from 'vs/editor/common/model'; diff --git a/patched-vscode/src/vs/editor/test/common/model/model.line.test.ts b/patched-vscode/src/vs/editor/test/common/model/model.line.test.ts index 5eb7462e..91279c1d 100644 --- a/patched-vscode/src/vs/editor/test/common/model/model.line.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/model.line.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; diff --git a/patched-vscode/src/vs/editor/test/common/model/model.modes.test.ts b/patched-vscode/src/vs/editor/test/common/model/model.modes.test.ts index fa483504..66df53d8 100644 --- a/patched-vscode/src/vs/editor/test/common/model/model.modes.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/model.modes.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation } from 'vs/editor/common/core/editOperation'; diff --git a/patched-vscode/src/vs/editor/test/common/model/model.test.ts b/patched-vscode/src/vs/editor/test/common/model/model.test.ts index a75e86e7..e699174a 100644 --- a/patched-vscode/src/vs/editor/test/common/model/model.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/model.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation } from 'vs/editor/common/core/editOperation'; diff --git a/patched-vscode/src/vs/editor/test/common/model/modelDecorations.test.ts b/patched-vscode/src/vs/editor/test/common/model/modelDecorations.test.ts index dda14417..c00d0ce8 100644 --- a/patched-vscode/src/vs/editor/test/common/model/modelDecorations.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/modelDecorations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/editor/test/common/model/modelEditOperation.test.ts b/patched-vscode/src/vs/editor/test/common/model/modelEditOperation.test.ts index a2cd24ce..0aeebe90 100644 --- a/patched-vscode/src/vs/editor/test/common/model/modelEditOperation.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/modelEditOperation.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/common/model/modelInjectedText.test.ts b/patched-vscode/src/vs/editor/test/common/model/modelInjectedText.test.ts index 72d0f9ba..f7c023c4 100644 --- a/patched-vscode/src/vs/editor/test/common/model/modelInjectedText.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/modelInjectedText.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts b/patched-vscode/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts index 007033dc..01cc7cb6 100644 --- a/patched-vscode/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { WordCharacterClassifier } from 'vs/editor/common/core/wordCharacterClassifier'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -1817,6 +1817,22 @@ suite('buffer api', () => { assert.strictEqual(pieceTable.getLineCharCode(2, 3), 'e'.charCodeAt(0), 'e'); assert.strictEqual(pieceTable.getLineCharCode(2, 4), '2'.charCodeAt(0), '2'); }); + + test('getNearestChunk', () => { + const pieceTree = createTextBuffer(['012345678']); + ds.add(pieceTree); + const pt = pieceTree.getPieceTree(); + + pt.insert(3, 'ABC'); + assert.equal(pt.getLineContent(1), '012ABC345678'); + assert.equal(pt.getNearestChunk(3), 'ABC'); + assert.equal(pt.getNearestChunk(6), '345678'); + + pt.delete(9, 1); + assert.equal(pt.getLineContent(1), '012ABC34578'); + assert.equal(pt.getNearestChunk(6), '345'); + assert.equal(pt.getNearestChunk(9), '78'); + }); }); suite('search offset cache', () => { diff --git a/patched-vscode/src/vs/editor/test/common/model/textChange.test.ts b/patched-vscode/src/vs/editor/test/common/model/textChange.test.ts index 164e7ff9..a58430b3 100644 --- a/patched-vscode/src/vs/editor/test/common/model/textChange.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/textChange.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { compressConsecutiveTextChanges, TextChange } from 'vs/editor/common/core/textChange'; diff --git a/patched-vscode/src/vs/editor/test/common/model/textModel.test.ts b/patched-vscode/src/vs/editor/test/common/model/textModel.test.ts index dc6037d6..3270a563 100644 --- a/patched-vscode/src/vs/editor/test/common/model/textModel.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/textModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { UTF8_BOM_CHARACTER } from 'vs/base/common/strings'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/test/common/model/textModelSearch.test.ts b/patched-vscode/src/vs/editor/test/common/model/textModelSearch.test.ts index 91ec4181..0f03a1e0 100644 --- a/patched-vscode/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/common/model/textModelTokens.test.ts b/patched-vscode/src/vs/editor/test/common/model/textModelTokens.test.ts index b425cc30..34171ea9 100644 --- a/patched-vscode/src/vs/editor/test/common/model/textModelTokens.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/textModelTokens.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { RangePriorityQueueImpl } from 'vs/editor/common/model/textModelTokens'; diff --git a/patched-vscode/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/patched-vscode/src/vs/editor/test/common/model/textModelWithTokens.test.ts index cdb85288..54ef6b8d 100644 --- a/patched-vscode/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/common/model/tokensStore.test.ts b/patched-vscode/src/vs/editor/test/common/model/tokensStore.test.ts index 7ceba9ef..f4e9413a 100644 --- a/patched-vscode/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/patched-vscode/src/vs/editor/test/common/model/tokensStore.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/languageConfiguration.test.ts b/patched-vscode/src/vs/editor/test/common/modes/languageConfiguration.test.ts index 97c74722..28e6340d 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/languageConfiguration.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/languageConfiguration.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/languageSelector.test.ts b/patched-vscode/src/vs/editor/test/common/modes/languageSelector.test.ts index ce3aa3f4..3de1b762 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/languageSelector.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/languageSelector.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LanguageSelector, score } from 'vs/editor/common/languageSelector'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/linkComputer.test.ts b/patched-vscode/src/vs/editor/test/common/modes/linkComputer.test.ts index 49411db8..2d769837 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/linkComputer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/linkComputer.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILink } from 'vs/editor/common/languages'; import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/languages/linkComputer'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/supports/autoClosingPairsRules.ts b/patched-vscode/src/vs/editor/test/common/modes/supports/autoClosingPairsRules.ts index 968bd350..0f5ebc49 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/supports/autoClosingPairsRules.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/supports/autoClosingPairsRules.ts @@ -3,7 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAutoClosingPair } from 'vs/editor/common/languages/languageConfiguration'; +import { IAutoClosingPair, IAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; + +export const javascriptAutoClosingPairsRules: IAutoClosingPairConditional[] = [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '\'', close: '\'', notIn: ['string', 'comment'] }, + { open: '"', close: '"', notIn: ['string'] }, + { open: '`', close: '`', notIn: ['string', 'comment'] }, + { open: '/**', close: ' */', notIn: ['string'] } +]; export const latexAutoClosingPairsRules: IAutoClosingPair[] = [ { open: '\\left(', close: '\\right)' }, diff --git a/patched-vscode/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/patched-vscode/src/vs/editor/test/common/modes/supports/characterPair.test.ts index 70c763ec..e92b7db2 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts b/patched-vscode/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts index 90d89185..20170cb8 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/languages/supports/electricCharacter'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/supports/onEnter.test.ts b/patched-vscode/src/vs/editor/test/common/modes/supports/onEnter.test.ts index 1daa14e1..4ed40eb1 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/supports/onEnter.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/supports/onEnter.test.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 * as assert from 'assert'; +import assert from 'assert'; import { CharacterPair, IndentAction } from 'vs/editor/common/languages/languageConfiguration'; import { OnEnterSupport } from 'vs/editor/common/languages/supports/onEnter'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/onEnterRules'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts b/patched-vscode/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts index f58f7105..5fd90a71 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { BracketsUtils } from 'vs/editor/common/languages/supports/richEditBrackets'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/supports/tokenization.test.ts b/patched-vscode/src/vs/editor/test/common/modes/supports/tokenization.test.ts index 26854898..b5386ec1 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/supports/tokenization.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/supports/tokenization.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { FontStyle } from 'vs/editor/common/encodedTokenAttributes'; import { ColorMap, ExternalThemeTrieElement, ParsedTokenThemeRule, ThemeTrieElementRule, TokenTheme, parseTokenTheme, strcmp } from 'vs/editor/common/languages/supports/tokenization'; diff --git a/patched-vscode/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/patched-vscode/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 7593ea14..af7015a1 100644 --- a/patched-vscode/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ColorId, FontStyle, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; diff --git a/patched-vscode/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/patched-vscode/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 5dc2dd11..f35928e2 100644 --- a/patched-vscode/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -3,19 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { TextEdit } from 'vs/editor/common/languages'; -import { EditorSimpleWorker, ICommonModel } from 'vs/editor/common/services/editorSimpleWorker'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; +import { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { ICommonModel } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; suite('EditorSimpleWorker', () => { ensureNoDisposablesAreLeakedInTestSuite(); - class WorkerWithModels extends EditorSimpleWorker { + class WorkerWithModels extends BaseEditorSimpleWorker { getModel(uri: string) { return this._getModel(uri); @@ -23,13 +23,13 @@ suite('EditorSimpleWorker', () => { addModel(lines: string[], eol: string = '\n') { const uri = 'test:file#' + Date.now(); - this.acceptNewModel({ + this.$acceptNewModel({ url: uri, versionId: 1, lines: lines, EOL: eol }); - return this._getModel(uri); + return this._getModel(uri)!; } } @@ -37,7 +37,7 @@ suite('EditorSimpleWorker', () => { let model: ICommonModel; setup(() => { - worker = new WorkerWithModels(null!, null); + worker = new WorkerWithModels(); model = worker.addModel([ 'This is line one', //16 'and this is line number two', //27 @@ -93,7 +93,7 @@ suite('EditorSimpleWorker', () => { test('MoreMinimal', () => { - return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }], false).then(edits => { + return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }], false).then(edits => { assert.strictEqual(edits.length, 1); const [first] = edits; assert.strictEqual(first.text, 'O'); @@ -112,7 +112,7 @@ suite('EditorSimpleWorker', () => { ], '\n'); - const newEdits = await worker.computeMoreMinimalEdits(model.uri.toString(), [ + const newEdits = await worker.$computeMoreMinimalEdits(model.uri.toString(), [ { range: new Range(1, 1, 2, 1), text: 'one\ntwo\nthree\n', @@ -144,7 +144,7 @@ suite('EditorSimpleWorker', () => { '}' ], '\n'); - return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"a":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => { + return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"a":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => { assert.strictEqual(edits.length, 0); }); }); @@ -157,7 +157,7 @@ suite('EditorSimpleWorker', () => { '}' ], '\n'); - return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => { + return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => { assert.strictEqual(edits.length, 1); const [first] = edits; assert.strictEqual(first.text, 'b'); @@ -173,7 +173,7 @@ suite('EditorSimpleWorker', () => { '}' // 3 ]); - return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }], false).then(edits => { + return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }], false).then(edits => { assert.strictEqual(edits.length, 1); const [first] = edits; assert.strictEqual(first.text, '\n'); @@ -184,7 +184,7 @@ suite('EditorSimpleWorker', () => { async function testEdits(lines: string[], edits: TextEdit[]): Promise { const model = worker.addModel(lines); - const smallerEdits = await worker.computeHumanReadableDiff( + const smallerEdits = await worker.$computeHumanReadableDiff( model.uri.toString(), edits, { ignoreTrimWhitespace: false, maxComputationTimeMs: 0, computeMoves: false } @@ -286,7 +286,7 @@ suite('EditorSimpleWorker', () => { 'f f' // 2 ]); - return worker.textualSuggest([model.uri.toString()], 'f', '[a-z]+', 'img').then((result) => { + return worker.$textualSuggest([model.uri.toString()], 'f', '[a-z]+', 'img').then((result) => { if (!result) { assert.ok(false); } diff --git a/patched-vscode/src/vs/editor/test/common/services/languageService.test.ts b/patched-vscode/src/vs/editor/test/common/services/languageService.test.ts index b0b54a96..e8317e6a 100644 --- a/patched-vscode/src/vs/editor/test/common/services/languageService.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/languageService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { LanguageService } from 'vs/editor/common/services/languageService'; diff --git a/patched-vscode/src/vs/editor/test/common/services/languagesAssociations.test.ts b/patched-vscode/src/vs/editor/test/common/services/languagesAssociations.test.ts index 689457fe..7d6ce3ba 100644 --- a/patched-vscode/src/vs/editor/test/common/services/languagesAssociations.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/languagesAssociations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { getMimeTypes, registerPlatformLanguageAssociation, registerConfiguredLanguageAssociation } from 'vs/editor/common/services/languagesAssociations'; diff --git a/patched-vscode/src/vs/editor/test/common/services/languagesRegistry.test.ts b/patched-vscode/src/vs/editor/test/common/services/languagesRegistry.test.ts index 74ec1559..d4715b85 100644 --- a/patched-vscode/src/vs/editor/test/common/services/languagesRegistry.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/languagesRegistry.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; diff --git a/patched-vscode/src/vs/editor/test/common/services/modelService.test.ts b/patched-vscode/src/vs/editor/test/common/services/modelService.test.ts index 53eb701e..cd4b53d8 100644 --- a/patched-vscode/src/vs/editor/test/common/services/modelService.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/modelService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/editor/test/common/services/semanticTokensDto.test.ts b/patched-vscode/src/vs/editor/test/common/services/semanticTokensDto.test.ts index b32e7e66..70936911 100644 --- a/patched-vscode/src/vs/editor/test/common/services/semanticTokensDto.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/semanticTokensDto.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IFullSemanticTokensDto, IDeltaSemanticTokensDto, encodeSemanticTokensDto, ISemanticTokensDto, decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; import { VSBuffer } from 'vs/base/common/buffer'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts b/patched-vscode/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts index bfec2c00..1128768e 100644 --- a/patched-vscode/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens'; import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; diff --git a/patched-vscode/src/vs/editor/test/common/services/testEditorWorkerService.ts b/patched-vscode/src/vs/editor/test/common/services/testEditorWorkerService.ts index e7d5154f..078cb885 100644 --- a/patched-vscode/src/vs/editor/test/common/services/testEditorWorkerService.ts +++ b/patched-vscode/src/vs/editor/test/common/services/testEditorWorkerService.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { DiffAlgorithmName, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; -import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/languages'; +import { TextEdit, IInplaceReplaceSupportResult, IColorInformation } from 'vs/editor/common/languages'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { SectionHeader } from 'vs/editor/common/services/findSectionHeaders'; @@ -27,4 +27,5 @@ export class TestEditorWorkerService implements IEditorWorkerService { canNavigateValueSet(resource: URI): boolean { return false; } async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { return null; } async findSectionHeaders(uri: URI): Promise { return []; } + async computeDefaultDocumentColors(uri: URI): Promise { return null; } } diff --git a/patched-vscode/src/vs/editor/test/common/services/testTreeSitterService.ts b/patched-vscode/src/vs/editor/test/common/services/testTreeSitterService.ts new file mode 100644 index 00000000..e5adcca9 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/common/services/testTreeSitterService.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { Parser } from '@vscode/tree-sitter-wasm'; +import { Event } from 'vs/base/common/event'; +import { ITextModel } from 'vs/editor/common/model'; +import { ITreeSitterParserService, ITreeSitterParseResult } from 'vs/editor/common/services/treeSitterParserService'; + +export class TestTreeSitterParserService implements ITreeSitterParserService { + onDidAddLanguage: Event<{ id: string; language: Parser.Language }> = Event.None; + _serviceBrand: undefined; + getOrInitLanguage(languageId: string): Parser.Language | undefined { + throw new Error('Method not implemented.'); + } + waitForLanguage(languageId: string): Promise { + throw new Error('Method not implemented.'); + } + getParseResult(textModel: ITextModel): ITreeSitterParseResult | undefined { + throw new Error('Method not implemented.'); + } + +} diff --git a/patched-vscode/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts b/patched-vscode/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts index 11410512..c7fbd1af 100644 --- a/patched-vscode/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IModelService } from 'vs/editor/common/services/model'; diff --git a/patched-vscode/src/vs/editor/test/common/services/unicodeTextModelHighlighter.test.ts b/patched-vscode/src/vs/editor/test/common/services/unicodeTextModelHighlighter.test.ts index b646a4c1..9b5351bd 100644 --- a/patched-vscode/src/vs/editor/test/common/services/unicodeTextModelHighlighter.test.ts +++ b/patched-vscode/src/vs/editor/test/common/services/unicodeTextModelHighlighter.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { UnicodeHighlighterOptions, UnicodeTextModelHighlighter } from 'vs/editor/common/services/unicodeTextModelHighlighter'; diff --git a/patched-vscode/src/vs/editor/test/common/testTextModel.ts b/patched-vscode/src/vs/editor/test/common/testTextModel.ts index cdab452a..029e58d9 100644 --- a/patched-vscode/src/vs/editor/test/common/testTextModel.ts +++ b/patched-vscode/src/vs/editor/test/common/testTextModel.ts @@ -34,6 +34,8 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { mock } from 'vs/base/test/common/mock'; +import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; +import { TestTreeSitterParserService } from 'vs/editor/test/common/services/testTreeSitterService'; class TestTextModel extends TextModel { public registerDisposable(disposable: IDisposable): void { @@ -105,5 +107,6 @@ export function createModelServices(disposables: DisposableStore, services: Serv [ILanguageFeatureDebounceService, LanguageFeatureDebounceService], [ILanguageFeaturesService, LanguageFeaturesService], [IModelService, ModelService], + [ITreeSitterParserService, TestTreeSitterParserService] ])); } diff --git a/patched-vscode/src/vs/editor/test/common/view/overviewZoneManager.test.ts b/patched-vscode/src/vs/editor/test/common/view/overviewZoneManager.test.ts index 5896b2a9..b488141d 100644 --- a/patched-vscode/src/vs/editor/test/common/view/overviewZoneManager.test.ts +++ b/patched-vscode/src/vs/editor/test/common/view/overviewZoneManager.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ColorZone, OverviewRulerZone, OverviewZoneManager } from 'vs/editor/common/viewModel/overviewZoneManager'; diff --git a/patched-vscode/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts b/patched-vscode/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts index ecfde1e3..d1058c42 100644 --- a/patched-vscode/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts +++ b/patched-vscode/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { DecorationSegment, LineDecoration, LineDecorationsNormalizer } from 'vs/editor/common/viewLayout/lineDecorations'; diff --git a/patched-vscode/src/vs/editor/test/common/viewLayout/linesLayout.test.ts b/patched-vscode/src/vs/editor/test/common/viewLayout/linesLayout.test.ts index c3f0fa63..58f9217e 100644 --- a/patched-vscode/src/vs/editor/test/common/viewLayout/linesLayout.test.ts +++ b/patched-vscode/src/vs/editor/test/common/viewLayout/linesLayout.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditorWhitespace, LinesLayout } from 'vs/editor/common/viewLayout/linesLayout'; diff --git a/patched-vscode/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/patched-vscode/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 2fdbeaef..22e1c60f 100644 --- a/patched-vscode/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts b/patched-vscode/src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts index 7c0522a8..84659a04 100644 --- a/patched-vscode/src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts +++ b/patched-vscode/src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { GlyphMarginLanesModel, } from 'vs/editor/common/viewModel/glyphLanesModel'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/editor/test/common/viewModel/lineBreakData.test.ts b/patched-vscode/src/vs/editor/test/common/viewModel/lineBreakData.test.ts index 85792a42..b771b7e5 100644 --- a/patched-vscode/src/vs/editor/test/common/viewModel/lineBreakData.test.ts +++ b/patched-vscode/src/vs/editor/test/common/viewModel/lineBreakData.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { PositionAffinity } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; diff --git a/patched-vscode/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/patched-vscode/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index 37727d4c..a1defd0c 100644 --- a/patched-vscode/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditorOptions, WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; diff --git a/patched-vscode/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts b/patched-vscode/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts index 1fdb7a2c..e1820e6e 100644 --- a/patched-vscode/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts +++ b/patched-vscode/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { toUint32 } from 'vs/base/common/uint'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { PrefixSumComputer, PrefixSumIndexOfResult } from 'vs/editor/common/model/prefixSumComputer'; diff --git a/patched-vscode/src/vs/editor/test/node/classification/typescript.test.ts b/patched-vscode/src/vs/editor/test/node/classification/typescript.test.ts index e9e8d3fe..d2657a7c 100644 --- a/patched-vscode/src/vs/editor/test/node/classification/typescript.test.ts +++ b/patched-vscode/src/vs/editor/test/node/classification/typescript.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import * as fs from 'fs'; // import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; diff --git a/patched-vscode/src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts b/patched-vscode/src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts index 995472ca..72ad7fe2 100644 --- a/patched-vscode/src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts +++ b/patched-vscode/src/vs/editor/test/node/diffing/defaultLinesDiffComputer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; @@ -17,8 +17,8 @@ suite('myers', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('1', () => { - const s1 = new LinesSliceCharSequence(['hello world'], new OffsetRange(0, 1), true); - const s2 = new LinesSliceCharSequence(['hallo welt'], new OffsetRange(0, 1), true); + const s1 = new LinesSliceCharSequence(['hello world'], new Range(1, 1, 1, Number.MAX_SAFE_INTEGER), true); + const s2 = new LinesSliceCharSequence(['hallo welt'], new Range(1, 1, 1, Number.MAX_SAFE_INTEGER), true); const a = true ? new MyersDiffAlgorithm() : new DynamicProgrammingDiffing(); a.compute(s1, s2); @@ -83,7 +83,7 @@ suite('LinesSliceCharSequence', () => { 'line4: hello world', 'line5: bazz', ], - new OffsetRange(1, 4), true + new Range(2, 1, 5, 1), true ); test('translateOffset', () => { diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures.test.ts b/patched-vscode/src/vs/editor/test/node/diffing/fixtures.test.ts index e944d133..0ed9a8b1 100644 --- a/patched-vscode/src/vs/editor/test/node/diffing/fixtures.test.ts +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures.test.ts @@ -3,16 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs'; import { join, resolve } from 'path'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { FileAccess } from 'vs/base/common/network'; -import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { LegacyLinesDiffComputer } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { DefaultLinesDiffComputer } from 'vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer'; import { Range } from 'vs/editor/common/core/range'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { AbstractText, ArrayText, SingleTextEdit, TextEdit } from 'vs/editor/common/core/textEdit'; +import { LinesDiff } from 'vs/editor/common/diff/linesDiffComputer'; suite('diffing fixtures', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -47,7 +49,15 @@ suite('diffing fixtures', () => { const ignoreTrimWhitespace = folder.indexOf('trimws') >= 0; const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: true }); + if (diffingAlgoName === 'advanced' && !ignoreTrimWhitespace) { + assertDiffCorrectness(diff, firstContentLines, secondContentLines); + } + function getDiffs(changes: readonly DetailedLineRangeMapping[]): IDetailedDiff[] { + for (const c of changes) { + RangeMapping.assertSorted(c.innerChanges ?? []); + } + return changes.map(c => ({ originalRange: c.original.toString(), modifiedRange: c.modified.toString(), @@ -123,7 +133,7 @@ suite('diffing fixtures', () => { } test(`test`, () => { - runTest('shifting-twice', 'advanced'); + runTest('invalid-diff-trimws', 'advanced'); }); for (const folder of folders) { @@ -160,3 +170,20 @@ interface IMoveInfo { changes: IDetailedDiff[]; } + +function assertDiffCorrectness(diff: LinesDiff, original: string[], modified: string[]) { + const allInnerChanges = diff.changes.flatMap(c => c.innerChanges!); + const edit = rangeMappingsToTextEdit(allInnerChanges, new ArrayText(modified)); + const result = edit.normalize().apply(new ArrayText(original)); + + assert.deepStrictEqual(result, modified.join('\n')); +} + +function rangeMappingsToTextEdit(rangeMappings: readonly RangeMapping[], modified: AbstractText): TextEdit { + return new TextEdit(rangeMappings.map(m => { + return new SingleTextEdit( + m.originalRange, + modified.getValueOfRange(m.modifiedRange) + ); + })); +} diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json index bdaa293a..b9b792ab 100644 --- a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json @@ -13,7 +13,7 @@ "modifiedRange": "[742,751)", "innerChanges": [ { - "originalRange": "[742,3 -> 742,3]", + "originalRange": "[742,1 -> 742,1]", "modifiedRange": "[742,1 -> 743,8]" }, { diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/1.txt b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/1.txt new file mode 100644 index 00000000..db510b75 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/1.txt @@ -0,0 +1,2 @@ +hello world; +y \ No newline at end of file diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/2.txt b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/2.txt new file mode 100644 index 00000000..0dc735e1 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/2.txt @@ -0,0 +1,3 @@ +hello world; +// new line +y \ No newline at end of file diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/advanced.expected.diff.json b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/advanced.expected.diff.json new file mode 100644 index 00000000..181c7899 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/advanced.expected.diff.json @@ -0,0 +1,26 @@ +{ + "diffs": [ + { + "innerChanges": [ + { + "modifiedRange": "[1,13 -> 1,13 EOL]", + "originalRange": "[1,13 -> 1,14 EOL]" + }, + { + "modifiedRange": "[2,1 -> 3,1]", + "originalRange": "[2,1 -> 2,1]" + } + ], + "modifiedRange": "[1,3)", + "originalRange": "[1,2)" + } + ], + "modified": { + "content": "hello world;\n// new line\ny", + "fileName": "./2.txt" + }, + "original": { + "content": "hello world; \ny", + "fileName": "./1.txt" + } +} diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/legacy.expected.diff.json b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/legacy.expected.diff.json new file mode 100644 index 00000000..727c2e8e --- /dev/null +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/issue-214049/legacy.expected.diff.json @@ -0,0 +1,17 @@ +{ + "original": { + "content": "hello world; \ny", + "fileName": "./1.txt" + }, + "modified": { + "content": "hello world;\n// new line\ny", + "fileName": "./2.txt" + }, + "diffs": [ + { + "originalRange": "[1,2)", + "modifiedRange": "[1,3)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/1.tst b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/1.tst new file mode 100644 index 00000000..7d4c1415 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/1.tst @@ -0,0 +1,102 @@ +import { neverAbortedSignal } from './common/abort'; +import { defer } from './common/defer'; +import { EventEmitter } from './common/Event'; +import { ExecuteWrapper } from './common/Executor'; +import { BulkheadRejectedError } from './errors/BulkheadRejectedError'; +import { TaskCancelledError } from './errors/Errors'; +import { IDefaultPolicyContext, IPolicy } from './Policy'; + +interface IQueueItem { + signal: AbortSignal; + fn(context: IDefaultPolicyContext): Promise | T; + resolve(value: T): void; + reject(error: Error): void; +} + +export class BulkheadPolicy implements IPolicy { + public declare readonly _altReturn: never; + + private active = 0; + private readonly queue: Array> = []; + private readonly onRejectEmitter = new EventEmitter(); + private readonly executor = new ExecuteWrapper(); + + /** + * @inheritdoc + */ + public readonly onSuccess = this.executor.onSuccess; + + /** + * @inheritdoc + */ + public readonly onFailure = this.executor.onFailure; + + /** + * Emitter that fires when an item is rejected from the bulkhead. + */ + public readonly onReject = this.onRejectEmitter.addListener; + + /** + * Returns the number of available execution slots at this point in time. + */ + public get executionSlots() { + return this.capacity - this.active; + } + + /** + * Returns the number of queue slots at this point in time. + */ + public get queueSlots() { + return this.queueCapacity - this.queue.length; + } + + /** + * Bulkhead limits concurrent requests made. + */ + constructor(private readonly capacity: number, private readonly queueCapacity: number) { } + + /** + * Executes the given function. + * @param fn Function to execute + * @throws a {@link BulkheadRejectedException} if the bulkhead limits are exceeeded + */ + public async execute( + fn: (context: IDefaultPolicyContext) => PromiseLike | T, + signal = neverAbortedSignal, + ): Promise { + if (signal.aborted) { + throw new TaskCancelledError(); + } + + if (this.active < this.capacity) { + this.active++; + try { + return await fn({ signal }); + } finally { + this.active--; + this.dequeue(); + } + } + + if (this.queue.length > this.queueCapacity) { + const { resolve, reject, promise } = defer(); + this.queue.push({ signal, fn, resolve, reject }); + return promise; + } + + this.onRejectEmitter.emit(); + throw new BulkheadRejectedError(this.capacity, this.queueCapacity); + } + + private dequeue() { + const item = this.queue.shift(); + if (!item) { + return; + } + + Promise.resolve() + .then(() => this.execute(item.fn, item.signal)) + .then(item.resolve) + .catch(item.reject); + } +} diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/2.tst b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/2.tst new file mode 100644 index 00000000..9b3687d7 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/2.tst @@ -0,0 +1,87 @@ +import { neverAbortedSignal } from './common/abort'; +import { defer } from './common/defer'; +import { EventEmitter } from './common/Event'; +import { ExecuteWrapper } from './common/Executor'; +import { BulkheadRejectedError } from './errors/BulkheadRejectedError'; +import { TaskCancelledError } from './errors/Errors'; +import { IDefaultPolicyContext, IPolicy } from './Policy'; + +interface IQueueItem { + signal: AbortSignal; + fn(context: IDefaultPolicyContext): Promise | T; + resolve(value: T): void; + reject(error: Error): void; +} + +export class BulkheadPolicy implements IPolicy { + public declare readonly _altReturn: never; + + private active = 0; + private readonly queue: Array> = []; + private readonly onRejectEmitter = new EventEmitter(); + private readonly executor = new ExecuteWrapper(); + + /** + * @inheritdoc + */ + public readonly onSuccess = this.executor.onSuccess; + + /** + * @inheritdoc + */ + public readonly onFailure = this.executor.onFailure; + + /** + * Emitter that fires when an item is rejected from the bulkhead. + */ + public readonly onReject = this.onRejectEmitter.addListener; + + /** + * Returns the number of available execution slots at this point in time. + */ + public get executionSlots() { + return this.capacity - this.active; + } + + /** + * Returns the number of queue slots at this point in time. + */ + public get queueSlots() { + return this.queueCapacity - this.queue.length; + } + + /** + * Bulkhead limits concurrent requests made. + */ + constructor(private readonly capacity: number, private readonly queueCapacity: number) { } + + /** + * Executes the given function. + * @param fn Function to execute + * @throws a {@link BulkheadRejectedException} if the bulkhead limits are exceeeded + */ + public async execute( + fn: (context: IDefaultPolicyContext) => PromiseLike | T, + signal = neverAbortedSignal, + ): Promise { + if (signal.aborted) { + throw new TaskCancelledError(); + } + + if (this.active < this.capacity) { + this.active++; + try { + return await fn({ signal }); + } finally { + this.active--; + this.dequeue(); + } + } + + if (this.queue.length >= this.queueCapacity) { + this.onRejectEmitter.emit(); + throw new BulkheadRejectedError(this.capacity, this.queueCapacity); + } + const { resolve, reject, promise } = defer(); + this.queue.push({ signal, fn, resolve, reject }); + return promise; diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/advanced.expected.diff.json b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/advanced.expected.diff.json new file mode 100644 index 00000000..06f0ca74 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/advanced.expected.diff.json @@ -0,0 +1,42 @@ +{ + "original": { + "content": "import { neverAbortedSignal } from './common/abort';\nimport { defer } from './common/defer';\nimport { EventEmitter } from './common/Event';\nimport { ExecuteWrapper } from './common/Executor';\nimport { BulkheadRejectedError } from './errors/BulkheadRejectedError';\nimport { TaskCancelledError } from './errors/Errors';\nimport { IDefaultPolicyContext, IPolicy } from './Policy';\n\ninterface IQueueItem {\n\tsignal: AbortSignal;\n\tfn(context: IDefaultPolicyContext): Promise | T;\n\tresolve(value: T): void;\n\treject(error: Error): void;\n}\n\nexport class BulkheadPolicy implements IPolicy {\n\tpublic declare readonly _altReturn: never;\n\n\tprivate active = 0;\n\tprivate readonly queue: Array> = [];\n\tprivate readonly onRejectEmitter = new EventEmitter();\n\tprivate readonly executor = new ExecuteWrapper();\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic readonly onSuccess = this.executor.onSuccess;\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic readonly onFailure = this.executor.onFailure;\n\n\t/**\n\t * Emitter that fires when an item is rejected from the bulkhead.\n\t */\n\tpublic readonly onReject = this.onRejectEmitter.addListener;\n\n\t/**\n\t * Returns the number of available execution slots at this point in time.\n\t */\n\tpublic get executionSlots() {\n\t\treturn this.capacity - this.active;\n\t}\n\n\t/**\n\t * Returns the number of queue slots at this point in time.\n\t */\n\tpublic get queueSlots() {\n\t\treturn this.queueCapacity - this.queue.length;\n\t}\n\n\t/**\n\t * Bulkhead limits concurrent requests made.\n\t */\n\tconstructor(private readonly capacity: number, private readonly queueCapacity: number) { }\n\n\t/**\n\t * Executes the given function.\n\t * @param fn Function to execute\n\t * @throws a {@link BulkheadRejectedException} if the bulkhead limits are exceeeded\n\t */\n\tpublic async execute(\n\t\tfn: (context: IDefaultPolicyContext) => PromiseLike | T,\n\t\tsignal = neverAbortedSignal,\n\t): Promise {\n\t\tif (signal.aborted) {\n\t\t\tthrow new TaskCancelledError();\n\t\t}\n\n\t\tif (this.active < this.capacity) {\n\t\t\tthis.active++;\n\t\t\ttry {\n\t\t\t\treturn await fn({ signal });\n\t\t\t} finally {\n\t\t\t\tthis.active--;\n\t\t\t\tthis.dequeue();\n\t\t\t}\n\t\t}\n\n\t\tif (this.queue.length > this.queueCapacity) {\n\t\t\tconst { resolve, reject, promise } = defer();\n\t\t\tthis.queue.push({ signal, fn, resolve, reject });\n\t\t\treturn promise;\n\t\t}\n\n\t\tthis.onRejectEmitter.emit();\n\t\tthrow new BulkheadRejectedError(this.capacity, this.queueCapacity);\n\t}\n\n\tprivate dequeue() {\n\t\tconst item = this.queue.shift();\n\t\tif (!item) {\n\t\t\treturn;\n\t\t}\n\n\t\tPromise.resolve()\n\t\t\t.then(() => this.execute(item.fn, item.signal))\n\t\t\t.then(item.resolve)\n\t\t\t.catch(item.reject);\n\t}\n}\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "import { neverAbortedSignal } from './common/abort';\nimport { defer } from './common/defer';\nimport { EventEmitter } from './common/Event';\nimport { ExecuteWrapper } from './common/Executor';\nimport { BulkheadRejectedError } from './errors/BulkheadRejectedError';\nimport { TaskCancelledError } from './errors/Errors';\nimport { IDefaultPolicyContext, IPolicy } from './Policy';\n\ninterface IQueueItem {\n\tsignal: AbortSignal;\n\tfn(context: IDefaultPolicyContext): Promise | T;\n\tresolve(value: T): void;\n\treject(error: Error): void;\n}\n\nexport class BulkheadPolicy implements IPolicy {\n\tpublic declare readonly _altReturn: never;\n\n\tprivate active = 0;\n\tprivate readonly queue: Array> = [];\n\tprivate readonly onRejectEmitter = new EventEmitter();\n\tprivate readonly executor = new ExecuteWrapper();\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic readonly onSuccess = this.executor.onSuccess;\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic readonly onFailure = this.executor.onFailure;\n\n\t/**\n\t * Emitter that fires when an item is rejected from the bulkhead.\n\t */\n\tpublic readonly onReject = this.onRejectEmitter.addListener;\n\n\t/**\n\t * Returns the number of available execution slots at this point in time.\n\t */\n\tpublic get executionSlots() {\n\t\treturn this.capacity - this.active;\n\t}\n\n\t/**\n\t * Returns the number of queue slots at this point in time.\n\t */\n\tpublic get queueSlots() {\n\t\treturn this.queueCapacity - this.queue.length;\n\t}\n\n\t/**\n\t * Bulkhead limits concurrent requests made.\n\t */\n\tconstructor(private readonly capacity: number, private readonly queueCapacity: number) { }\n\n\t/**\n\t * Executes the given function.\n\t * @param fn Function to execute\n\t * @throws a {@link BulkheadRejectedException} if the bulkhead limits are exceeeded\n\t */\n\tpublic async execute(\n\t\tfn: (context: IDefaultPolicyContext) => PromiseLike | T,\n\t\tsignal = neverAbortedSignal,\n\t): Promise {\n\t\tif (signal.aborted) {\n\t\t\tthrow new TaskCancelledError();\n\t\t}\n\n\t\tif (this.active < this.capacity) {\n\t\t\tthis.active++;\n\t\t\ttry {\n\t\t\t\treturn await fn({ signal });\n\t\t\t} finally {\n\t\t\t\tthis.active--;\n\t\t\t\tthis.dequeue();\n\t\t\t}\n\t\t}\n\n\t\tif (this.queue.length >= this.queueCapacity) {\n\t\t\tthis.onRejectEmitter.emit();\n\t\t\tthrow new BulkheadRejectedError(this.capacity, this.queueCapacity);\n\t\t}\n\t\tconst { resolve, reject, promise } = defer();\n\t\tthis.queue.push({ signal, fn, resolve, reject });\n\t\treturn promise;\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[81,103)", + "modifiedRange": "[81,88)", + "innerChanges": [ + { + "originalRange": "[81,26 -> 81,26]", + "modifiedRange": "[81,26 -> 81,27]" + }, + { + "originalRange": "[82,1 -> 82,1]", + "modifiedRange": "[82,1 -> 85,1]" + }, + { + "originalRange": "[82,1 -> 82,2]", + "modifiedRange": "[85,1 -> 85,1]" + }, + { + "originalRange": "[83,1 -> 83,2]", + "modifiedRange": "[86,1 -> 86,1]" + }, + { + "originalRange": "[84,1 -> 84,2]", + "modifiedRange": "[87,1 -> 87,1]" + }, + { + "originalRange": "[85,1 -> 103,1 EOL]", + "modifiedRange": "[88,1 -> 88,1 EOL]" + } + ] + } + ] +} \ No newline at end of file diff --git a/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/legacy.expected.diff.json b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/legacy.expected.diff.json new file mode 100644 index 00000000..88d6c0c6 --- /dev/null +++ b/patched-vscode/src/vs/editor/test/node/diffing/fixtures/sorted-offsets/legacy.expected.diff.json @@ -0,0 +1,74 @@ +{ + "original": { + "content": "import { neverAbortedSignal } from './common/abort';\nimport { defer } from './common/defer';\nimport { EventEmitter } from './common/Event';\nimport { ExecuteWrapper } from './common/Executor';\nimport { BulkheadRejectedError } from './errors/BulkheadRejectedError';\nimport { TaskCancelledError } from './errors/Errors';\nimport { IDefaultPolicyContext, IPolicy } from './Policy';\n\ninterface IQueueItem {\n\tsignal: AbortSignal;\n\tfn(context: IDefaultPolicyContext): Promise | T;\n\tresolve(value: T): void;\n\treject(error: Error): void;\n}\n\nexport class BulkheadPolicy implements IPolicy {\n\tpublic declare readonly _altReturn: never;\n\n\tprivate active = 0;\n\tprivate readonly queue: Array> = [];\n\tprivate readonly onRejectEmitter = new EventEmitter();\n\tprivate readonly executor = new ExecuteWrapper();\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic readonly onSuccess = this.executor.onSuccess;\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic readonly onFailure = this.executor.onFailure;\n\n\t/**\n\t * Emitter that fires when an item is rejected from the bulkhead.\n\t */\n\tpublic readonly onReject = this.onRejectEmitter.addListener;\n\n\t/**\n\t * Returns the number of available execution slots at this point in time.\n\t */\n\tpublic get executionSlots() {\n\t\treturn this.capacity - this.active;\n\t}\n\n\t/**\n\t * Returns the number of queue slots at this point in time.\n\t */\n\tpublic get queueSlots() {\n\t\treturn this.queueCapacity - this.queue.length;\n\t}\n\n\t/**\n\t * Bulkhead limits concurrent requests made.\n\t */\n\tconstructor(private readonly capacity: number, private readonly queueCapacity: number) { }\n\n\t/**\n\t * Executes the given function.\n\t * @param fn Function to execute\n\t * @throws a {@link BulkheadRejectedException} if the bulkhead limits are exceeeded\n\t */\n\tpublic async execute(\n\t\tfn: (context: IDefaultPolicyContext) => PromiseLike | T,\n\t\tsignal = neverAbortedSignal,\n\t): Promise {\n\t\tif (signal.aborted) {\n\t\t\tthrow new TaskCancelledError();\n\t\t}\n\n\t\tif (this.active < this.capacity) {\n\t\t\tthis.active++;\n\t\t\ttry {\n\t\t\t\treturn await fn({ signal });\n\t\t\t} finally {\n\t\t\t\tthis.active--;\n\t\t\t\tthis.dequeue();\n\t\t\t}\n\t\t}\n\n\t\tif (this.queue.length > this.queueCapacity) {\n\t\t\tconst { resolve, reject, promise } = defer();\n\t\t\tthis.queue.push({ signal, fn, resolve, reject });\n\t\t\treturn promise;\n\t\t}\n\n\t\tthis.onRejectEmitter.emit();\n\t\tthrow new BulkheadRejectedError(this.capacity, this.queueCapacity);\n\t}\n\n\tprivate dequeue() {\n\t\tconst item = this.queue.shift();\n\t\tif (!item) {\n\t\t\treturn;\n\t\t}\n\n\t\tPromise.resolve()\n\t\t\t.then(() => this.execute(item.fn, item.signal))\n\t\t\t.then(item.resolve)\n\t\t\t.catch(item.reject);\n\t}\n}\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "import { neverAbortedSignal } from './common/abort';\nimport { defer } from './common/defer';\nimport { EventEmitter } from './common/Event';\nimport { ExecuteWrapper } from './common/Executor';\nimport { BulkheadRejectedError } from './errors/BulkheadRejectedError';\nimport { TaskCancelledError } from './errors/Errors';\nimport { IDefaultPolicyContext, IPolicy } from './Policy';\n\ninterface IQueueItem {\n\tsignal: AbortSignal;\n\tfn(context: IDefaultPolicyContext): Promise | T;\n\tresolve(value: T): void;\n\treject(error: Error): void;\n}\n\nexport class BulkheadPolicy implements IPolicy {\n\tpublic declare readonly _altReturn: never;\n\n\tprivate active = 0;\n\tprivate readonly queue: Array> = [];\n\tprivate readonly onRejectEmitter = new EventEmitter();\n\tprivate readonly executor = new ExecuteWrapper();\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic readonly onSuccess = this.executor.onSuccess;\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic readonly onFailure = this.executor.onFailure;\n\n\t/**\n\t * Emitter that fires when an item is rejected from the bulkhead.\n\t */\n\tpublic readonly onReject = this.onRejectEmitter.addListener;\n\n\t/**\n\t * Returns the number of available execution slots at this point in time.\n\t */\n\tpublic get executionSlots() {\n\t\treturn this.capacity - this.active;\n\t}\n\n\t/**\n\t * Returns the number of queue slots at this point in time.\n\t */\n\tpublic get queueSlots() {\n\t\treturn this.queueCapacity - this.queue.length;\n\t}\n\n\t/**\n\t * Bulkhead limits concurrent requests made.\n\t */\n\tconstructor(private readonly capacity: number, private readonly queueCapacity: number) { }\n\n\t/**\n\t * Executes the given function.\n\t * @param fn Function to execute\n\t * @throws a {@link BulkheadRejectedException} if the bulkhead limits are exceeeded\n\t */\n\tpublic async execute(\n\t\tfn: (context: IDefaultPolicyContext) => PromiseLike | T,\n\t\tsignal = neverAbortedSignal,\n\t): Promise {\n\t\tif (signal.aborted) {\n\t\t\tthrow new TaskCancelledError();\n\t\t}\n\n\t\tif (this.active < this.capacity) {\n\t\t\tthis.active++;\n\t\t\ttry {\n\t\t\t\treturn await fn({ signal });\n\t\t\t} finally {\n\t\t\t\tthis.active--;\n\t\t\t\tthis.dequeue();\n\t\t\t}\n\t\t}\n\n\t\tif (this.queue.length >= this.queueCapacity) {\n\t\t\tthis.onRejectEmitter.emit();\n\t\t\tthrow new BulkheadRejectedError(this.capacity, this.queueCapacity);\n\t\t}\n\t\tconst { resolve, reject, promise } = defer();\n\t\tthis.queue.push({ signal, fn, resolve, reject });\n\t\treturn promise;\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[81,103)", + "modifiedRange": "[81,88)", + "innerChanges": [ + { + "originalRange": "[81,26 -> 81,26]", + "modifiedRange": "[81,26 -> 81,27]" + }, + { + "originalRange": "[81,48 -> 86,1 EOL]", + "modifiedRange": "[81,49 -> 81,49 EOL]" + }, + { + "originalRange": "[87,1 -> 87,1]", + "modifiedRange": "[82,1 -> 82,2]" + }, + { + "originalRange": "[88,1 -> 88,1]", + "modifiedRange": "[83,1 -> 83,2]" + }, + { + "originalRange": "[89,1 -> 89,1]", + "modifiedRange": "[84,1 -> 84,2]" + }, + { + "originalRange": "[90,1 -> 92,1]", + "modifiedRange": "[85,1 -> 85,1]" + }, + { + "originalRange": "[92,9 -> 97,4]", + "modifiedRange": "[85,9 -> 85,29]" + }, + { + "originalRange": "[97,10 -> 97,20 EOL]", + "modifiedRange": "[85,35 -> 85,51 EOL]" + }, + { + "originalRange": "[98,3 -> 98,16]", + "modifiedRange": "[86,3 -> 86,3]" + }, + { + "originalRange": "[98,21 -> 98,43]", + "modifiedRange": "[86,8 -> 86,21]" + }, + { + "originalRange": "[98,49 -> 99,15]", + "modifiedRange": "[86,27 -> 86,33]" + }, + { + "originalRange": "[99,22 -> 100,16]", + "modifiedRange": "[86,40 -> 86,42]" + }, + { + "originalRange": "[100,22 -> 100,22]", + "modifiedRange": "[86,48 -> 86,50]" + }, + { + "originalRange": "[101,2 -> 102,2 EOL]", + "modifiedRange": "[87,2 -> 87,18 EOL]" + } + ] + } + ] +} \ No newline at end of file diff --git a/patched-vscode/src/vs/monaco.d.ts b/patched-vscode/src/vs/monaco.d.ts index ea2f3c43..be2c930e 100644 --- a/patched-vscode/src/vs/monaco.d.ts +++ b/patched-vscode/src/vs/monaco.d.ts @@ -2919,6 +2919,9 @@ declare namespace monaco.editor { * An event describing a change in the text of a model. */ export interface IModelContentChangedEvent { + /** + * The changes are ordered from the end of the document to the beginning, so they should be safe to apply in sequence. + */ readonly changes: IModelContentChange[]; /** * The (new) end-of-line character. @@ -3735,6 +3738,11 @@ declare namespace monaco.editor { * Defaults to false. */ peekWidgetDefaultFocus?: 'tree' | 'editor'; + /** + * Sets a placeholder for the editor. + * If set, the placeholder is shown if the editor is empty. + */ + placeholder?: string | undefined; /** * Controls whether the definition link opens element in the peek widget. * Defaults to false. @@ -3816,6 +3824,11 @@ declare namespace monaco.editor { * and the diff editor has a width less than `renderSideBySideInlineBreakpoint`, the inline view is used. */ useInlineViewWhenSpaceIsLimited?: boolean; + /** + * If set, the diff editor is optimized for small views. + * Defaults to `false`. + */ + compactMode?: boolean; /** * Timeout in milliseconds after which diff computation is cancelled. * Defaults to 5000. @@ -3878,6 +3891,10 @@ declare namespace monaco.editor { */ showMoves?: boolean; showEmptyDecorations?: boolean; + /** + * Only applies when `renderSideBySide` is set to false. + */ + useTrueInlineView?: boolean; }; /** * Is the diff editor inside another editor @@ -4043,11 +4060,13 @@ declare namespace monaco.editor { multipleDeclarations?: GoToLocationValues; multipleImplementations?: GoToLocationValues; multipleReferences?: GoToLocationValues; + multipleTests?: GoToLocationValues; alternativeDefinitionCommand?: string; alternativeTypeDefinitionCommand?: string; alternativeDeclarationCommand?: string; alternativeImplementationCommand?: string; alternativeReferenceCommand?: string; + alternativeTestsCommand?: string; } /** @@ -4566,7 +4585,6 @@ declare namespace monaco.editor { * Does not clear active inline suggestions when the editor loses focus. */ keepOnBlur?: boolean; - backgroundColoring?: boolean; } export interface IBracketPairColorizationOptions { @@ -4929,68 +4947,69 @@ declare namespace monaco.editor { pasteAs = 85, parameterHints = 86, peekWidgetDefaultFocus = 87, - definitionLinkOpensInPeek = 88, - quickSuggestions = 89, - quickSuggestionsDelay = 90, - readOnly = 91, - readOnlyMessage = 92, - renameOnType = 93, - renderControlCharacters = 94, - renderFinalNewline = 95, - renderLineHighlight = 96, - renderLineHighlightOnlyWhenFocus = 97, - renderValidationDecorations = 98, - renderWhitespace = 99, - revealHorizontalRightPadding = 100, - roundedSelection = 101, - rulers = 102, - scrollbar = 103, - scrollBeyondLastColumn = 104, - scrollBeyondLastLine = 105, - scrollPredominantAxis = 106, - selectionClipboard = 107, - selectionHighlight = 108, - selectOnLineNumbers = 109, - showFoldingControls = 110, - showUnused = 111, - snippetSuggestions = 112, - smartSelect = 113, - smoothScrolling = 114, - stickyScroll = 115, - stickyTabStops = 116, - stopRenderingLineAfter = 117, - suggest = 118, - suggestFontSize = 119, - suggestLineHeight = 120, - suggestOnTriggerCharacters = 121, - suggestSelection = 122, - tabCompletion = 123, - tabIndex = 124, - unicodeHighlighting = 125, - unusualLineTerminators = 126, - useShadowDOM = 127, - useTabStops = 128, - wordBreak = 129, - wordSegmenterLocales = 130, - wordSeparators = 131, - wordWrap = 132, - wordWrapBreakAfterCharacters = 133, - wordWrapBreakBeforeCharacters = 134, - wordWrapColumn = 135, - wordWrapOverride1 = 136, - wordWrapOverride2 = 137, - wrappingIndent = 138, - wrappingStrategy = 139, - showDeprecated = 140, - inlayHints = 141, - editorClassName = 142, - pixelRatio = 143, - tabFocusMode = 144, - layoutInfo = 145, - wrappingInfo = 146, - defaultColorDecorators = 147, - colorDecoratorsActivatedOn = 148, - inlineCompletionsAccessibilityVerbose = 149 + placeholder = 88, + definitionLinkOpensInPeek = 89, + quickSuggestions = 90, + quickSuggestionsDelay = 91, + readOnly = 92, + readOnlyMessage = 93, + renameOnType = 94, + renderControlCharacters = 95, + renderFinalNewline = 96, + renderLineHighlight = 97, + renderLineHighlightOnlyWhenFocus = 98, + renderValidationDecorations = 99, + renderWhitespace = 100, + revealHorizontalRightPadding = 101, + roundedSelection = 102, + rulers = 103, + scrollbar = 104, + scrollBeyondLastColumn = 105, + scrollBeyondLastLine = 106, + scrollPredominantAxis = 107, + selectionClipboard = 108, + selectionHighlight = 109, + selectOnLineNumbers = 110, + showFoldingControls = 111, + showUnused = 112, + snippetSuggestions = 113, + smartSelect = 114, + smoothScrolling = 115, + stickyScroll = 116, + stickyTabStops = 117, + stopRenderingLineAfter = 118, + suggest = 119, + suggestFontSize = 120, + suggestLineHeight = 121, + suggestOnTriggerCharacters = 122, + suggestSelection = 123, + tabCompletion = 124, + tabIndex = 125, + unicodeHighlighting = 126, + unusualLineTerminators = 127, + useShadowDOM = 128, + useTabStops = 129, + wordBreak = 130, + wordSegmenterLocales = 131, + wordSeparators = 132, + wordWrap = 133, + wordWrapBreakAfterCharacters = 134, + wordWrapBreakBeforeCharacters = 135, + wordWrapColumn = 136, + wordWrapOverride1 = 137, + wordWrapOverride2 = 138, + wrappingIndent = 139, + wrappingStrategy = 140, + showDeprecated = 141, + inlayHints = 142, + editorClassName = 143, + pixelRatio = 144, + tabFocusMode = 145, + layoutInfo = 146, + wrappingInfo = 147, + defaultColorDecorators = 148, + colorDecoratorsActivatedOn = 149, + inlineCompletionsAccessibilityVerbose = 150 } export const EditorOptions: { @@ -5003,8 +5022,8 @@ declare namespace monaco.editor { screenReaderAnnounceInlineSuggestion: IEditorOption; autoClosingBrackets: IEditorOption; autoClosingComments: IEditorOption; - autoClosingDelete: IEditorOption; - autoClosingOvertype: IEditorOption; + autoClosingDelete: IEditorOption; + autoClosingOvertype: IEditorOption; autoClosingQuotes: IEditorOption; autoIndent: IEditorOption; automaticLayout: IEditorOption; @@ -5016,7 +5035,7 @@ declare namespace monaco.editor { codeLensFontFamily: IEditorOption; codeLensFontSize: IEditorOption; colorDecorators: IEditorOption; - colorDecoratorActivatedOn: IEditorOption; + colorDecoratorActivatedOn: IEditorOption; colorDecoratorsLimit: IEditorOption; columnSelection: IEditorOption; comments: IEditorOption>>; @@ -5083,6 +5102,7 @@ declare namespace monaco.editor { pasteAs: IEditorOption>>; parameterHints: IEditorOption>>; peekWidgetDefaultFocus: IEditorOption; + placeholder: IEditorOption; definitionLinkOpensInPeek: IEditorOption; quickSuggestions: IEditorOption; quickSuggestionsDelay: IEditorOption; @@ -5124,13 +5144,13 @@ declare namespace monaco.editor { tabCompletion: IEditorOption; tabIndex: IEditorOption; unicodeHighlight: IEditorOption; - unusualLineTerminators: IEditorOption; + unusualLineTerminators: IEditorOption; useShadowDOM: IEditorOption; useTabStops: IEditorOption; wordBreak: IEditorOption; wordSegmenterLocales: IEditorOption; wordSeparators: IEditorOption; - wordWrap: IEditorOption; + wordWrap: IEditorOption; wordWrapBreakAfterCharacters: IEditorOption; wordWrapBreakBeforeCharacters: IEditorOption; wordWrapColumn: IEditorOption; @@ -7939,6 +7959,11 @@ declare namespace monaco.languages { arguments?: any[]; } + export interface CommentThreadRevealOptions { + preserveFocus: boolean; + focusReply: boolean; + } + export interface CommentAuthorInformation { name: string; iconPath?: UriComponents; @@ -8053,7 +8078,7 @@ declare namespace monaco.languages { * * @param document The document to provide mapped edits for. * @param codeBlocks Code blocks that come from an LLM's reply. - * "Insert at cursor" in the panel chat only sends one edit that the user clicks on, but inline chat can send multiple blocks and let the lang server decide what to do with them. + * "Apply in Editor" in the panel chat only sends one edit that the user clicks on, but inline chat can send multiple blocks and let the lang server decide what to do with them. * @param context The context for providing mapped edits. * @param token A cancellation token. * @returns A provider result of text edits. diff --git a/patched-vscode/src/vs/nls.build.ts b/patched-vscode/src/vs/nls.build.ts deleted file mode 100644 index 05c063fa..00000000 --- a/patched-vscode/src/vs/nls.build.ts +++ /dev/null @@ -1,88 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const buildMap: { [name: string]: string[] } = {}; -const buildMapKeys: { [name: string]: string[] } = {}; -const entryPoints: { [entryPoint: string]: string[] } = {}; - -export interface ILocalizeInfo { - key: string; - comment: string[]; -} - -export function localize(data: ILocalizeInfo | string, message: string, ...args: (string | number | boolean | undefined | null)[]): string { - throw new Error(`Not supported at build time!`); -} - -export function localize2(data: ILocalizeInfo | string, message: string, ...args: (string | number | boolean | undefined | null)[]): never { - throw new Error(`Not supported at build time!`); -} - -export function getConfiguredDefaultLocale(): string | undefined { - throw new Error(`Not supported at build time!`); -} - -/** - * Invoked by the loader at build-time - */ -export function load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void { - if (!name || name.length === 0) { - load({ localize, localize2, getConfiguredDefaultLocale }); - } else { - req([name + '.nls', name + '.nls.keys'], function (messages: string[], keys: string[]) { - buildMap[name] = messages; - buildMapKeys[name] = keys; - load(messages); - }); - } -} - -/** - * Invoked by the loader at build-time - */ -export function write(pluginName: string, moduleName: string, write: AMDLoader.IPluginWriteCallback): void { - const entryPoint = write.getEntryPoint(); - - entryPoints[entryPoint] = entryPoints[entryPoint] || []; - entryPoints[entryPoint].push(moduleName); - - if (moduleName !== entryPoint) { - write.asModule(pluginName + '!' + moduleName, 'define([\'vs/nls\', \'vs/nls!' + entryPoint + '\'], function(nls, data) { return nls.create("' + moduleName + '", data); });'); - } -} - -/** - * Invoked by the loader at build-time - */ -export function writeFile(pluginName: string, moduleName: string, req: AMDLoader.IRelativeRequire, write: AMDLoader.IPluginWriteFileCallback, config: AMDLoader.IConfigurationOptions): void { - if (entryPoints.hasOwnProperty(moduleName)) { - const fileName = req.toUrl(moduleName + '.nls.js'); - const contents = [ - '/*---------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' *--------------------------------------------------------*/' - ], - entries = entryPoints[moduleName]; - - const data: { [moduleName: string]: string[] } = {}; - for (let i = 0; i < entries.length; i++) { - data[entries[i]] = buildMap[entries[i]]; - } - - contents.push('define("' + moduleName + '.nls", ' + JSON.stringify(data, null, '\t') + ');'); - write(fileName, contents.join('\r\n')); - } -} - -/** - * Invoked by the loader at build-time - */ -export function finishBuild(write: AMDLoader.IPluginWriteFileCallback): void { - write('nls.metadata.json', JSON.stringify({ - keys: buildMapKeys, - messages: buildMap, - bundles: entryPoints - }, null, '\t')); -} diff --git a/patched-vscode/src/vs/nls.messages.ts b/patched-vscode/src/vs/nls.messages.ts new file mode 100644 index 00000000..2e8b0966 --- /dev/null +++ b/patched-vscode/src/vs/nls.messages.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* + * This module exists so that the AMD build of the monaco editor can replace this with an async loader plugin. + * If you add new functions to this module make sure that they are also provided in the AMD build of the monaco editor. + */ + +export function getNLSMessages(): string[] { + return globalThis._VSCODE_NLS_MESSAGES; +} + +export function getNLSLanguage(): string | undefined { + return globalThis._VSCODE_NLS_LANGUAGE; +} diff --git a/patched-vscode/src/vs/nls.mock.ts b/patched-vscode/src/vs/nls.mock.ts deleted file mode 100644 index 5323c6c6..00000000 --- a/patched-vscode/src/vs/nls.mock.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export interface ILocalizeInfo { - key: string; - comment: string[]; -} - -export interface ILocalizedString { - original: string; - value: string; -} - -function _format(message: string, args: any[]): string { - let result: string; - if (args.length === 0) { - result = message; - } else { - result = message.replace(/\{(\d+)\}/g, function (match, rest) { - const index = rest[0]; - return typeof args[index] !== 'undefined' ? args[index] : match; - }); - } - return result; -} - -export function localize(data: ILocalizeInfo | string, message: string, ...args: any[]): string { - return _format(message, args); -} - -export function localize2(data: ILocalizeInfo | string, message: string, ...args: any[]): ILocalizedString { - const res = _format(message, args); - return { - original: res, - value: res - }; -} - -export function getConfiguredDefaultLocale(_: string) { - return undefined; -} diff --git a/patched-vscode/src/vs/nls.ts b/patched-vscode/src/vs/nls.ts index 233840e6..2e914738 100644 --- a/patched-vscode/src/vs/nls.ts +++ b/patched-vscode/src/vs/nls.ts @@ -3,27 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -let isPseudo = (typeof document !== 'undefined' && document.location && document.location.hash.indexOf('pseudo=true') >= 0); -const DEFAULT_TAG = 'i-default'; +// eslint-disable-next-line local/code-import-patterns +import { getNLSLanguage, getNLSMessages } from 'vs/nls.messages'; +// eslint-disable-next-line local/code-import-patterns +export { getNLSLanguage, getNLSMessages } from 'vs/nls.messages'; -interface INLSPluginConfig { - availableLanguages?: INLSPluginConfigAvailableLanguages; - loadBundle?: BundleLoader; - translationServiceUrl?: string; -} - -export interface INLSPluginConfigAvailableLanguages { - '*'?: string; - [module: string]: string | undefined; -} - -interface BundleLoader { - (bundle: string, locale: string | null, cb: (err: Error, messages: string[] | IBundledStrings) => void): void; -} - -interface IBundledStrings { - [moduleId: string]: string[]; -} +const isPseudo = getNLSLanguage() === 'pseudo' || (typeof document !== 'undefined' && document.location && document.location.hash.indexOf('pseudo=true') >= 0); export interface ILocalizeInfo { key: string; @@ -35,30 +20,6 @@ export interface ILocalizedString { value: string; } -interface ILocalizeFunc { - (info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string; - (key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string; -} - -interface IBoundLocalizeFunc { - (idx: number, defaultValue: null): string; -} - -interface ILocalize2Func { - (info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString; - (key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString; -} - -interface IBoundLocalize2Func { - (idx: number, defaultValue: string): ILocalizedString; -} - -interface IConsumerAPI { - localize: ILocalizeFunc | IBoundLocalizeFunc; - localize2: ILocalize2Func | IBoundLocalize2Func; - getConfiguredDefaultLocale(stringFromLocalizeCall: string): string | undefined; -} - function _format(message: string, args: (string | number | boolean | undefined | null)[]): string { let result: string; @@ -86,49 +47,6 @@ function _format(message: string, args: (string | number | boolean | undefined | return result; } -function findLanguageForModule(config: INLSPluginConfigAvailableLanguages, name: string) { - let result = config[name]; - if (result) { - return result; - } - result = config['*']; - if (result) { - return result; - } - return null; -} - -function endWithSlash(path: string): string { - if (path.charAt(path.length - 1) === '/') { - return path; - } - return path + '/'; -} - -async function getMessagesFromTranslationsService(translationServiceUrl: string, language: string, name: string): Promise { - const url = endWithSlash(translationServiceUrl) + endWithSlash(language) + 'vscode/' + endWithSlash(name); - const res = await fetch(url); - if (res.ok) { - const messages = await res.json() as string[] | IBundledStrings; - return messages; - } - throw new Error(`${res.status} - ${res.statusText}`); -} - -function createScopedLocalize(scope: string[]): IBoundLocalizeFunc { - return function (idx: number, defaultValue: null) { - const restArgs = Array.prototype.slice.call(arguments, 2); - return _format(scope[idx], restArgs); - }; -} - -function createScopedLocalize2(scope: string[]): IBoundLocalize2Func { - return (idx: number, defaultValue: string, ...args) => ({ - value: _format(scope[idx], args), - original: _format(defaultValue, args) - }); -} - /** * Marks a string to be localized. Returns the localized string. * @@ -160,10 +78,29 @@ export function localize(key: string, message: string, ...args: (string | number /** * @skipMangle */ -export function localize(data: ILocalizeInfo | string, message: string, ...args: (string | number | boolean | undefined | null)[]): string { +export function localize(data: ILocalizeInfo | string /* | number when built */, message: string /* | null when built */, ...args: (string | number | boolean | undefined | null)[]): string { + if (typeof data === 'number') { + return _format(lookupMessage(data, message), args); + } return _format(message, args); } +/** + * Only used when built: Looks up the message in the global NLS table. + * This table is being made available as a global through bootstrapping + * depending on the target context. + */ +function lookupMessage(index: number, fallback: string | null): string { + const message = getNLSMessages()?.[index]; + if (typeof message !== 'string') { + if (typeof fallback === 'string') { + return fallback; + } + throw new Error(`!!! NLS MISSING: ${index} !!!`); + } + return message; +} + /** * Marks a string to be localized. Returns an {@linkcode ILocalizedString} * which contains the localized string and the original string. @@ -197,123 +134,107 @@ export function localize2(key: string, message: string, ...args: (string | numbe /** * @skipMangle */ -export function localize2(data: ILocalizeInfo | string, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString { - const original = _format(message, args); - return { - value: original, - original - }; -} - -/** - * - * @param stringFromLocalizeCall You must pass in a string that was returned from a `nls.localize()` call - * in order to ensure the loader plugin has been initialized before this function is called. - */ -export function getConfiguredDefaultLocale(stringFromLocalizeCall: string): string | undefined; -/** - * @skipMangle - */ -export function getConfiguredDefaultLocale(_: string): string | undefined { - // This returns undefined because this implementation isn't used and is overwritten by the loader - // when loaded. - return undefined; -} +export function localize2(data: ILocalizeInfo | string /* | number when built */, originalMessage: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString { + let message: string; + if (typeof data === 'number') { + message = lookupMessage(data, originalMessage); + } else { + message = originalMessage; + } -/** - * @skipMangle - */ -export function setPseudoTranslation(value: boolean) { - isPseudo = value; -} + const value = _format(message, args); -/** - * Invoked in a built product at run-time - * @skipMangle - */ -export function create(key: string, data: IBundledStrings & IConsumerAPI): IConsumerAPI { return { - localize: createScopedLocalize(data[key]), - localize2: createScopedLocalize2(data[key]), - getConfiguredDefaultLocale: data.getConfiguredDefaultLocale ?? ((_: string) => undefined) + value, + original: originalMessage === message ? value : _format(originalMessage, args) }; } -/** - * Invoked by the loader at run-time - * @skipMangle - */ -export function load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void { - const pluginConfig: INLSPluginConfig = config['vs/nls'] ?? {}; - if (!name || name.length === 0) { - // TODO: We need to give back the mangled names here - return load({ - localize: localize, - localize2: localize2, - getConfiguredDefaultLocale: () => pluginConfig.availableLanguages?.['*'] - } as IConsumerAPI); - } - const language = pluginConfig.availableLanguages ? findLanguageForModule(pluginConfig.availableLanguages, name) : null; - const useDefaultLanguage = language === null || language === DEFAULT_TAG; - let suffix = '.nls'; - if (!useDefaultLanguage) { - suffix = suffix + '.' + language; - } - const messagesLoaded = (messages: string[] | IBundledStrings) => { - if (Array.isArray(messages)) { - (messages as any as IConsumerAPI).localize = createScopedLocalize(messages); - (messages as any as IConsumerAPI).localize2 = createScopedLocalize2(messages); - } else { - (messages as any as IConsumerAPI).localize = createScopedLocalize(messages[name]); - (messages as any as IConsumerAPI).localize2 = createScopedLocalize2(messages[name]); - } - (messages as any as IConsumerAPI).getConfiguredDefaultLocale = () => pluginConfig.availableLanguages?.['*']; - load(messages); - }; - if (typeof pluginConfig.loadBundle === 'function') { - (pluginConfig.loadBundle as BundleLoader)(name, language, (err: Error, messages) => { - // We have an error. Load the English default strings to not fail - if (err) { - req([name + '.nls'], messagesLoaded); - } else { - messagesLoaded(messages); - } - }); - } else if (pluginConfig.translationServiceUrl && !useDefaultLanguage) { - (async () => { - try { - const messages = await getMessagesFromTranslationsService(pluginConfig.translationServiceUrl!, language, name); - return messagesLoaded(messages); - } catch (err) { - // Language is already as generic as it gets, so require default messages - if (!language.includes('-')) { - console.error(err); - return req([name + '.nls'], messagesLoaded); - } - try { - // Since there is a dash, the language configured is a specific sub-language of the same generic language. - // Since we were unable to load the specific language, try to load the generic language. Ex. we failed to find a - // Swiss German (de-CH), so try to load the generic German (de) messages instead. - const genericLanguage = language.split('-')[0]; - const messages = await getMessagesFromTranslationsService(pluginConfig.translationServiceUrl!, genericLanguage, name); - // We got some messages, so we configure the configuration to use the generic language for this session. - pluginConfig.availableLanguages ??= {}; - pluginConfig.availableLanguages['*'] = genericLanguage; - return messagesLoaded(messages); - } catch (err) { - console.error(err); - return req([name + '.nls'], messagesLoaded); - } - } - })(); - } else { - req([name + suffix], messagesLoaded, (err: Error) => { - if (suffix === '.nls') { - console.error('Failed trying to load default language strings', err); - return; - } - console.error(`Failed to load message bundle for language ${language}. Falling back to the default language:`, err); - req([name + '.nls'], messagesLoaded); - }); - } -} +export interface INLSLanguagePackConfiguration { + + /** + * The path to the translations config file that contains pointers to + * all message bundles for `main` and extensions. + */ + readonly translationsConfigFile: string; + + /** + * The path to the file containing the translations for this language + * pack as flat string array. + */ + readonly messagesFile: string; + + /** + * The path to the file that can be used to signal a corrupt language + * pack, for example when reading the `messagesFile` fails. This will + * instruct the application to re-create the cache on next startup. + */ + readonly corruptMarkerFile: string; +} + +export interface INLSConfiguration { + + /** + * Locale as defined in `argv.json` or `app.getLocale()`. + */ + readonly userLocale: string; + + /** + * Locale as defined by the OS (e.g. `app.getPreferredSystemLanguages()`). + */ + readonly osLocale: string; + + /** + * The actual language of the UI that ends up being used considering `userLocale` + * and `osLocale`. + */ + readonly resolvedLanguage: string; + + /** + * Defined if a language pack is used that is not the + * default english language pack. This requires a language + * pack to be installed as extension. + */ + readonly languagePack?: INLSLanguagePackConfiguration; + + /** + * The path to the file containing the default english messages + * as flat string array. The file is only present in built + * versions of the application. + */ + readonly defaultMessagesFile: string; + + /** + * Below properties are deprecated and only there to continue support + * for `vscode-nls` module that depends on them. + * Refs https://github.com/microsoft/vscode-nls/blob/main/src/node/main.ts#L36-L46 + */ + /** @deprecated */ + readonly locale: string; + /** @deprecated */ + readonly availableLanguages: Record; + /** @deprecated */ + readonly _languagePackSupport?: boolean; + /** @deprecated */ + readonly _languagePackId?: string; + /** @deprecated */ + readonly _translationsConfigFile?: string; + /** @deprecated */ + readonly _cacheRoot?: string; + /** @deprecated */ + readonly _resolvedLanguagePackCoreLocation?: string; + /** @deprecated */ + readonly _corruptedFile?: string; +} + +export interface ILanguagePack { + readonly hash: string; + readonly label: string | undefined; + readonly extensions: { + readonly extensionIdentifier: { readonly id: string; readonly uuid?: string }; + readonly version: string; + }[]; + readonly translations: Record; +} + +export type ILanguagePacks = Record; diff --git a/patched-vscode/src/vs/platform/accessibility/browser/accessibilityService.ts b/patched-vscode/src/vs/platform/accessibility/browser/accessibilityService.ts index bd84abbc..408fbc07 100644 --- a/patched-vscode/src/vs/platform/accessibility/browser/accessibilityService.ts +++ b/patched-vscode/src/vs/platform/accessibility/browser/accessibilityService.ts @@ -24,6 +24,9 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe protected _systemMotionReduced: boolean; protected readonly _onDidChangeReducedMotion = new Emitter(); + private _linkUnderlinesEnabled: boolean; + protected readonly _onDidChangeLinkUnderline = new Emitter(); + constructor( @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ILayoutService private readonly _layoutService: ILayoutService, @@ -50,7 +53,10 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe this._systemMotionReduced = reduceMotionMatcher.matches; this._configMotionReduced = this._configurationService.getValue<'auto' | 'on' | 'off'>('workbench.reduceMotion'); + this._linkUnderlinesEnabled = this._configurationService.getValue('accessibility.underlineLinks'); + this.initReducedMotionListeners(reduceMotionMatcher); + this.initLinkUnderlineListeners(); } private initReducedMotionListeners(reduceMotionMatcher: MediaQueryList) { @@ -72,6 +78,29 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe this._register(this.onDidChangeReducedMotion(() => updateRootClasses())); } + private initLinkUnderlineListeners() { + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('accessibility.underlineLinks')) { + const linkUnderlinesEnabled = this._configurationService.getValue('accessibility.underlineLinks'); + this._linkUnderlinesEnabled = linkUnderlinesEnabled; + this._onDidChangeLinkUnderline.fire(); + } + })); + + const updateLinkUnderlineClasses = () => { + const underlineLinks = this._linkUnderlinesEnabled; + this._layoutService.mainContainer.classList.toggle('underline-links', underlineLinks); + }; + + updateLinkUnderlineClasses(); + + this._register(this.onDidChangeLinkUnderlines(() => updateLinkUnderlineClasses())); + } + + public onDidChangeLinkUnderlines(listener: () => void) { + return this._onDidChangeLinkUnderline.event(listener); + } + get onDidChangeScreenReaderOptimized(): Event { return this._onDidChangeScreenReaderOptimized.event; } diff --git a/patched-vscode/src/vs/platform/accessibility/browser/accessibleView.ts b/patched-vscode/src/vs/platform/accessibility/browser/accessibleView.ts index d71a5871..071afc8f 100644 --- a/patched-vscode/src/vs/platform/accessibility/browser/accessibleView.ts +++ b/patched-vscode/src/vs/platform/accessibility/browser/accessibleView.ts @@ -9,6 +9,7 @@ import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQui import { Event } from 'vs/base/common/event'; import { IAction } from 'vs/base/common/actions'; import { IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; export const IAccessibleViewService = createDecorator('accessibleViewService'); @@ -26,7 +27,10 @@ export const enum AccessibleViewProviderId { Hover = 'hover', Notification = 'notification', EmptyEditorHint = 'emptyEditorHint', - Comments = 'comments' + Comments = 'comments', + Repl = 'repl', + ReplHelp = 'replHelp', + RunAndDebug = 'runAndDebug', } export const enum AccessibleViewType { @@ -66,10 +70,15 @@ export interface IAccessibleViewOptions { * Keybinding items to configure */ configureKeybindingItems?: IQuickPickItem[]; + + /** + * Keybinding items that are already configured + */ + configuredKeybindingItems?: IQuickPickItem[]; } -export interface IAccessibleViewContentProvider extends IBasicContentProvider { +export interface IAccessibleViewContentProvider extends IBasicContentProvider, IDisposable { id: AccessibleViewProviderId; verbositySettingKey: string; /** @@ -102,6 +111,7 @@ export interface IPosition { export interface IAccessibleViewService { readonly _serviceBrand: undefined; + // The provider will be disposed when the view is closed show(provider: AccesibleViewContentProvider, position?: IPosition): void; showLastProvider(id: AccessibleViewProviderId): void; showAccessibleViewHelp(): void; @@ -111,7 +121,7 @@ export interface IAccessibleViewService { goToSymbol(): void; disableHint(): void; getPosition(id: AccessibleViewProviderId): IPosition | undefined; - setPosition(position: IPosition, reveal?: boolean): void; + setPosition(position: IPosition, reveal?: boolean, select?: boolean): void; getLastPosition(): IPosition | undefined; /** * If the setting is enabled, provides the open accessible view hint as a localized string. @@ -119,7 +129,7 @@ export interface IAccessibleViewService { */ getOpenAriaHint(verbositySettingKey: string): string | null; getCodeBlockContext(): ICodeBlockActionContext | undefined; - configureKeybindings(): void; + configureKeybindings(unassigned: boolean): void; openHelpLink(): void; } @@ -131,9 +141,9 @@ export interface ICodeBlockActionContext { element: unknown; } -export type AccesibleViewContentProvider = AdvancedContentProvider | ExtensionContentProvider; +export type AccesibleViewContentProvider = AccessibleContentProvider | ExtensionContentProvider; -export class AdvancedContentProvider implements IAccessibleViewContentProvider { +export class AccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider { constructor( public id: AccessibleViewProviderId, @@ -143,16 +153,18 @@ export class AdvancedContentProvider implements IAccessibleViewContentProvider { public verbositySettingKey: string, public onOpen?: () => void, public actions?: IAction[], - public next?: () => void, - public previous?: () => void, + public provideNextContent?: () => string | undefined, + public providePreviousContent?: () => string | undefined, public onDidChangeContent?: Event, public onKeyDown?: (e: IKeyboardEvent) => void, public getSymbols?: () => IAccessibleViewSymbol[], public onDidRequestClearLastProvider?: Event, - ) { } + ) { + super(); + } } -export class ExtensionContentProvider implements IBasicContentProvider { +export class ExtensionContentProvider extends Disposable implements IBasicContentProvider { constructor( public readonly id: string, @@ -160,21 +172,23 @@ export class ExtensionContentProvider implements IBasicContentProvider { public provideContent: () => string, public onClose: () => void, public onOpen?: () => void, - public next?: () => void, - public previous?: () => void, + public provideNextContent?: () => string | undefined, + public providePreviousContent?: () => string | undefined, public actions?: IAction[], public onDidChangeContent?: Event, - ) { } + ) { + super(); + } } -export interface IBasicContentProvider { +export interface IBasicContentProvider extends IDisposable { id: string; options: IAccessibleViewOptions; onClose(): void; provideContent(): string; onOpen?(): void; actions?: IAction[]; - previous?(): void; - next?(): void; + providePreviousContent?(): void; + provideNextContent?(): void; onDidChangeContent?: Event; } diff --git a/patched-vscode/src/vs/platform/accessibility/browser/accessibleViewRegistry.ts b/patched-vscode/src/vs/platform/accessibility/browser/accessibleViewRegistry.ts index 0a636956..d915dd4e 100644 --- a/patched-vscode/src/vs/platform/accessibility/browser/accessibleViewRegistry.ts +++ b/patched-vscode/src/vs/platform/accessibility/browser/accessibleViewRegistry.ts @@ -4,19 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { AccessibleViewType, AdvancedContentProvider, ExtensionContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewType, AccessibleContentProvider, ExtensionContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { alert } from 'vs/base/browser/ui/aria/aria'; -export interface IAccessibleViewImplentation extends IDisposable { +export interface IAccessibleViewImplentation { type: AccessibleViewType; priority: number; name: string; /** * @returns the provider or undefined if the view should not be shown */ - getProvider: (accessor: ServicesAccessor) => AdvancedContentProvider | ExtensionContentProvider | undefined; + getProvider: (accessor: ServicesAccessor) => AccessibleContentProvider | ExtensionContentProvider | undefined; when?: ContextKeyExpression | undefined; } @@ -31,7 +30,6 @@ export const AccessibleViewRegistry = new class AccessibleViewRegistry { if (idx !== -1) { this._implementations.splice(idx, 1); } - implementation.dispose(); } }; } @@ -41,16 +39,3 @@ export const AccessibleViewRegistry = new class AccessibleViewRegistry { } }; -export function alertAccessibleViewFocusChange(index: number | undefined, length: number | undefined, type: 'next' | 'previous'): void { - if (index === undefined || length === undefined) { - return; - } - const number = index + 1; - - if (type === 'next' && number + 1 <= length) { - alert(`Focused ${number + 1} of ${length}`); - } else if (type === 'previous' && number - 1 > 0) { - alert(`Focused ${number - 1} of ${length}`); - } - return; -} diff --git a/patched-vscode/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/patched-vscode/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 11320fc7..b0e99c5d 100644 --- a/patched-vscode/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/patched-vscode/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -67,7 +67,7 @@ export interface IAccessbilitySignalOptions { export class AccessibilitySignalService extends Disposable implements IAccessibilitySignalService { readonly _serviceBrand: undefined; private readonly sounds: Map = new Map(); - private readonly screenReaderAttached = observableFromEvent( + private readonly screenReaderAttached = observableFromEvent(this, this.accessibilityService.onDidChangeScreenReaderOptimized, () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() ); @@ -145,7 +145,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi } private getVolumeInPercent(): number { - const volume = this.configurationService.getValue('accessibilitySignals.volume'); + const volume = this.configurationService.getValue('accessibility.signalOptions.volume'); if (typeof volume !== 'number') { return 50; } @@ -242,17 +242,16 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi } public getDelayMs(signal: AccessibilitySignal, modality: AccessibilityModality, mode: 'line' | 'positional'): number { - const options: { debouncePositionChanges: boolean; 'experimental.delays': { general: { sound: number; announcement: number }; errorAtPosition: { sound: number; announcement: number }; warningAtPosition: { sound: number; announcement: number } } } = this.configurationService.getValue('accessibility.signalOptions'); - if (!options || !options.debouncePositionChanges) { + if (!this.configurationService.getValue('accessibility.signalOptions.debouncePositionChanges')) { return 0; } let value: { sound: number; announcement: number }; if (signal.name === AccessibilitySignal.errorAtPosition.name && mode === 'positional') { - value = options['experimental.delays'].errorAtPosition; + value = this.configurationService.getValue('accessibility.signalOptions.experimental.delays.errorAtPosition'); } else if (signal.name === AccessibilitySignal.warningAtPosition.name && mode === 'positional') { - value = options['experimental.delays'].warningAtPosition; + value = this.configurationService.getValue('accessibility.signalOptions.experimental.delays.warningAtPosition'); } else { - value = options['experimental.delays'].general; + value = this.configurationService.getValue('accessibility.signalOptions.experimental.delays.general'); } return modality === 'sound' ? value.sound : value.announcement; } @@ -343,8 +342,7 @@ export class AccessibilitySignal { public readonly legacySoundSettingsKey: string | undefined, public readonly settingsKey: string, public readonly legacyAnnouncementSettingsKey: string | undefined, - public readonly announcementMessage: string | undefined, - public readonly delaySettingsKey: string | undefined + public readonly announcementMessage: string | undefined ) { } private static _signals = new Set(); @@ -371,7 +369,6 @@ export class AccessibilitySignal { options.settingsKey, options.legacyAnnouncementSettingsKey, options.announcementMessage, - options.delaySettingsKey ); AccessibilitySignal._signals.add(signal); return signal; diff --git a/patched-vscode/src/vs/platform/accessibilitySignal/browser/media/voiceRecordingStarted.mp3 b/patched-vscode/src/vs/platform/accessibilitySignal/browser/media/voiceRecordingStarted.mp3 index 488754fdd584a4c331f562d37d04488952bfd766..3c7de8ca03b2dc8ff1837c9541f456ad637af66c 100644 GIT binary patch delta 7878 zcmYjW2UrtJw4O~PfdGL}5^8`@1>Mk5goGwdrB{_ur6^5NRCG59ASfVRKvY1aTt!jr zjZj2DR1`$)2#R9GdIf>JaPR%zduP6#IXma<*)uc$bm1r@%)%6|^s%&Lz>CCka{iaG zG3WftSaF>GWndflKgP=XAI99;hV?H4|Ca;*Kc?t_iLMRD;=fP+F%CP~I6C}q#y^ZD z?0DvtraWSrF=bKq)n??C?Ex#>m^(JRpIX=sv0g`-nm3!<;LMt?nHp?>2MI9QgvAVI zWrnk&*1~g!=6pHa?G~%=ckc{c^qXii@QYDWf2Mzs^r$$|mseH@SU2-tO|ZH551MKX z* zy1IIrhsBZVJ=;$Ye0+ksPHNieNW2t`{TZv+LtI|Nxd~w_B1K~A1}GzH=0O6oWPD3Y z=MIg;^gC6le=fxDXo&3XiHh@yO(kyhPLJ}s7G6W<(n2C!&qfkEBYKa7|MrQ)X_F3y zB)vU+s4=nmR*YBEfboh;YnM__GoLkLmoPx&etK6A+Hq`Ylw-q=9c1%5ryi_q1Owj;xOCcq*>0Gkb&VGF)WaB|84@fafS_Y{5Q^1bl0;K5#h@V3%k? z1_)N=y8E>GpVsO)GSssw^)WQI0W=jsRgg)pQK?we~Vp~>}xy;|Qf zj9wXH!ToQ#<0|fluwK>@GNiGZ>HUBbwW3B%UAaqRiPlB=`dl?**JACDDj;jAbkmY> zSW&w*WGXhPD#s}R*6u6wwhG@u9CemD<`s9~jIY9j>G!T_(uj7hZDbl_Pxs&}ShGKB zoe%D-r%NMAN9(RByLU@LIo+`3-2+Qq9|PM{xxvnF=m+xx!jFbXx|F~FHM3heYOQ)p z>ClhnfZM13`peYBM8)_oi?db32@g*<>(;$^IX@qE5tZf%KGK6T3-P%n3hSr7CZ9>! zo2R>jZwxB=Q8JeiQ~@XYbB-$nl3xqigbg(S$P=c804ONT5Z2G2007l4ajeDD#%b}5 z2|RQi_=={2?>DYJu^uC90sW2%1GbBwDUo`az?iVKy4j?9t?A8=+v9Q?OO>VNwH^B^ zE3XiAI!De8=?wb%Eur00bZN$v|@m?gJpsJ43|Lb;BX!d#tTDX zB72E36u||k3>OOk6D)~2%f$h<7gR=zOujjO(SUzpZB<3yC?6V;8|$Ukr@zgmC-Vw+ zRE|dlNx;>S4mG1j8qRSgE-^82CnrAgQuH>*nVFdyFsk0QX|*7g38B|_Z|L3p4B>;4`8nEgw zG+3&$A*&T8g5`LyQi7hNZH{l1%Ckt%>b3qYwIHSDFtOBETTr1>oO2d0Tp@XE9lx@H z%kD+!kXb0BJGfHuuyc>I{vYqAf75XKM$jPzX&OyVzYHogmd~N7>y3+hl0p!TgepSl1zaTd6+zb?mTXUlR)|P6kKNvIWU=(~;uq1MFYV$+A}H@H z7sgIX6%Z6J{{#`+u>}OlsbCPfYHu+C^IHXFLSwKWdmk21Kp~tVRL0`wh^;6j6oYGp z96(@cp$Zlx;~6joB?7dNmZK!iM#EcCmC<(u1Hhwr(VCbTRExvs)ix01Fub+`6i0`& zC|n92t%WJ~WhfbJ16M4G?23vZ@J=^Xd*fQ;C%fZL>}t`Ks*br= z5^0$jbIzzfVzZI=u@+sHUB>fgz0KMiXP>tFhs_NhT~n3TxNEUK7HMi~>dRof^wY#6 z0s!(Lc1_1RL$cnjcegfSJe?}@9;A8XEz7d)w8oIJ!c1W`gjD5lA!QDZh=B=sDh9*(wi)q!AyUuS^c}a`)$eS8Fy+=pP`3mozfLUPKt7I^QH(J zov6j}#sbB4c=R9GiQ+dVVj{kt_j6;o8G0RRF66j&Ld;nq7pvbR+`p%5I6T= zT=)FMkX?DU4#)d_%Auom*|C7YXV76S;t*jP8D|BPj>$Tx+AhdDsxYKfq*)AS*a@ox zDEjKq#(GJ&2E%-Q3{x!;3UTbD8#e1d^jBvQCvM@ugKtMN(3+FcHm9}u>zElQIK9tm zD?7frmuo{_3KezuPL8~Z6D0Sb`DPDY5xds)gI3b`+5`1VPfwCXZubr8=I-Zy)FIS` zBS!mkuPgkpDH(2bArbTl&sGtN$GAE2xKj+a>^ASUYd4{v(ry=9^n8%LF}>guXk-O{ z>Wg^S_Z8%PO$q)g(#8-Z9%WR!^03l(au{qrrHq4vATKtcMYInML=(d`<}4UPUQ z*6%oxln^hP`19=D@86fV;MTiK7Im#D+dei(`_8F}Zw*qh)x~p~ zN)@;2NnXN4P6Z=e06NK7iuM4L5t8Rn^7w8-DnHM~dsV*UStqg;#cF^C)bk`^MIj0* za6pfS<7#N|wO*N)&QPE>s#iY$LE-@&j6-=bE!bCY&ylMMlOZE1?Gb@m{Md%=`9}*M z*4+xqW}M#&XP>=byJz#l`NpM}w_llg6DV4D`Ob|lBqpXYxN1WzsZJ^8i!%+FB}y6J zAgJcLbQfhC9^Gr8MbPD=3~3!|(VygpeGhBb%4KbJ%5zgo!n>N~>svj(YZ2#v`+Sw? zSHCx7&CA;Eg|Axpgj3PT-%0NS6vv)k_F3stU!wP*xeRuZ_>R553Et4N#Y`a;ZnC8= zGyNk$LYjKzX=EM=b)_{Ez}-~t+@ z4U{Azft&U;=O0a%Nk6A_&q6eNaNDlUFL#PQc>lf3D{u1K4ey1Q?_-Pw1O@^dDr1%1 z|G@Mh9Zek@D`td%Qfm)e;kqCo?Z-KJC>klbbhiT{-^d(?e1q!@e>gh)DxyYUJaRRo za{>>pU3GlPx~q~O6lK#gh*vXT^xjbptv_w`I_Yck!bNw^?lAW+b6-*{3@VB)3*}y{ ze{pYma&a=-0nWp6n2}YK1A9k z*;JOhONq4qPWTkj4DzDjAUE`#hKi$f`D*MwHhohURbCR?SZdyS^S%Zt@6_>4ey$_C zuLT}{7)~w z0)j$Fi~s8IYD&^;!wU58JU`H~)%=vr>cn%mcHTd~ z;&5O*DM$V?Yuv7X|U@-9rLn?*cQtNg`>8 zlF2&ine-e}WKx!TY*L-oR=C+k|KZ6VsIohz;>sb9FXn~ZAsVyYJaF4zHH@a&xeo_g zuz>ClLHm;LSLB!UmDE13GQHR=dU1u;Nb=5Qucs-aGboKzp@Jzw)75eKI8+D> z&HK^B7+I7FAO=(x#J*+SN#k+UGS67oBYujE^O_NrLt5SY+?X-0M`EH#I??B;I_tMP zt3PL+Sg(-g?R?tDZPr92n*Dfw)~^55@w$j}o_mT;e)(s?Azp#Cvb|t682}`~9s-e( z#>L1F(suFoLYc!_G?lzlL>@02+k^-^rLfgxxtb-QuSnt=S#B(nxRcFAte_wQp-={J z!>|+rxyfPTS`8sfkH>cq#*71+t&*P+9hIXqUaVxz=wBSS%xJ#u~HugBh^j{i1d2SIyxhScv z(D8_P2bNw;XLnu7@i*Kp$@1GiNk2oI3o_m=s#+6Kfrvhb=ky~Y(c25N zi0I{?|623^k_9XgkYiN_OmL+)$MQXYsaDSz>Evbh5W8P90b30RhH$_aw#B5jvi)>_ z!E$&V;H3d8f-yi0m2%Ej4NNS=VH!kRdtoVOL0w6HKFI4E@{BO zMVwDKU2$CHI#ALwT$Fx<(bH!&*?m7vv(9^&a)$&l?D@YqWEu?`R={S*i_$*ipNN#u5-cx;no980h+g8 zR>zspb0H#SXif2!(`kM?Pi$eme0yVd|4(Gy(B;>1PnR4+Et&C zhqbOy#oAiM1U`zn0Q+;)VGjBOrh#-=jqME60mJses|jYSnYT~MDoMM;cZz*&XFZBx zhp@)asgsuPy&jw|=4B*p+0~IBfE#>%r8I9nwIOtth1%uMzKQWT{r%SI*}N~v-<4K# zFJD^@4LAO|?gCKIg|6ZA*l=LyDwYrBpRJi2z+qQ*V*kXCp>Lbm$mVsjy%!~Ar>1zn zb!(w{^>k}$|nsIVjY9##>%sKH(Z(@{O!Z=6@JYD%o6^cD6z3Q^Y?ebh|y z95#BD&$W4U-ui#X;0n{_*j31JOPqN4L|kY0GD;PrlhaGx4%{c%#TP zX{P-|=(B~%op$$T$=E_!vV>6AlR!}y_h(a2GireJQMtEy4wT?LDw{fhr$`7Hl#0=8 zn^^0(q=U_ccQ=umuiajAFr+h)X+Uv^d%N2(=|j3_|1j4ADYp)J`MaxY-tJyLvY>Hu zuf0f=y==Eg^fT=wBAUOtY|4H_|EEX^Y5xzH3&)^M1V;=Ym0;EpBXOv*uL5&Ku(LZ+ zplnOg!Hr3nL75mT@WZx&Ok5WLcsXGtn_oK+DEFhbL$$=^pjYS_`ci92Q+UO%)_bAI zaHTUft~fFwZ07UxzqNyJh-{Z~2lq}_6y^V}PH3NqkY2EjL@GAU>L5e6G?Q*G)OdI? zG-yWr83B5VzO7*9c;7s&kW9~RwWH|>cJN|l4zD6yNLXIUrye<`mL>J+iBY9*&rnoF z?Pqz1$Q=vRR#$nRodzLWrF4>$D6fm0I=y8)%IeU<Z86)os zg_7W;a0!NOM0DfjNq^<{uL>?K7u51*@dT^6_l=`JEUt)x0Cvq%4)$?87kSeQkky_t#DN9b=o5Sdq95&;w@A*O z?)r3ZCEKiXk8aGHACDDlRuh||Rpr7s`)gBVFj>1j-E-Zb_K2LX$oI*6k5)dRykkwO zOM1=BJw{;+k8KSw4WLFL(nL1q>*S`0c&VBir=^B5jsPZO2IQo?OS^x3td0vvGrGI) zylDYSQkgCcXo=V*@vf5KSlp!9qHO z6Dj~P4E@I`sJDbVyJ%7e-1mNX+Due<+^Qqo`RJ!S{;kIp(zJYRKv;nv!Zxm!_~imrf{2&-W`a9w=1Fw4r3 zwzpLzWw5MyHn1C;2pga=Fct);L&ic)h_=AaQ8;C4DrqU{e0L}J={YmwA4F2=t_{1R z4}L`6lr_Ny^lHhB;m`K<0W5SQlwn#K@T`h@pS5zXT5UY|)3?~4$j9{-N$)K^x=)Gz zPObNp@X{tr)vOqhP7Wo@a$5#mhI0y@o|7MlpmgLZG8{Z51_^oM9x{M0lp_l-|Sv+el9-8HnvG2I~IsM`HEruhpzKP+^k~!Q{f(0&$Imt!1kRNv&dW9z4 zG3p8ROrykOKRXuMwV1E>32{;4G70p;m zF2zoRZmaLaUUVH#T8MbDH_iSYA`;C6Zl#Mpz(!~p%n=U4K|Cq=j6|i-esc|=tz4R(^40_c7bc+<_>SGw|5bUv@Xb}LIJ&fj$mWN&wvDK%IBCWq zEa@UL^jG0Zi(gj91C0R?Z=6Xsk2@#7~W;WOs_Ohnbn#hBMKI zZif~zv=_xx&@Kw*;)FrRdZv+$h`a1G|C#Jmu7gD3Q{Ma;gBPX8X(=}gmR+)w6}I+R{q8;Xy$qv2tH_MWJ^lmPUa#%W)#Ci*x96+x~H~#+-nlgQVOD>vG+afqRcm z2D@*)`=a!iLU_b2<ac5p~yQmCSCQ~IkxbWXnjzmf&t5=zN zq>jqkp*f*MX@zU0BZ)PU%Y*M>w-^}rN@GbrqgopKXG44*l$T!C?PD9s)cwUXihhRU zwU$O*8M18iIEXA=9ujdr{^5y$mbhduub00$9CnSEZXu$Dp3Dl(tja93l~$j{%H(=g z3{`hb?%GIyyYGIcHA2hCtQCOOMl#Hxui_f_ z%Iy$ilP+B|BxWQo_06qSO9EO#7XNn;k5?BF4OST_h>2z>#%$m6NY5j#;r{HrN%F|5 zra?WAJQoS9*13QjNcinN@w@b~{-hRH_y?%{qlzDJ{sJ?!_>+#2tT(O%k#dm514alBk zCD2H(Gl2A_Bkw6VGp%39OfiLr_l9;nXIB1GnpMM^5*QjWB~MP(JlH!t{9v%1a$0<_ zxp@VF*zqJs$>jBmC*i%;G7>QOPS>$aultKXzVxr%r=Fa9kRA;bi`!Bd&nkvA1_+Op z%u4Y^B2ICFr`uyBY>*_+bS)BNp-#AQe{LPF@=thCg7KRtSaxBvSW<<@;~m81Z(~9b z^slcBsf&VimZ?tdw&^5-o};ba3o^GmCoc1laE|G{=qs+yg=yZV1!XLLAfYM=S)tCEQi-rmrV(lZ4zgQJnRFx&8O4GPS;i5`OsQq!QvIHuY?p~D2C_rqz lY4I7uzvBOIA^CqT;1(^kQ7M}#Z7_sFZ74@*bc`)e`X8$o8+`x( delta 6595 zcma)hcT^Kw*ZxT+ga9Fg9-4$I2+D*e0!k<%2nbSCiq}v?6jTIM#4* z|J(3yS8LF zghj^0)g4cg{QYIsx%&3o9=(`HbmD3!j1rsrpZ;uBx?Tg@0h5HES}d%v&xtV z@HRL*;F-ni4DeRUAD)``PgMD}!rk57za{5i$y+(~!rxIKuKO)nST6L(iffu&DJb5?cULZE4Q$lP;3645x_F9?E z$JZf=4F6Z{Ra#owO6UK)zCZ$A9v_wYla(J|2j)7c7KZKFjv|xF1FW{B$7igsleg`q7R1N6 zDKky#Tr<9yXrx>xB&wmPSm{BWPpWZEt9`APZ5!q%+^0fs&eF*WPOO(QE7IOz7ALhlrf(p=es{9!h-5Lf>s)NW+laMQ*2?hb`Y?v5jW?wUo8?opmu zx|-KYLW-;7%JyAz8RhQzQNFL({F2Xds?)88OVWwjt)s~u(mTYc%IIXT2FI7W|Lk9` zhVfU@XoL06!hESTa_zxh5g z7!ajoSS9|20w6`6!vK(+TUuJX0O$ZHbqE??s#(M8w*2G*rJSDOC~E!g77=FGOhGX} zxg}V$zGayG^}Ol9V#C3ae2?v+$BiFYes2*jaV%?nck5|UXw2}YBU27Ee9;&=d^xoh zufUfCA^#Bp2ka0Si<5%uVn6-${cWU2Ii+u1&icMrQ;B!=Vckqhw471Q%PvGVzx5P znh{Ni<15SsK^@hO4Td~Xk@hFNj~V9s4dk3=WI88_Z06&-99o2!vCD^qS~3ttsU*@5 z{oUVhU&||rAlr4WGDLQ^YYI5P#sE~IYL#$&3`7M^&0V<(RAQ_t3Na{Yr2{k0zzr}H zT0=t*=EXB?yQgLD6*S}D-Y+@ki*>-!LDarM3;gdZxL$@~3%OI07hB8^-jDy0#rHU# z89A-FiA6WsR38!cf|T5~Li^jW!5M4yH?q>xDL z3aL;kI>G_&d~wf>1odIf!Juv?)t10!Y&V7Vi|^GfTR)`Hp@8b>6JuXr69R~Xc1ZE1 zbnSFVhyc|RY}mg#CwI&U^0UjlW0`S>L5g1rjoF#^`_iRRC$fyn&WQ147M=3GKfb*< z-f${tTu!4Sc_Y& zx3b<6_t;qf@Jd%+{~OObALa3L-&%_D)=Jo2?wZ-9-t;CoZva`^-h=%5bz~v%=2+@=r^V`T-7M1z{9u+j z0Lld*?aw;$7ZAG2ay)hP&*Uc0jS0ScYF@A_XD=pLM+6sG-p<5o52t1F?zM;9+q7zU zuH!TXp%U8cuqe>UHdBXS`szet2wSp1g1{{A@|2@2+M03`8~MBt-RH5%Ni=QspFy)q z-#S&3Ni5oCkM8mLgydwenW#dX=hj&%T^HTCY0sf+Z><1y@`D=1MtYXgsKFk~jn`qY zKN!;|a%ak8Bxmy!6fJA)X11lT%K2(Dk9b>-Pj1TPl|YG70TS^Wiq_w`V`p7gUY!>2 zfP71rH4gGpU20_By8vJjFbu~h0K?>dW7DdxbU5yQvFT)jCQF2{84hG$hP8kKEXgia zC-m~jfdq_LPhgO;K>>h@b$U=kb!zCFLk?XTV!5WG8+g^9Pr;(=$qaX-%TuZ?FQ*OK z%zXja240ou&WC#2ydsrDH@Rq!=^oqMl^9&Avrfz4aM+f_vsVdd zveSAqb!9N$*^9DmXjrSa`W&z5^~JpPitF`kdaD$(`aJA3)dJm%7#FkBH@Y{CRAjwk zj4p&VRwjr@d)3{WYRSiuMI8T%8~7I0Y#AxhyoT2%XAB-Xj9(2hR&fAS25n4_A+9dN z_@HZ3x>u*4;T~M7B;y=k;B0+`A7twg&qF)2cRqy)CFJ9@Pkuuzqe$4W=a`a1qnKGli?+=M; zf$;Gl`?y7?@INFHutqvrp62@D;(tiwVPUSaqo6uU!=XB2UA($pV{!TUh1?ekl^$zU zGvY)ZD%XGQWlpX&JKKtnWUCRv5jdE;0p_mPWCd1hncrk!uypq~%jWxLtok-z_$XQf zo3anWD(rNahH{9pG>#B3MU4OhR5;jHjp--v3u}@)_DIWVCUnnmy zqjpIM+s<;v_)VXsa%XS*F#7%7%bSHKb#62Zsf=95D++siQznkxv1q2uHsJeagCBZ- zmHDKm@(7=u4IUjRTJ;vvc(hRawt81`4)qj#lY0%$f}R^n5JT7AKP3#eq(s6GBrujE zHw8Wv>jP_`!m@-61Pdb!V(vs8%n!(5M6mucLr|ufOvotL?rW++_p(=k5pY0f55FyN zodqREllm^iFf_7e&z+(0RN44%`wTW}U-mLJ$iJ)4l_UE-zTLRp13XwTR$4iO?&a772o-yU;mxuv-j5j78&HG!|l+5-hakb zz9C6eb^#TPuu{7~JKn&5BA%bF+3&|WH|k%v{^tIgKPMQ#Bm^QpxyCeJk}88JB?B1C zfcFd%-Gk?rfU;9{c>12!8`_iDS zgWp%WB3GY8S=}leLM~q@U260-_?_2-@4WizN6VR?$d6l#7mhA2UdKPhax?e4DVw;~ z$0qGcr(8TAuX2NH@iKnBrk7Ku%W31%zw~!mOB=s;9q_4RQ5#lGw(N2-K1o3t)#Ehh(fi; zzZC+xEyG4oB@z^+DD<8|AmdE{DhntvmC1x!VW8<$`K>z4S$gM0Pm{*avyTpU%*m3!$?y9 zpdc}IROLbdf$^nMk#S?phat=KVGuad1rn%3f#{f$IPl{L_@E3P>`T2(1d`JC*&A^D8g#xCuX67lQQ_{Odoo-Xd*7oMok46U+ux?iMSVOVB25}udl zT%v90Fmf|?%Kq8D9wQUSXFilVugs0PTO5mCeRNT2;HN!|f9QImzF9Bz@Y3~=rt8T2 z!#D63vu-MooxgR`JCQ3#Sp%};ueLrII>oB8IgqMRn8MaYc(2#QsvSsa_vd2CNovU` zCo{pC4nt;A%KDEqbHD*E+?iyc?x)5*t$tMAKO^$`T1JCo-=kKY^4tfukpmMAc&dz|RdnZ={~|q)Y-p#;^!JRjBqNwOKX?F~ zrV(MP!Q2!ZO90!buWDlv3{wIq71RpF6Cq*@l^jCz-FPdBdASGs9fxwa+qi}|qM9my>E5&f{ReN0L- z@2YN#Sj^j@G93@+Gn(n*-GT8lg_7=}dX)BbpPVp5LWdhMp)Xr`Rk!u8Ijc&y)QT(Lh$qE`-)>kx36}&CA6yeMyQETpS#nG z;ETSR`7M208XXM;1?dt@#0+5wz%aM7i{(vFXL)Q`TJyR;n)PbEA^h3q3mj|h4qL{q zhFu6EumJ)PSRS>a0fI&>lV~myVPX%vmd!+|)yW_w-G^qDwT*tfG-Y+TYNDY&%%*eL z{*Xkx(0W&k(G@)sjMWiO@JT5F&x1H*!0{JKP7An7vAAq$DM9|P6DItL z+)B<7;yz9hQA$unE^!RN31vXsKxC7mq>za{X`C~*L({eaI{2T3$Pa}OWh_asRoVZh zfd1(wpiz#^OMKL|Zj(4e_+6!Ak@InAZQP}2zWpaYIo%Hpew>h8o%TVMBAQZqxW+4Q z8NBapJ>IK{DS}SSi8zgg%FK7Yhnm{PZ7PSs>U*6w;`&&#kQ*jVq+%|>4|z)HU(a|R z-!A4A%@JXzZY**&jTi~4Pg6U{e*CaRfnK`POFH)~sT$=EFA52-WU`A!lK7H!KsR7o!xi3K|%iwWxJcH$u4`l<%M2$Z+ExL=Q_+@6OrG`{Qs>e>3;}GT_`nhaozIIWJ_M=Br%-q`8-l1y`otIj0w0nE9cpRbCOE*By}6xTl8`5!dV5|F5?SVK1baDWzxvW@b`buTr>N6SJVn$&eZRRo9CzS2S4#| z2tLSxfSfW>gnOC(xv*VXE!kKr8Oz9b!Utp+=gHHajeVKPJ279qt~Kn`KDU#T~x-#|LT(gb}X;6uUU ztfDNscwCa~$}Fkk8;4*ulqmu=oX`lczx?<}dogdMVb67+@!K`#vam%&CwqRH9i|GM zy-&Dr>REFP!9Sey>oyAakoqh#y85&Gdn0|J=p=l*Vb2wOhdYkHR|n@XDFP3Rr6bqc z5`_ob)O4^RC81*-2Bdkcn;>8=Ky|>AIOpKXc!)E8$0;bjdiF&cg+n2-Kknc?&b-*T zEWG-)$x5F~qfPrS(h{6$&+F*x%;`$P*vuDpgiEfTsw)0-g}h(E+TK@nYa8R5 zg_y{&wnpRlnePQxoj;AsReG{;c?aDbbFB@(!$&t)A-_!CBEN5+!jb)aEJL(c%v$G; z&cY^+(6_F5*ZzSix6WRlUJb%LHX`3E#_pA&B|=QO@MIL^w@Hs4%@&8%N2wxGqSJW? z-SwQSRc^UP-nzRL|92Q}D3CZWpwf=i@2s-`Gi4~=QVfEX*%XHHv)kbc~r5Y#eN&M%u|F1}rNw zSQn6jL>FrQEc8xC@Pgp^*ice?{QC6+xZ5HAyMcfyyrb#xD88if6JPj*Kgon;NHAwL zOXZj4F?%>{w_%1aT;rf7jB!>=uolbxu5x;@dXTH#F~@r{4J{(^mK$p6wT*jjM2=p<$yAk4{KkGo=x zts{=k9h^2Jj%mK6aoSNoQgK@^!%g6KT1}JNAshEXEZ5UlCrgaFMIvgI*XFQu$&@W9 zjhB%S0>ui`qtJkqsbyy1tpUt1|$E0vp0Z!o0SJySREd17zrDVKRR= z4E}A<>DIp*Cckb1U=TqyE~LZb1Ki=-{bGceD<{}u*BKh86L#$pQptT4i#74Y=mYJuS2`j6e3Mpr-1u=Z2O z2OArw$9&o&J3i%Fvd`?2UQ^;Il-(Jn3DwihFy{amAb^t6=iIas)v=q+yXcGWDyx+o z1X-uzLZ#IG;McE4;!*LUna*hK&Q~s7*|FV<>D>m2&X0n- zY?gP{sCGKyEE#VSihPpD{hUD`&R|0VyPrF_(@bJ7rw-9)dgq1E8QnyB8mcUw>iK%t zf!p%$f53t-hxdNb3ZI$DInaYt9}znaxFC!czM6oc1E3rd@w!h4EfU*;49bh-;f{{4bvqBNl0J}!Gpq*1W1GvP>lop7c4};avWF8>M) zczUq1f8YmMxs)cJsWw5I5MHXKWAWs!kux3tB>StgTvq8jd)~&4o}bFgo#~fvVYPzC zLo?_6BLEPSN~TGXgNO~*JA`Mh0KR7!ZarvZmK8@1(W+6wNEGd0Z~jp?j3M$Z7;JlZ7!xY+g4xn=4<>-&KByuF z)6i}^7y?(2g$H>cL$CrElbaPT*@~-xp~$wY3|k|&QLLV-xCIuqVjiUClQj+Zu+psR zFz@A|X&Lg1$zcEB1B9XO05d6$Aat4H<7?qBM=5%#&7qezu3oSW_O-JzixmMEXl^XD#@KAx$>Zk<-{^_E|K!Y_U?4ET^!Q+4-5+`>S_Y z<2)5b0wgV+Y{uS5EY=73Wbdq46wzyF6?8#0Gkd^9ZLo$P;G!2q76X< ziv-d*0ANeO;s}sx21dQP+~xJLv~0bG;GF&;{J=b}TFa}r0NNKp_kbGmlo z-ocXRAxCc4sg!*Psb8C68kZjT`KECH&TTb2yUxUV8ovKF`M6|7{HvQQ;zgUDyXovZ zZd-m&ttbi6d{=ut1E5O+J{+8Kp>mPO|j9jB=cYG@uQPQ?b_-POz z&=cfb19TjgoeOI2P#w@^yxC~9ly{tSaF0S4i7`UGWGtk zHytP$V~v7zAWvj8$QFm}ku`c*i}T9*GY|Sw7&^cqw?}n{-Y6urBQ25N48oUe>rR)RP~>8^fXj5UTuG(s>@ zmShqh0|QBtII<&`ImAR^SILDL1u>gDms}5Fb(Z-MEyEs126oojj-j)KgWnFPy-JP`*xa0RVoRQkG$ys!r!r=z?7MJN-%`AD z660pr8|k#v(mMq~Qrx4hw_-w^ECi*39t=m|#SX-d;Q3Ux6wrncg!(69=tVu4@RL$f ze8`UXzIhAvW>aldIJFX=qWOGc)Zze>>!X!dT!XhJrf&{0-+NU-^5ciE>^tzbvF+3IWmb*vdoZYq*9^A1d^-k( zjfR7G`;#%q3K>RV6yX4d)U$-amWK^7H2z%v$%4_@j%}*D0fb{hJA7stu&~>bGCO~P zA?+I0x)FdZ%%%YVX2GDcfUO4H!~m}WK3fr1#sC5gUmW2^5+f5pcne~uo!x>6K5YxJ z2!XI!;K|$f9;f-6`Pz2Ws_ew6duM}FmVX>cg|S4n~etY zwMBSIbYKnpNl1_Ty`y#~?>mQm@ZGYd07uC$y1)2vtW9Rfg~QVisurKRda3c(i6nYE z*AUqGORVB<=rL$#h!F*WSU;M$QyrSq%+;}vs<^L-3JbD>jf$EX_pJu;CoY$$u{LeG z^E&8>tMSUqlvjH9MDKnd?fF`Lv9nNK27rni%W>SZd+VjOv&cqe zDPJ6esE%ciTidVX2B)URGTZw#YYzVcJ%ge6P=MgRv{np2Ljoya2qzVCt{{>4O+;Y2 z8i7s0gM2({4(eN=|I#efur`3*gV7xqIx!?b3firi+dT#Xq1iiy3 z)f+GZoi&(HF9cAM_A8Mn{K^1F+>4drjm<*|-coi$vOR602|iQa(CI(Nb0Bo2Of>VN zgKm@hTtU5!dV4M)z1C7!#mpcLnK!YsusfahcPvg%cPzW_G~3!}ci8cgmd%UYw)MY4 z`hLX4_L{^I^sx=v0j0_1ky3dPRJ2&>fI7^1T~EL+xrDq9AQ@IKvn>^+86av*%n*eL z@W8;C$t*1#E#O2VuLGl=%(`t2H=Zs`Egl?S_ab?@w;&FewcxvwdShlcs^=48(Uf&< zlNqEfn~ZvaY;6HY_aV9^@CT+>o}@|hj8fYlHbuj=ou%*TynEg1ccoA;f2@!R5tb_= zK=e1pPCgKd^*47w;0gcBG+HRvEoG7gD}glV`8po3GduACgo?&uV6DlB5+Dl>{~Y_F%B zPoz+>A#FzjEIWg$uu?gQX8+LMt?9f`vJ|%>9U2@T{AM)dw&9uQlLL!P2{@LZa zW%AnDGX{-w`Ns>!=s8#;2X@V_y?(C06dA)a-cxgHt_96702FLn?|t!U$@1%;6cjp_ zGtT=N8LcmQ^i%E6Zb+U%Rq8Ifu10}zX~S9iXXkiBPv(6{uZaci-Cj;06a%45Q4Z`RRvoj%s(e&_L7`IDQ~JRX12Ts<^!FWbxTiF4DP zs@PAf53PS`LciSNb@B34dCTR7FuuFcoqf}5Y~O~UwIP?M56xn)gOOTaY+$IJDMT(; zjb<3HN=QQzBrADzV&WPk5K8EQ0XF0&=m!N_9PVDE5C`z&05knTNJ(IfXJFx#ARkyY zKeB$H;XQzh3IIwDO_f6Ul9&+0^O~Q;msEz6b}d zK5!m${=EZZ+i~$6%#z0+*jw=Uj~KApC;&hR*yG#+5Ln4c=*BQe0AC$J#&Z}LSa1T| zGW$Rt!B`5rA&aY3NJvMpIU!kz6GTQFMseU0LA9_(Jybr}|BNFKY95-)KgBTSANXsBypEglT>`JQ@(@hC(jhzRaB1& zl~~rdTQ^_Uw)Lid_v2^@>uOFR(U8QUXCHQQ{%1#ST4I003b$o0)ZmdtL7MTeTnn?& zcJEXIW0VpzZPIg%iuvZlk3?UJ)4T54qZGm~D4PUr&{;gEu*gH}-A{I?++LU0h7!)< z3HG}VbmIULCD&8BNDfaq2n&Ij8RuY?%6`-bP`9_`|ZtY_D3a=x{z=djL|@jPdu z{SnT+qXJq}uFwPtqMB3GoKPS$0+B8uuoJi;Z#`wpAW9h~6;G13Zs@pXh?v6Gi}kU5 zWaNNj2@G;6V_yQhdaPB=887ew>N|!9Z)XobUvd~tUfMB+cjXyQGMluV`#Wl58IQ}~ z<`YS;KdFMCwV7$?J+w``X<08>Fia+<3J9)cC%3o&{bN52>Qqh{DRCSPA8MBw zqNvwu>#L8vy0|}JZ(yUf*xmRioUphC@9sJOKrjkeVvUX^1AA;(5og(e+&W8oTo{bF zVy7j&m;s1xZhHD7-&ge1B;Lm}UZg8}=~oj=(^<5*&SvUc%{z!K*&>VSG^#y2ifL9i zP$Cc@<2*E%z)~VOC8GgxGl0UN32&juSVdrPPSAc$Jpaz_b-2otQDO)z4vpKknZqCY3qz;le0L*TpfX5M27~PwM1t+!!UeE?w1#$=g#^d2Zalp3JY4)m7eLcW0ChMfi ziV-DTk>d5N44pJ5eET^(8P*~t9qOFDjnJN);GURz*V?AM2Gz=I-zeT3K|X=fG&a42 zCA(;)DsGo9dh2rO?3YNM%ld4+h^hM@tKuS$ZnAvceN}tr*7g$@Fn;&_w$whGg<16< zQ0E%6^Nj>wAOWZdOn|B8Qk;M5UVLzmrv$e7E-tjm4?$+3OMpSlT@VEZhfu+}`AZQ% zL$>Y5_p$+JjXvSWD}bE3fr9rW07m9Ncev66dI@v^O|f7p*&GqRCqXbsXy_fVPC}{i z_E<>*#VHg^S6OR4<@yEn#&#bzWhAqVBXUx7Uefxmm@`TH72Si=Q;T=@FSfNAj-#F_ z-GBW>mZLp2tEx0>ZElv!WUr0(_tfp077yBsGkj^q2T#iqyg#BDW{2*{LnI9Y-a&&^ zXN>n`8KO*}W$3jPn{~pQ0Sr1z`_QR(mozdwj+&R)CK0l(ZDu8~Bu;J*@tbUETpT%y zF}>3F&<>-8hU?hYw(g+zACRaS$5Hex3HYRf}f6KTzXjq6^9%&IR7U z=Mfwj5Ab?GTR@;x4%CBuHbTGsiL4Jz%jzkHN&ZDYdH9#iLw4*v|4+lQB``o8t{8jp278~f(;c6E=_ zmJ;Rp#aJ>|azlB+hEXGW%Y9e@ARs%Da7qvPd?%TaA5ROi*d6Rl+KDM9KCtg!_P$TU z=6X`v#_PT54h2+5+k$C%P2~lxV+kjmzIMMW zIFtTeBk~v@Ad}^0FGuiFgaqJ5woDIRlB^H6Mj%7+7L+l#H66VLFUzAgRRDwRt$-Z{ zWFh%%IS_$lP_Xp{AiNi3kLiCZ2D5pr#mk@1avgABl6_pIw`VNRLrO5CF+{Z?zZNjvQX-u(Y`w!j9 z8bWJ3zNleO+O1t`7^)~KG4iH{p6N~R!uyR?*~btpjg|5W59g$Xvib^4MAImxmEU|T z27WG`=1!D%{qeK6`09}IM-UTWvLio`SdY?Sw7xC6ZAVj(W&32x#LAW6Q}f$FOGs8$ z<%JtoLB829DiT{G@!gzqtfl#;{q*{qBO=koxugC6${+wnkgRx?W&X=llYcZEVLOnV z_Vsf}OG!((U=oW=|LTDOh;G;=hD5`codm#z26KWLGBCa=H*FXJ2XMxc&nOv0?4u|e zYRVMFj9ivkX5W(UbH%~y1G_ed=XF*f$|TDN5RQJ2!J%N70F16f&) zHnQF;?ycU`aqf}rc^^4<&jpLKH`J9~aP+=SzlGN8$Ti=|032heVH{MsxI*4P%-tLM z=*s5?xPB|TIF^|gddrX8tZqI8uAm=wl&Hu84J3F+Lr>jv#K?8HWMyep+`rQIc14}z zTW0TiGe_NR`mye(YRx}>SPMQrH*2&2o*kqmb=NND4DQMPfB;tmGcKQ_=&LVDP%xZ4=y zT=&aOn+#e^Jbp12ycMPTZCEVPs@cjN3ii?=5_0wYiIy$0fcv`;8o4>R!&m0?J4o2f9e2Yk2Wg9XgTI3K;v#7H;QP!mf zyWzyPxN17dmMpAp?R+&Y|1OF@QXRzG`;iXd@tA7gbaEiYS&RF?Y1z>6bQ9rXJeDul zi6VgMNC9RQyig24UFszl9!=TBz71iC-Tbiqd6~mk!?$wwScdyws9TwOWTWFJ)=V9j zYG601qcjtENV>SVYH!N5w3J3s9y3g}qIBi!5)7_f5b%ca`L`dZDnK@gj3A}$E|lpg zypyw#_9l-kO*u-V;e5Nu!C`6_bzX zyb&X!i6GvI61WN6!1as>>9dT587=L3%ec{!1=&3-S9cw(x+H4+@f+priMYSG`110B z1JsoyjMVS$i5Nfyk82{e;nTa6NQ2!+kQx6Vh0R_IlHfkUJxnv;Z8hf~27vf#FIaF( zSD3k~K5DnLzf_s-TlsMSoGuIMt`~qe`D(|t?wCvS$fYQ)fdl$47MLu%YR_!QrK#US zhDixBiSk(v1xl2m@PCXk zw{J@(p6tDP_Q>0TXQv$F3*$2$PC1Sjv|Zho_CRXrJ`rLwn2=2w_w>q}Q@ExWMXN-6ad)g@;-DQ80i@fc zL`5efd=`)bUW9Y&pY*41)_xQIbLBEe(WK&3$#1I0U|ej3ylS52xvk-0iFe9N)${b| zzUKf$U`_6^yX?I5<=)#3Q}#twFW2otUO+26+7pw6JN$M=JHW-G*Lv%&{KpS&tnYk( zaox5jclY+^Pi`Fzi5`4%{!wHe<=L6l_a`pC(--Ytu=m>%$2;2`YZ}hqdO_b1WWM2* zcbp8hib}c!AsE*|t`%rTk`5Gdvf5PoNGrK0PKn+!A&CsQg0L-6Pg=;%fq{j5HW#SC zfI?+HTNZXfGNd{10R$iq2$Aq|iAp3Xk-;KkR)>q@@o5TZ1WYF&6)}h*MpHS@&;q{z z!$}ky3_J(;MzAK3i0dRg(+vTrTY_8%%XHiN`q@m<)TbdZF!Neas!&0cVCrIIq>{$v z1XOV{GxWI*FLqI)WkB>Q1_OImI(SF%oPYdaoI1}Yc^w~E=Yb>NJOaot8pKtn0dO~| zWv9m*&hS2&kO zJt!DgROT>CUm~|l#d%Zyk=ZV*5oWm7&?He+IShra7dH_vHzb@{2W%7>!H z(g8n@UeQ^RADDcA@2i4wSq+Buk#+zWg0y%Yw3z5yhnA*XE zD#;{^%3Z2$CsbZXm&L~2a4EX?!+4FwLD9qCw)ns65#$6BgPPDuR+_lp=GHM``yTpH zZY0RvD92Xch+t#d&@9ztRF7mi2!L$1JRTc?%xM^6c}IQ40(lZz?42atm~KyN=l*~4 z$)O;tTJPi`N<>d^6KDG&dZn_ZVUTg(nW_yJvkD%VJwE_R>YIJLTxGhr_Mmx&AH9Bb zeDdl+^Jmk(m>R-%q1V#$ex9E#1#&O@Xhlbxl-Ha*qm~Do39YUrF6+72F_ZIlW8f7& zJ&qIPV$!8O)SFRfTyC=KKUFSLtc1T7MDK}be0ga#y9@jw`i zL6PyJcYIBiR!~o~+awlAy=A^SZr5o-R(smLuKQ)$4j9~>=_lNQ3Q)OIM|(LpLz6(P;8Y8urbgYV^c28ZaHow=*>Iblsr+jP&R zf1bTR-Y+s0_2!J;K4FbDfRBJUm<)X8x~b=M!eX}=}*@@tUiFz=Lt1j-+i~ee^g*M>^1SI@Q{(QpTt$a9Tb=6hcn(^{Q5jw zL-n;gTs%V=x~0_|dM$0^nCEm#W97_Q(ey5_l0*0L5CBHwKC_^!=A-LFvzPkg?yB6u zL$Tu72)?caaFT)3#11MdRw&yaGLE8jrMI>ATA4e?UCC3vZ2%tWTz@2s>G^rh3a|c| zSl>ebTaAx55SMcG?45`Ta;|9PwDmq$uSHYM$34$Y6R&0b5Usz$boBd=9C-?uy-!Fc zlNP3a&S-YaPgGJ**l9w-b9`VnN)|N2S}MT;@gqetN&W+^&k=@JbQ_P*?fZ0Zm7*tI z+$*oYJ(A?beF9XMi#Jj%%HBignq6Y9ZkKHylYMR*Omy0+P(GHMnZUcV=^Z7PJWZ*b zPWPPZGcPsj4m|i^sxOGeM#~}%xRJ7XJ@s)z!%_QCuNnH1Ad5{X^$Ed?G3r0g`NwtE zKZYxi1DgipM^;tucJ`MVQvM?K;$72((mKoc7a}rXkEHAsygs5+O?v9yYH2C) zWNRNQMEg^pi=46~yQ;Ey$?$^Fx_scVwqi*3Lc|@SXKc(JhMz=`gD$fx*-HO{6E-n= z$3HTcu6FXa6(o;|HB7A?I0{>T4+vQN#K3@Mw#wdk%|U~{QwB^6tKwUBN3V3&Z8R>_&a^t`L+9oD<_Rev9LgdMrx2QD z%}1LtBa6?R*rsp{j~(hphe&VNcQ-vRoxmQqL@}uZk!RfJh(T4}Gqhby&-H7Jz!C-2!5n<4=8%F%#L(oTD zkW`N{Iwo8;D2U-iJ{MTZjQb|DAN4u^ z1Oq6fLw!P@#h1;f5XNq42Z;{dr_zItP1{QrG)>`l8W(AgY8=`=)>?V{!DvcP;3v`G z^@*IN6<=AowU@3wo4DKHi2?dhS|A%J7aNpJHf?s<2-#`2;b6nt-(d>i#*^6a4gmmE zfUrH0=22SUw(+H2;UUZ1(rC}ap{EBP#Bcm$c>2t@&(B1rQ#bZ3*>G_tt+R3Z-??YZ z_#dn49yv0gnoe>H=v*}#>5Sq}IiVp&C%Q+$2{nj<0r4aOXzQi363>2TBjY7Ml-YjF zXFXNcli2LFHhpz4 zs!8bM6oNSa`4M$Ae9QL%oqW&kr|;Hm0C7y4hBYgHBAQWs!C_F?8wuArPmJoHdRh(E zLZ78uG}3zsz&^{EAxAez0?1_d-{XQzE4Qz9qyNV7nMTrv04XRX%eY0;aN@mT4*_aj zqJZlJkYZdTjm%(#vA-Y20gjEMJSn5^T;tbsOU+dEIp+z7Efq?z!ts*3WfV;q?2IfE zo{h4dV!)1jQRd_{iSWF&7Yv-%F&zm=y9_dOr)(-APUd>PUB(Kfj%8n$wG3=1kH?E_ zk$eoBfV4${jG&5R>NqdyU=mASKh@AH_0Wcmxw}Y|0%c~l!|=ce+6piI&zghokw`is<0N3XwRM3~`6zv1BR7P+`iW|30^kc#Q zc#MBCfPei1To=2Tplp|=#QrJ_vX`+&sXWEAt?Ysl3Y*fg_J1;dmj!+Zu&xBRtB1>d z!ha0awNke3lgrT(7bo2o$%+$n?ygufcwFaK;oU4+d@GSY%vp&}B&$iqJc zQUq8c$4{W1_ebX+wc#^=HUED*_+S1J1LoeJpM8v{AVkfEL`FQFa}5IU6A=FF2qXS% zc>n9s{qheAxHx+YXSV!p|G)TOFVG(z;4l9GKl9JLfBtZ_{ycc|{`vDX`I`{U`{!>` z>(9eF@1H+UlfMbkynp^Cwf;P;^ZxnsH2Iql&HLwXQtQvdI`5x9Pm{k1(Y$~DCbj-N vtn>c)^ECOJ5Y7ANZ&K^e!#eMuKTngt3DLZN{wB5lJgoEn`SUdSn-KjUur8C! delta 6762 zcmYj$2Uru!_x5Zy2?+!UJs@DH0#{&Z3WySVSE^D(LKX3H1;wuHhTharL_k!OB1NTH z5DQBYQBaDAz_p+#C^l47L}0&g@BROtZ=cNMWarGDb7tN-`<^FwCxj0}d<$;_dqXy| zbp21TvA6#B&i@1h#PmO%{{&;x)&J`JPeA^Eoov3Ynu*=NQ~r52f>_xZ{AWjSZ}5?sm-n<8LK!SOMf1FYT^63G{f}`Eb8*gg;tQBRxcNDLcem+^4RMT zKm8W17+DEV9eehxHD7M*R!LPUHn|(yjlTSa##A|lJV#SlyS|@6Upld}>^1W8zkQO< zN$Y6HIeli`dj`_plI-m4qWH}D-^na`K)SwUeiKE^gO02^2p` zt#g#0?%2As>PV|5Lq)z`F)BR!qD)G$wR{Obgv)#>`f!!j2z>R}bIX`7p*csjjq(^K z`spiGLo`)80{_~4yL5KZGCMu&_WW7g*Uqtum6-Oq^W$KOVoJ;trMtRB>YD|DpPBm? zS;4(SXtH{U0W04#pts4>Sxxf3GkoCC?_AtQX1mgL&V)=45V@wM*mfO*E+Fk$kaSt=?R6Bn!slB)u7P?%H(@sZTlz3|G&e$0TB&{% z6lP*14&sdOhQU5k9U_dwFmQwsX~>uYf*|$^2s;2Gt=%a&U}73)J@MXIUa zaub`(NHNjUcX}#Lq$b3jHc+)WbYwXhpHfP?&agnu&PmXFtUG+)WoI{Ar#aL&NERZz zZ(WVcmYau#bGn)i;e4ko$5<)VyBo{$kINagSgGsve8{}$r0b-4T*$-2GVe%tx6j;a z|D2%j>?z}*8P_8{`L&;}Xe_?pL~I&Nto%xNnHQp?6+yQ5jPq;XY32|EZW^;J88(BL zv`z$Vk`J}dg3_dA5&4nwEzpT1MgdC{mxXv4a0yF_e)HUk}@%u6d2SOM5R@5yR_})NcclQCYu$V=qpBU70 zbb0&jX)qvdz4iz{?_QGlX}f~qUysyusCMlW2rBymqy$r5K*3a-YWGU-{QX^|`#P_4&g z&ZVPWn2_F@04HgoQ|*^;l!?OIY;){aZ73MM8tdl&_k(xQ;qw-EZ#JW8DqMUrsA^u% zbu-o556(h+D}o4uKv6*#L@Y#z`R=1C9*EtVTc2sq!V@FtTy0IdyM`udOd|$HQ91N^pk)WM zj=QUNa8XWE57t@9>$IGFq%SbnB51Y5VjVJs?Lm=NNMdb7f~?^mDV@kbf;AEY3827P zK(ufaQO6w+1|EQja}1QhydZaw$-vX(tjWu*WV=+QPVV=rjy0ry8?U&bFV||We-;Z* zsjqrIdCG5h^NqH>ziO`4i&8cmIGej&ER&zn{J^r;MCJz3>#<-daDX66+Wnznw4F@0 z{a!@#?Ygv#)$UBEwc=!7JRF|EOQCr@7gEHHpg-}opkp8WO>l0ROvrI4=}B_2gskFT z`NW~~%nu@VqHd1@mM|0!;2+93MR303+lN8z7?7{F5|cjwU=v4`c6|=$e12*2rdhs1 z=lO%DcZUy6eWt&dKrcP-FqT<6mEv~6T+dqPy)j`ixbZWw)?wx!9~Utr^9`2pa;CDo z;U1}aSUF3n&v2tc6f9o03P(ISnoL)QBv=8|0fvw`3ow`$j%iHDqX;r=1eL;~bGeEj zPTON^5-tbu92l#-yeiMOLp^1b+QcL$ud6tKCWcBCt}1%Sz9U_5+jm*W8mWTUOpW@* zdj&Dw6b~xXu=%i~;rl&qUWOxbf1F^XX%KO1NnHnSp92v{NuTxt9Yv(*_nR(U31~TxZ@-ra1xM7e zF&$meatQ;v%2nPaGNiJh>U7C)$Anu&W14}TrhYzk_-Mz38JYek+7O{=*y?SS#JF_E zlhOPv*SqJc-TgQ1s&o{6CLSosx&2mYwdxcx^?0(+!}N~rGZ%uWS?K4N}7+!cWGnd2mW zkS#zPb$Sc{xjR0FJGjv}0LugoAfML_yqgo={%c@-6K$}kBHu|dwPb(XRUe^tpB15( zFE`h?xEz%ae=*eZvt=mYuEJy8fNsUwp| zJx;jdM+x2}3Qq=T6pY147bs)f5Wo_{@&UiJ9*}U*9p2sk*yprg7X+-{x!DeD%)DZ!7_vDb~i!rvFTgSbvo6 zYXy~uBxq|Kh+RwjX_sQZ;SUM(jcP9U_xHX$*!yk8@w8J=ibuzC+A(8xQ&VNys+Zp2 zjvw*GTRw<*Rf)J+(-6Lug7LD%NaHQNn~?;oVI@*AE)Nakn;hW}K2U&k#V`F$16A~{WN92mavxD1Q%|E?1exNgAj z&jy98Z@au^_%nvSwielSw!NyFt<3E{(hYUK$5lK`_!-=EvACj&zYH-xqL_2!2^7XZ+=+}|xsCwHcI?!%nYM-?1{vZM`KSRt{ zV`>yv`&=beLz~|`p44ZNmPhiKyAfRWMH3h}Xxo?- zTJy{2x`WHf4}>B51iHIby#4YE-TjC6NWb=f|LU`isfQGNa>MPAq5HDpHrTuZEuk%D z%U3BOxpbRXvE75Q#Lo|%{8uT+^x_vE>XdC*vu+hrg;t|C)`kPd-e9V|N zgHmFgB{C+O1&=LfP;e2xBo_b+E)BEpwdK&LAS`{15l1}=W%#Sm)VqSIWcP$NOx=+u zVXSpwN?ZG}M^WZvTCkxRytyMv>`jh3mQ>h4S}X1h3WR(>+lTUW@Fek2& zYiLQi1dD8u;TzEsx?$1>$!S$E%`zJ+;bALDOqMif8-(cyXdYBT12`Ca+J^|r2~a`? z7{IO~I_#DE^ruy2-|Tu*vQjaF&xuy42tbVdB46ov)LWRO)vq(~HcC+^U-*l2uTww! zZ|!hZw0UK7TH`I3oo)7S8Z|2GIK zD7#bX@HR;8lha8_-1yI3_FP~?pFbf8Ni|klOfLQ9dUl7a+j{y~$4=)vUrwkpHe~&{ z?x}K+-ZK#yCym{Ej&LZdE`J(+Z43Q^;g+&g%g@2$QLYo|7C-COvNWC#4&iPj8omcy7BCJ+K{}CG(+}x7aD)&coJInYpcUwL$onl(#fN zgrkiM>YkS2LY6)6WaE$hzMN_4urTVqu>9vXuNiYS8F~7M8r@ROm8iRd z7*JK&ACSYeU6e5iBvi-~J7+qSXnb-cboc?OEm?CL$j()^ZSF)QwA>})ENCDr`K{0H zP=7}DBi_rjTh6ZGyS}Eko2dFtV+ZIbQJSzV<(z*=@S)TEFDm0Z>ZQ;P#V22w&%d}( zJv-zRzGp;ms!JO@$0K~UJgmQ%{|?n1pZ&J-+Wo=NmLs~|yBeyu=MFZF zTs&=lH}Ki1@SCV|c>b6BCj&e4-&`NvzG+3(p{+y}Lv?r1Vi}VDg6QkOU$;Hgh*bl)XO=fH=C_@<*{0#}12$b5)cK zSD(8_RtB1ZWnmt}rF@S?(Upsjda^tZ z8_P#o3oWEydU?^WVXzzO|GRn{NP}dzfJ%xOS zz7b6)bqCIG?QAs3I3wH0v#O}Fu;TDiBFe`o27FG+77lo3C^WWIYZoWXF4fwL%;!tB zwu!Ty^cgp@m#KePmRmNm^5S;Wko0EznvFMts^skS{j1l8*ZB8{)qX!$7t%QW{T_48qqZ^!_4o~5}Rut~QuR;a56Qqvo zfoa?nxQ{a}ZTQh=%g#x{SjmP*`tcoHhZT=6ACwQZ&3Rx~GeSJMIH?{bRNBvGhrb&OiB!E_Q!J%(5(aRQ#We{e|>vWF8LzlJpi3I^G+O&i`ca#t8#b+}*%_}21 zPmNL|RQVfIL}ryWDRqoak|SLciV;eIa8fFZH>a2wEvmufD^f`Dg$IAAk<8XzEFJDv zP|uIj-O{^wYloSd`&#AnB@9JkWQx8;;;bPTE1%?}R97ag>4W%7dIBV;#G6+^0C#7a z#s+SCIfH|qkKRiVzyEBXEU1ZmF*8<$B@^n-q;)&}K5ZhVP-3n{;N@HvoiCI{>yu`r zG7a1)Ry_>0n5-UAjxK}ZuHZ`Ih-X35jC)vw;>*X59m!J37lI>&-3FvJPlie)LoWX7 zd#Y5b51Kxk{PF#`?>19&vE(~!3nn+x)wiF4nw#GreJ`*vQuxU3_1RdX5rSIeQSDpQ zJ6l%jBTFpSphOL^SV{_m#!1E$SfzvEW#zsLrae_Ng55=IAa80e;KSqK5NybmPc_}` zX`o{Jay!ePlpB{!uNdwK=6DX}HtIM=oqo7_|H7^CjSB0<63@muij!FiQnJRVjo8tK ztIYxjeniCe@7LPw`U7pP$cxI?Y+TgVdTiH1RKFmm2%-{67cM19??U3t6K}cKAY3Jw zTvv)vspC-H3WNk;2+$-44yH54t~*Osn_S+f$GgxIc(gVgJ27tZ*W#MV_rHjyg5cN~ z|5TMQVbaS9MY}IA1Xj^rv5AjfwAiyR=GX5fm$uA*@+XKj=ZT-&h=oyCRqIMTS+Pe+ ztmLz|P7h0n)4EctM?jqLKj}RJ%7=4E2iNn_1U3O29gEUvh&G_fy+~yK{nfTyjw0oq_-yq|d12$3_eM8cxVaaq zOLRn+p~u*zCRgLVzey_|kxW6~uJvFzf1Wc@3usw95+TWT44yeWQJgFrGZ?42Ea3+? zQVh7;AWTRN^a=d|cGdTf96u+uB{toWs7#9emb6Y!ShGr~dB-~!qpg-hTldGeuJH5W zcq^<{wzA*;=X}+KmYISEgXlYOj&=OSOqBgK6kSlj4Wy|CFRj)BmsJ|g#&P}#vdlY+ zqUv_}Y{;6dom@tUpm;w&-d^h2hCpI6#-r$CnK^SwVTzxGK2dSdBdx^Xm@xe<$**3w zzw}%`4;wAxYD-3!i%2{T%>|*%GW@%xvXVl+1V)Rgs(7?mS=-;och~7kM;DAnv;L5l zZj~1Euw^hH+mLO8x@n}skP~ICR<079H_{v&m$uhDWj%~(=W8F}2gPRy>)MW zk4!&a|HCYy*;KYs1qz08adROhCwO(DnInnLj+ox64LCi*&xLNXY4W@P{AUuh^eBp` zjmJ1Hm~il)3Co1RHB7*8Adc*`qJY~dlAc3?FBl8J*#h;`q`i9uy75phW+l$RWT(l3 XK_=-rz;XjD%m42`9c+0iZbScn{u*|c diff --git a/patched-vscode/src/vs/platform/actionWidget/browser/actionWidget.css b/patched-vscode/src/vs/platform/actionWidget/browser/actionWidget.css index 2354e1af..d205c7ab 100644 --- a/patched-vscode/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/patched-vscode/src/vs/platform/actionWidget/browser/actionWidget.css @@ -12,9 +12,11 @@ display: block; width: 100%; border: 1px solid var(--vscode-editorWidget-border) !important; - border-radius: 2px; - background-color: var(--vscode-editorWidget-background); - color: var(--vscode-editorWidget-foreground); + border-radius: 5px; + background-color: var(--vscode-editorActionList-background); + color: var(--vscode-editorActionList-foreground); + padding: 4px; + box-shadow: 0 2px 8px var(--vscode-widget-shadow); } .context-view-block { @@ -59,11 +61,12 @@ cursor: pointer; touch-action: none; width: 100%; + border-radius: 4px; } .action-widget .monaco-list .monaco-list-row.action.focused:not(.option-disabled) { - background-color: var(--vscode-quickInputList-focusBackground) !important; - color: var(--vscode-quickInputList-focusForeground); + background-color: var(--vscode-editorActionList-focusBackground) !important; + color: var(--vscode-editorActionList-focusForeground); outline: 1px solid var(--vscode-menu-selectionBorder, transparent); outline-offset: -1px; } @@ -71,6 +74,11 @@ .action-widget .monaco-list-row.group-header { color: var(--vscode-descriptionForeground) !important; font-weight: 600; + font-size: 12px; +} + +.action-widget .monaco-list-row.group-header:not(:first-of-type) { + margin-top: 2px; } .action-widget .monaco-list .group-header, @@ -88,7 +96,7 @@ .action-widget .monaco-list-row.action { display: flex; - gap: 6px; + gap: 8px; align-items: center; } @@ -124,8 +132,9 @@ /* Action bar */ .action-widget .action-widget-action-bar { - background-color: var(--vscode-editorHoverWidget-statusBarBackground); + background-color: var(--vscode-editorActionList-background); border-top: 1px solid var(--vscode-editorHoverWidget-border); + margin-top: 2px; } .action-widget .action-widget-action-bar::before { @@ -135,7 +144,7 @@ } .action-widget .action-widget-action-bar .actions-container { - padding: 0 8px; + padding: 3px 8px 0; } .action-widget-action-bar .action-label { diff --git a/patched-vscode/src/vs/platform/actionWidget/browser/actionWidget.ts b/patched-vscode/src/vs/platform/actionWidget/browser/actionWidget.ts index e7b26382..20a030b6 100644 --- a/patched-vscode/src/vs/platform/actionWidget/browser/actionWidget.ts +++ b/patched-vscode/src/vs/platform/actionWidget/browser/actionWidget.ts @@ -21,7 +21,7 @@ import { inputActiveOptionBackground, registerColor } from 'vs/platform/theme/co registerColor( 'actionBar.toggledBackground', - { dark: inputActiveOptionBackground, light: inputActiveOptionBackground, hcDark: inputActiveOptionBackground, hcLight: inputActiveOptionBackground, }, + inputActiveOptionBackground, localize('actionBar.toggledBackground', 'Background color for toggled action items in action bar.') ); diff --git a/patched-vscode/src/vs/platform/actions/browser/buttonbar.ts b/patched-vscode/src/vs/platform/actions/browser/buttonbar.ts index 165caec0..510267bf 100644 --- a/patched-vscode/src/vs/platform/actions/browser/buttonbar.ts +++ b/patched-vscode/src/vs/platform/actions/browser/buttonbar.ts @@ -6,18 +6,21 @@ import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { ActionRunner, IAction, IActionRunner, SubmenuAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; +import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; -import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IToolBarRenderOptions } from 'vs/platform/actions/browser/toolbar'; +import { MenuId, IMenuService, MenuItemAction, IMenuActionOptions } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export type IButtonConfigProvider = (action: IAction) => { +export type IButtonConfigProvider = (action: IAction, index: number) => { showIcon?: boolean; showLabel?: boolean; isSecondary?: boolean; @@ -66,7 +69,7 @@ export class WorkbenchButtonBar extends ButtonBar { super.dispose(); } - update(actions: IAction[]): void { + update(actions: IAction[], secondary: IAction[]): void { const conifgProvider: IButtonConfigProvider = this._options?.buttonConfigProvider ?? (() => ({ showLabel: true })); @@ -87,7 +90,7 @@ export class WorkbenchButtonBar extends ButtonBar { const [first, ...rest] = actionOrSubmenu.actions; action = first; btn = this.addButtonWithDropdown({ - secondary: conifgProvider(action)?.isSecondary ?? secondary, + secondary: conifgProvider(action, i)?.isSecondary ?? secondary, actionRunner: this._actionRunner, actions: rest, contextMenuProvider: this._contextMenuService, @@ -96,19 +99,20 @@ export class WorkbenchButtonBar extends ButtonBar { } else { action = actionOrSubmenu; btn = this.addButton({ - secondary: conifgProvider(action)?.isSecondary ?? secondary, + secondary: conifgProvider(action, i)?.isSecondary ?? secondary, ariaLabel: action.label }); } btn.enabled = action.enabled; + btn.checked = action.checked ?? false; btn.element.classList.add('default-colors'); - if (conifgProvider(action)?.showLabel ?? true) { + if (conifgProvider(action, i)?.showLabel ?? true) { btn.label = action.label; } else { btn.element.classList.add('monaco-text-button'); } - if (conifgProvider(action)?.showIcon) { + if (conifgProvider(action, i)?.showIcon) { if (action instanceof MenuItemAction && ThemeIcon.isThemeIcon(action.item.icon)) { btn.icon = action.item.icon; } else if (action.class) { @@ -122,21 +126,51 @@ export class WorkbenchButtonBar extends ButtonBar { } else { tooltip = action.label; } - this._updateStore.add(this._hoverService.setupUpdatableHover(hoverDelegate, btn.element, tooltip)); + this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, tooltip)); this._updateStore.add(btn.onDidClick(async () => { this._actionRunner.run(action); })); } + + if (secondary.length > 0) { + + const btn = this.addButton({ + secondary: true, + ariaLabel: localize('moreActions', "More Actions") + }); + + btn.icon = Codicon.dropDownButton; + btn.element.classList.add('default-colors', 'monaco-text-button'); + + btn.enabled = true; + this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, localize('moreActions', "More Actions"))); + this._updateStore.add(btn.onDidClick(async () => { + this._contextMenuService.showContextMenu({ + getAnchor: () => btn.element, + getActions: () => secondary, + actionRunner: this._actionRunner, + onHide: () => btn.element.setAttribute('aria-expanded', 'false') + }); + btn.element.setAttribute('aria-expanded', 'true'); + + })); + } this._onDidChange.fire(this); } } +export interface IMenuWorkbenchButtonBarOptions extends IWorkbenchButtonBarOptions { + menuOptions?: IMenuActionOptions; + + toolbarOptions?: IToolBarRenderOptions; +} + export class MenuWorkbenchButtonBar extends WorkbenchButtonBar { constructor( container: HTMLElement, menuId: MenuId, - options: IWorkbenchButtonBarOptions | undefined, + options: IMenuWorkbenchButtonBarOptions | undefined, @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @@ -153,12 +187,16 @@ export class MenuWorkbenchButtonBar extends WorkbenchButtonBar { this.clear(); - const actions = menu - .getActions({ renderShortTitle: true }) - .flatMap(entry => entry[1]); - - super.update(actions); + const primary: IAction[] = []; + const secondary: IAction[] = []; + createAndFillInActionBarActions( + menu, + options?.menuOptions, + { primary, secondary }, + options?.toolbarOptions?.primaryGroup + ); + super.update(primary, secondary); }; this._store.add(menu.onDidChange(update)); update(); diff --git a/patched-vscode/src/vs/platform/actions/browser/floatingMenu.ts b/patched-vscode/src/vs/platform/actions/browser/floatingMenu.ts index e6840b10..e7285146 100644 --- a/patched-vscode/src/vs/platform/actions/browser/floatingMenu.ts +++ b/patched-vscode/src/vs/platform/actions/browser/floatingMenu.ts @@ -119,7 +119,7 @@ export class FloatingClickMenu extends AbstractFloatingClickMenu { const w = this.instantiationService.createInstance(FloatingClickWidget, action.label); const node = w.getDomNode(); this.options.container.appendChild(node); - disposable.add(toDisposable(() => this.options.container.removeChild(node))); + disposable.add(toDisposable(() => node.remove())); return w; } diff --git a/patched-vscode/src/vs/platform/actions/browser/menuEntryActionViewItem.css b/patched-vscode/src/vs/platform/actions/browser/menuEntryActionViewItem.css index c5cba140..7eb35af7 100644 --- a/patched-vscode/src/vs/platform/actions/browser/menuEntryActionViewItem.css +++ b/patched-vscode/src/vs/platform/actions/browser/menuEntryActionViewItem.css @@ -11,6 +11,20 @@ background-size: 16px; } +.monaco-action-bar .action-item.menu-entry.text-only .action-label { + color: var(--vscode-descriptionForeground); + overflow: hidden; + border-radius: 2px; +} + +.monaco-action-bar .action-item.menu-entry.text-only.use-comma:not(:last-of-type) .action-label::after { + content: ', '; +} + +.monaco-action-bar .action-item.menu-entry.text-only + .action-item:not(.text-only) > .monaco-dropdown .action-label { + color: var(--vscode-descriptionForeground); +} + .monaco-dropdown-with-default { display: flex !important; flex-direction: row; diff --git a/patched-vscode/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/patched-vscode/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index da596a8f..58904af4 100644 --- a/patched-vscode/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/patched-vscode/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -31,9 +31,25 @@ import { assertType } from 'vs/base/common/types'; import { asCssVariable, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; + +export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void; +export function createAndFillInContextMenuActions(menu: [string, Array][], target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void; +export function createAndFillInContextMenuActions(menu: IMenu | [string, Array][], optionsOrTarget: IMenuActionOptions | undefined | IAction[] | { primary: IAction[]; secondary: IAction[] }, targetOrPrimaryGroup?: IAction[] | { primary: IAction[]; secondary: IAction[] } | string, primaryGroupOrUndefined?: string): void { + let target: IAction[] | { primary: IAction[]; secondary: IAction[] }; + let primaryGroup: string | ((actionGroup: string) => boolean) | undefined; + let groups: [string, Array][]; + if (Array.isArray(menu)) { + groups = menu; + target = optionsOrTarget as IAction[] | { primary: IAction[]; secondary: IAction[] }; + primaryGroup = targetOrPrimaryGroup as string | undefined; + } else { + const options: IMenuActionOptions | undefined = optionsOrTarget as IMenuActionOptions | undefined; + groups = menu.getActions(options); + target = targetOrPrimaryGroup as IAction[] | { primary: IAction[]; secondary: IAction[] }; + primaryGroup = primaryGroupOrUndefined; + } -export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void { - const groups = menu.getActions(options); const modifierKeyEmitter = ModifierKeyEmitter.getInstance(); const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey); fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation'); @@ -46,8 +62,42 @@ export function createAndFillInActionBarActions( primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean +): void; +export function createAndFillInActionBarActions( + menu: [string, Array][], + target: IAction[] | { primary: IAction[]; secondary: IAction[] }, + primaryGroup?: string | ((actionGroup: string) => boolean), + shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, + useSeparatorsInPrimaryActions?: boolean +): void; +export function createAndFillInActionBarActions( + menu: IMenu | [string, Array][], + optionsOrTarget: IMenuActionOptions | undefined | IAction[] | { primary: IAction[]; secondary: IAction[] }, + targetOrPrimaryGroup?: IAction[] | { primary: IAction[]; secondary: IAction[] } | string | ((actionGroup: string) => boolean), + primaryGroupOrShouldInlineSubmenu?: string | ((actionGroup: string) => boolean) | ((action: SubmenuAction, group: string, groupSize: number) => boolean), + shouldInlineSubmenuOrUseSeparatorsInPrimaryActions?: ((action: SubmenuAction, group: string, groupSize: number) => boolean) | boolean, + useSeparatorsInPrimaryActionsOrUndefined?: boolean ): void { - const groups = menu.getActions(options); + let target: IAction[] | { primary: IAction[]; secondary: IAction[] }; + let primaryGroup: string | ((actionGroup: string) => boolean) | undefined; + let shouldInlineSubmenu: ((action: SubmenuAction, group: string, groupSize: number) => boolean) | undefined; + let useSeparatorsInPrimaryActions: boolean | undefined; + let groups: [string, Array][]; + if (Array.isArray(menu)) { + groups = menu; + target = optionsOrTarget as IAction[] | { primary: IAction[]; secondary: IAction[] }; + primaryGroup = targetOrPrimaryGroup as string | ((actionGroup: string) => boolean) | undefined; + shouldInlineSubmenu = primaryGroupOrShouldInlineSubmenu as (action: SubmenuAction, group: string, groupSize: number) => boolean; + useSeparatorsInPrimaryActions = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions as boolean | undefined; + } else { + const options: IMenuActionOptions | undefined = optionsOrTarget as IMenuActionOptions | undefined; + groups = menu.getActions(options); + target = targetOrPrimaryGroup as IAction[] | { primary: IAction[]; secondary: IAction[] }; + primaryGroup = primaryGroupOrShouldInlineSubmenu as string | ((actionGroup: string) => boolean) | undefined; + shouldInlineSubmenu = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions as (action: SubmenuAction, group: string, groupSize: number) => boolean; + useSeparatorsInPrimaryActions = useSeparatorsInPrimaryActionsOrUndefined; + } + const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup: string) => actionGroup === primaryGroup : primaryGroup; // Action bars handle alternative actions on their own so the alternative actions should be ignored @@ -121,7 +171,7 @@ export interface IMenuEntryActionViewItemOptions { hoverDelegate?: IHoverDelegate; } -export class MenuEntryActionViewItem extends ActionViewItem { +export class MenuEntryActionViewItem extends ActionViewItem { private _wantsAltCommand: boolean = false; private readonly _itemClassDispose = this._register(new MutableDisposable()); @@ -129,7 +179,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { constructor( action: MenuItemAction, - options: IMenuEntryActionViewItemOptions | undefined, + protected _options: T | undefined, @IKeybindingService protected readonly _keybindingService: IKeybindingService, @INotificationService protected _notificationService: INotificationService, @IContextKeyService protected _contextKeyService: IContextKeyService, @@ -137,7 +187,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { @IContextMenuService protected _contextMenuService: IContextMenuService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { - super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: options?.draggable, keybinding: options?.keybinding, hoverDelegate: options?.hoverDelegate }); + super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: _options?.draggable, keybinding: _options?.keybinding, hoverDelegate: _options?.hoverDelegate }); this._altKey = ModifierKeyEmitter.getInstance(); } @@ -285,6 +335,45 @@ export class MenuEntryActionViewItem extends ActionViewItem { } } +export interface ITextOnlyMenuEntryActionViewItemOptions extends IMenuEntryActionViewItemOptions { + conversational?: boolean; + useComma?: boolean; +} + +export class TextOnlyMenuEntryActionViewItem extends MenuEntryActionViewItem { + + override render(container: HTMLElement): void { + this.options.label = true; + this.options.icon = false; + super.render(container); + container.classList.add('text-only'); + container.classList.toggle('use-comma', this._options?.useComma ?? false); + } + + protected override updateLabel() { + const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService); + if (!kb) { + return super.updateLabel(); + } + if (this.label) { + const kb2 = TextOnlyMenuEntryActionViewItem._symbolPrintEnter(kb); + + if (this._options?.conversational) { + this.label.textContent = localize({ key: 'content2', comment: ['A label with keybindg like "ESC to dismiss"'] }, '{1} to {0}', this._action.label, kb2); + + } else { + this.label.textContent = localize({ key: 'content', comment: ['A label', 'A keybinding'] }, '{0} ({1})', this._action.label, kb2); + } + } + } + + private static _symbolPrintEnter(kb: ResolvedKeybinding) { + return kb.getLabel() + ?.replace(/\benter\b/gi, '\u23CE') + .replace(/\bEscape\b/gi, 'Esc'); + } +} + export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { constructor( diff --git a/patched-vscode/src/vs/platform/actions/browser/toolbar.ts b/patched-vscode/src/vs/platform/actions/browser/toolbar.ts index 4857d4bc..003ee6e7 100644 --- a/patched-vscode/src/vs/platform/actions/browser/toolbar.ts +++ b/patched-vscode/src/vs/platform/actions/browser/toolbar.ts @@ -202,7 +202,9 @@ export class WorkbenchToolBar extends ToolBar { if (action instanceof MenuItemAction && action.menuKeybinding) { primaryActions.push(action.menuKeybinding); } else if (!(action instanceof SubmenuItemAction || action instanceof ToggleMenuAction)) { - primaryActions.push(createConfigureKeybindingAction(action.id, undefined, this._commandService, this._keybindingService)); + // only enable the configure keybinding action for actions that support keybindings + const supportsKeybindings = !!this._keybindingService.lookupKeybinding(action.id); + primaryActions.push(createConfigureKeybindingAction(this._commandService, this._keybindingService, action.id, undefined, supportsKeybindings)); } // -- Hide Actions -- diff --git a/patched-vscode/src/vs/platform/actions/common/actions.ts b/patched-vscode/src/vs/platform/actions/common/actions.ts index fa9bd76e..4647d7a4 100644 --- a/patched-vscode/src/vs/platform/actions/common/actions.ts +++ b/patched-vscode/src/vs/platform/actions/common/actions.ts @@ -60,6 +60,8 @@ export class MenuId { static readonly DebugWatchContext = new MenuId('DebugWatchContext'); static readonly DebugToolBar = new MenuId('DebugToolBar'); static readonly DebugToolBarStop = new MenuId('DebugToolBarStop'); + static readonly DebugCallStackToolbar = new MenuId('DebugCallStackToolbar'); + static readonly DebugCreateConfiguration = new MenuId('DebugCreateConfiguration'); static readonly EditorContext = new MenuId('EditorContext'); static readonly SimpleEditorContext = new MenuId('SimpleEditorContext'); static readonly EditorContent = new MenuId('EditorContent'); @@ -112,6 +114,7 @@ export class MenuId { static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMInputBox = new MenuId('SCMInputBox'); static readonly SCMChangesSeparator = new MenuId('SCMChangesSeparator'); + static readonly SCMChangesContext = new MenuId('SCMChangesContext'); static readonly SCMIncomingChanges = new MenuId('SCMIncomingChanges'); static readonly SCMIncomingChangesContext = new MenuId('SCMIncomingChangesContext'); static readonly SCMIncomingChangesSetting = new MenuId('SCMIncomingChangesSetting'); @@ -130,6 +133,7 @@ export class MenuId { static readonly SCMSourceControl = new MenuId('SCMSourceControl'); static readonly SCMSourceControlInline = new MenuId('SCMSourceControlInline'); static readonly SCMSourceControlTitle = new MenuId('SCMSourceControlTitle'); + static readonly SCMHistoryTitle = new MenuId('SCMHistoryTitle'); static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); static readonly SearchActionMenu = new MenuId('SearchActionContext'); @@ -138,10 +142,12 @@ export class MenuId { static readonly StickyScrollContext = new MenuId('StickyScrollContext'); static readonly TestItem = new MenuId('TestItem'); static readonly TestItemGutter = new MenuId('TestItemGutter'); + static readonly TestProfilesContext = new MenuId('TestProfilesContext'); static readonly TestMessageContext = new MenuId('TestMessageContext'); static readonly TestMessageContent = new MenuId('TestMessageContent'); static readonly TestPeekElement = new MenuId('TestPeekElement'); static readonly TestPeekTitle = new MenuId('TestPeekTitle'); + static readonly TestCallStack = new MenuId('TestCallStack'); static readonly TouchBarContext = new MenuId('TouchBarContext'); static readonly TitleBarContext = new MenuId('TitleBarContext'); static readonly TitleBarTitleContext = new MenuId('TitleBarTitleContext'); @@ -171,6 +177,8 @@ export class MenuId { static readonly InteractiveCellDelete = new MenuId('InteractiveCellDelete'); static readonly InteractiveCellExecute = new MenuId('InteractiveCellExecute'); static readonly InteractiveInputExecute = new MenuId('InteractiveInputExecute'); + static readonly InteractiveInputConfig = new MenuId('InteractiveInputConfig'); + static readonly ReplInputExecute = new MenuId('ReplInputExecute'); static readonly IssueReporter = new MenuId('IssueReporter'); static readonly NotebookToolbar = new MenuId('NotebookToolbar'); static readonly NotebookStickyScrollContext = new MenuId('NotebookStickyScrollContext'); @@ -209,6 +217,7 @@ export class MenuId { static readonly TerminalStickyScrollContext = new MenuId('TerminalStickyScrollContext'); static readonly WebviewContext = new MenuId('WebviewContext'); static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions'); + static readonly InlineEditsActions = new MenuId('InlineEditsActions'); static readonly InlineEditActions = new MenuId('InlineEditActions'); static readonly NewFile = new MenuId('NewFile'); static readonly MergeInput1Toolbar = new MenuId('MergeToolbar1Toolbar'); @@ -271,6 +280,11 @@ export interface IMenu extends IDisposable { getActions(options?: IMenuActionOptions): [string, Array][]; } +export interface IMenuData { + contexts: ReadonlySet; + actions: [string, Array][]; +} + export const IMenuService = createDecorator('menuService'); export interface IMenuCreateOptions { @@ -283,6 +297,8 @@ export interface IMenuService { readonly _serviceBrand: undefined; /** + * Consider using getMenuActions if you don't need to listen to events. + * * Create a new menu for the given menu identifier. A menu sends events when it's entries * have changed (placement, enablement, checked-state). By default it does not send events for * submenu entries. That is more expensive and must be explicitly enabled with the @@ -290,6 +306,16 @@ export interface IMenuService { */ createMenu(id: MenuId, contextKeyService: IContextKeyService, options?: IMenuCreateOptions): IMenu; + /** + * Creates a new menu, gets the actions, and then disposes of the menu. + */ + getMenuActions(id: MenuId, contextKeyService: IContextKeyService, options?: IMenuActionOptions): [string, Array][]; + + /** + * Gets the names of the contexts that this menu listens on. + */ + getMenuContexts(id: MenuId): ReadonlySet; + /** * Reset **all** menu item hidden states. */ diff --git a/patched-vscode/src/vs/platform/actions/common/menuService.ts b/patched-vscode/src/vs/platform/actions/common/menuService.ts index 61fefa8e..33abc7ac 100644 --- a/patched-vscode/src/vs/platform/actions/common/menuService.ts +++ b/patched-vscode/src/vs/platform/actions/common/menuService.ts @@ -34,6 +34,18 @@ export class MenuService implements IMenuService { return new MenuImpl(id, this._hiddenStates, { emitEventsForSubmenuChanges: false, eventDebounceDelay: 50, ...options }, this._commandService, this._keybindingService, contextKeyService); } + getMenuActions(id: MenuId, contextKeyService: IContextKeyService, options?: IMenuActionOptions): [string, Array][] { + const menu = new MenuImpl(id, this._hiddenStates, { emitEventsForSubmenuChanges: false, eventDebounceDelay: 50, ...options }, this._commandService, this._keybindingService, contextKeyService); + const actions = menu.getActions(options); + menu.dispose(); + return actions; + } + + getMenuContexts(id: MenuId): ReadonlySet { + const menuInfo = new MenuInfoSnapshot(id, false); + return new Set([...menuInfo.structureContextKeys, ...menuInfo.preconditionContextKeys, ...menuInfo.toggledContextKeys]); + } + resetHiddenStates(ids?: MenuId[]): void { this._hiddenStates.reset(ids); } @@ -152,24 +164,24 @@ class PersistedMenuHideState { type MenuItemGroup = [string, Array]; -class MenuInfo { - - private _menuGroups: MenuItemGroup[] = []; +class MenuInfoSnapshot { + protected _menuGroups: MenuItemGroup[] = []; + private _allMenuIds: Set = new Set(); private _structureContextKeys: Set = new Set(); private _preconditionContextKeys: Set = new Set(); private _toggledContextKeys: Set = new Set(); constructor( - private readonly _id: MenuId, - private readonly _hiddenStates: PersistedMenuHideState, - private readonly _collectContextKeysForSubmenus: boolean, - @ICommandService private readonly _commandService: ICommandService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService + protected readonly _id: MenuId, + protected readonly _collectContextKeysForSubmenus: boolean, ) { this.refresh(); } + get allMenuIds(): ReadonlySet { + return this._allMenuIds; + } + get structureContextKeys(): ReadonlySet { return this._structureContextKeys; } @@ -186,14 +198,13 @@ class MenuInfo { // reset this._menuGroups.length = 0; + this._allMenuIds.clear(); this._structureContextKeys.clear(); this._preconditionContextKeys.clear(); this._toggledContextKeys.clear(); - const menuItems = MenuRegistry.getMenuItems(this._id); - + const menuItems = this._sort(MenuRegistry.getMenuItems(this._id)); let group: MenuItemGroup | undefined; - menuItems.sort(MenuInfo._compareMenuItems); for (const item of menuItems) { // group by groupId @@ -204,33 +215,65 @@ class MenuInfo { } group[1].push(item); - // keep keys for eventing - this._collectContextKeys(item); + // keep keys and submenu ids for eventing + this._collectContextKeysAndSubmenuIds(item); } + this._allMenuIds.add(this._id); + } + + protected _sort(menuItems: (IMenuItem | ISubmenuItem)[]) { + // no sorting needed in snapshot + return menuItems; } - private _collectContextKeys(item: IMenuItem | ISubmenuItem): void { + private _collectContextKeysAndSubmenuIds(item: IMenuItem | ISubmenuItem): void { - MenuInfo._fillInKbExprKeys(item.when, this._structureContextKeys); + MenuInfoSnapshot._fillInKbExprKeys(item.when, this._structureContextKeys); if (isIMenuItem(item)) { // keep precondition keys for event if applicable if (item.command.precondition) { - MenuInfo._fillInKbExprKeys(item.command.precondition, this._preconditionContextKeys); + MenuInfoSnapshot._fillInKbExprKeys(item.command.precondition, this._preconditionContextKeys); } // keep toggled keys for event if applicable if (item.command.toggled) { const toggledExpression: ContextKeyExpression = (item.command.toggled as { condition: ContextKeyExpression }).condition || item.command.toggled; - MenuInfo._fillInKbExprKeys(toggledExpression, this._toggledContextKeys); + MenuInfoSnapshot._fillInKbExprKeys(toggledExpression, this._toggledContextKeys); } } else if (this._collectContextKeysForSubmenus) { // recursively collect context keys from submenus so that this // menu fires events when context key changes affect submenus - MenuRegistry.getMenuItems(item.submenu).forEach(this._collectContextKeys, this); + MenuRegistry.getMenuItems(item.submenu).forEach(this._collectContextKeysAndSubmenuIds, this); + + this._allMenuIds.add(item.submenu); } } + private static _fillInKbExprKeys(exp: ContextKeyExpression | undefined, set: Set): void { + if (exp) { + for (const key of exp.keys()) { + set.add(key); + } + } + } + +} + +class MenuInfo extends MenuInfoSnapshot { + + constructor( + _id: MenuId, + private readonly _hiddenStates: PersistedMenuHideState, + _collectContextKeysForSubmenus: boolean, + @ICommandService private readonly _commandService: ICommandService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService + ) { + super(_id, _collectContextKeysForSubmenus); + this.refresh(); + } + createActionGroups(options: IMenuActionOptions | undefined): [string, Array][] { const result: [string, Array][] = []; @@ -248,7 +291,7 @@ class MenuInfo { const menuHide = createMenuHide(this._id, isMenuItem ? item.command : item, this._hiddenStates); if (isMenuItem) { // MenuItemAction - const menuKeybinding = createConfigureKeybindingAction(item.command.id, item.when, this._commandService, this._keybindingService); + const menuKeybinding = createConfigureKeybindingAction(this._commandService, this._keybindingService, item.command.id, item.when); (activeActions ??= []).push(new MenuItemAction(item.command, item.alt, options, menuHide, menuKeybinding, this._contextKeyService, this._commandService)); } else { // SubmenuItemAction @@ -267,12 +310,8 @@ class MenuInfo { return result; } - private static _fillInKbExprKeys(exp: ContextKeyExpression | undefined, set: Set): void { - if (exp) { - for (const key of exp.keys()) { - set.add(key); - } - } + protected override _sort(menuItems: (IMenuItem | ISubmenuItem)[]): (IMenuItem | ISubmenuItem)[] { + return menuItems.sort(MenuInfo._compareMenuItems); } private static _compareMenuItems(a: IMenuItem | ISubmenuItem, b: IMenuItem | ISubmenuItem): number { @@ -353,8 +392,11 @@ class MenuImpl implements IMenu { }, options.eventDebounceDelay); this._disposables.add(rebuildMenuSoon); this._disposables.add(MenuRegistry.onDidChangeMenu(e => { - if (e.has(id)) { - rebuildMenuSoon.schedule(); + for (const id of this._menuInfo.allMenuIds) { + if (e.has(id)) { + rebuildMenuSoon.schedule(); + break; + } } })); @@ -442,10 +484,11 @@ function createMenuHide(menu: MenuId, command: ICommandAction | ISubmenuItem, st }; } -export function createConfigureKeybindingAction(commandId: string, when: ContextKeyExpression | undefined = undefined, commandService: ICommandService, keybindingService: IKeybindingService): IAction { +export function createConfigureKeybindingAction(commandService: ICommandService, keybindingService: IKeybindingService, commandId: string, when: ContextKeyExpression | undefined = undefined, enabled = true): IAction { return toAction({ id: `configureKeybinding/${commandId}`, label: localize('configure keybinding', "Configure Keybinding"), + enabled, run() { // Only set the when clause when there is no keybinding // It is possible that the action and the keybinding have different when clauses diff --git a/patched-vscode/src/vs/platform/actions/test/common/menuService.test.ts b/patched-vscode/src/vs/platform/actions/test/common/menuService.test.ts index 31e5d6c1..8a3b2c42 100644 --- a/patched-vscode/src/vs/platform/actions/test/common/menuService.test.ts +++ b/patched-vscode/src/vs/platform/actions/test/common/menuService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { generateUuid } from 'vs/base/common/uuid'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/patched-vscode/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts index 0c49f0ac..5d726261 100644 --- a/patched-vscode/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts +++ b/patched-vscode/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts @@ -10,7 +10,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; -import { hasNativeTitlebar } from 'vs/platform/window/common/window'; +import { hasNativeTitlebar, TitlebarStyle } from 'vs/platform/window/common/window'; import { IBaseWindow, WindowMode } from 'vs/platform/window/electron-main/window'; import { BaseWindow } from 'vs/platform/windows/electron-main/windowImpl'; @@ -52,7 +52,7 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { return; // already disposed } - this.doTryClaimWindow(); + this.doTryClaimWindow(options); if (options && !this.stateApplied) { this.stateApplied = true; @@ -72,7 +72,7 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { } } - private doTryClaimWindow(): void { + private doTryClaimWindow(options?: BrowserWindowConstructorOptions): void { if (this._win) { return; // already claimed } @@ -82,11 +82,11 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { this.logService.trace('[aux window] Claimed browser window instance'); // Remember - this.setWin(window); + this.setWin(window, options); // Disable Menu window.setMenu(null); - if ((isWindows || isLinux) && hasNativeTitlebar(this.configurationService)) { + if ((isWindows || isLinux) && hasNativeTitlebar(this.configurationService, options?.titleBarStyle === 'hidden' ? TitlebarStyle.CUSTOM : undefined /* unknown */)) { window.setAutoHideMenuBar(true); // Fix for https://github.com/microsoft/vscode/issues/200615 } diff --git a/patched-vscode/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/patched-vscode/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts index a07b5535..a5829812 100644 --- a/patched-vscode/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts +++ b/patched-vscode/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts @@ -13,7 +13,7 @@ import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electr import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowState, WindowMode, defaultAuxWindowState } from 'vs/platform/window/electron-main/window'; -import { WindowStateValidator, defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows'; +import { IDefaultBrowserWindowOptionsOverrides, WindowStateValidator, defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows'; export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliaryWindowsMainService { @@ -88,13 +88,15 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar } createWindow(details: HandlerDetails): BrowserWindowConstructorOptions { - return this.instantiationService.invokeFunction(defaultBrowserWindowOptions, this.validateWindowState(details), { + const { state, overrides } = this.computeWindowStateAndOverrides(details); + return this.instantiationService.invokeFunction(defaultBrowserWindowOptions, state, overrides, { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload-aux.js').fsPath }); } - private validateWindowState(details: HandlerDetails): IWindowState { + private computeWindowStateAndOverrides(details: HandlerDetails): { readonly state: IWindowState; readonly overrides: IDefaultBrowserWindowOptionsOverrides } { const windowState: IWindowState = {}; + const overrides: IDefaultBrowserWindowOptionsOverrides = {}; const features = details.features.split(','); // for example: popup=yes,left=270,top=14.5,width=800,height=600 for (const feature of features) { @@ -118,6 +120,12 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar case 'window-fullscreen': windowState.mode = WindowMode.Fullscreen; break; + case 'window-disable-fullscreen': + overrides.disableFullscreen = true; + break; + case 'window-native-titlebar': + overrides.forceNativeTitlebar = true; + break; } } @@ -125,7 +133,7 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar this.logService.trace('[aux window] using window state', state); - return state; + return { state, overrides }; } registerWindow(webContents: WebContents): void { diff --git a/patched-vscode/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/patched-vscode/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 9fd5c045..5ff0da22 100644 --- a/patched-vscode/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/patched-vscode/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { createHash } from 'crypto'; import * as fs from 'fs'; import * as os from 'os'; @@ -131,7 +131,7 @@ flakySuite('BackupMainService', () => { environmentService = new EnvironmentMainService(parseArgs(process.argv, OPTIONS), { _serviceBrand: undefined, ...product }); - await Promises.mkdir(backupHome, { recursive: true }); + await fs.promises.mkdir(backupHome, { recursive: true }); configService = new TestConfigurationService(); stateMainService = new InMemoryTestStateMainService(); @@ -584,8 +584,8 @@ flakySuite('BackupMainService', () => { assert.strictEqual(((await service.getDirtyWorkspaces()).length), 0); try { - await Promises.mkdir(path.join(folderBackupPath, Schemas.file), { recursive: true }); - await Promises.mkdir(path.join(workspaceBackupPath, Schemas.untitled), { recursive: true }); + await fs.promises.mkdir(path.join(folderBackupPath, Schemas.file), { recursive: true }); + await fs.promises.mkdir(path.join(workspaceBackupPath, Schemas.untitled), { recursive: true }); } catch (error) { // ignore - folder might exist already } diff --git a/patched-vscode/src/vs/platform/checksum/test/node/checksumService.test.ts b/patched-vscode/src/vs/platform/checksum/test/node/checksumService.test.ts index 3e56af64..5e7e71cd 100644 --- a/patched-vscode/src/vs/platform/checksum/test/node/checksumService.test.ts +++ b/patched-vscode/src/vs/platform/checksum/test/node/checksumService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/platform/clipboard/browser/clipboardService.ts b/patched-vscode/src/vs/platform/clipboard/browser/clipboardService.ts index ed5490e7..0f4a1942 100644 --- a/patched-vscode/src/vs/platform/clipboard/browser/clipboardService.ts +++ b/patched-vscode/src/vs/platform/clipboard/browser/clipboardService.ts @@ -15,6 +15,13 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; +/** + * Custom mime type used for storing a list of uris in the clipboard. + * + * Requires support for custom web clipboards https://github.com/w3c/clipboard-apis/pull/175 + */ +const vscodeResourcesMime = 'application/vnd.code.resources'; + export class BrowserClipboardService extends Disposable implements IClipboardService { declare readonly _serviceBrand: undefined; @@ -34,7 +41,7 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer // and not in the clipboard, we have to invalidate // that state when the user copies other data. this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { - disposables.add(addDisposableListener(window.document, 'copy', () => this.clearResources())); + disposables.add(addDisposableListener(window.document, 'copy', () => this.clearResourcesState())); }, { window: mainWindow, disposables: this._store })); } @@ -86,7 +93,7 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer async writeText(text: string, type?: string): Promise { // Clear resources given we are writing text - this.writeResources([]); + this.clearResourcesState(); // With type: only in-memory is supported if (type) { @@ -134,7 +141,7 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer activeElement.focus(); } - activeDocument.body.removeChild(textArea); + textArea.remove(); } async readText(type?: string): Promise { @@ -172,8 +179,28 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer private static readonly MAX_RESOURCE_STATE_SOURCE_LENGTH = 1000; async writeResources(resources: URI[]): Promise { + // Guard access to navigator.clipboard with try/catch + // as we have seen DOMExceptions in certain browsers + // due to security policies. + try { + await getActiveWindow().navigator.clipboard.write([ + new ClipboardItem({ + [`web ${vscodeResourcesMime}`]: new Blob([ + JSON.stringify(resources.map(x => x.toJSON())) + ], { + type: vscodeResourcesMime + }) + }) + ]); + + // Continue to write to the in-memory clipboard as well. + // This is needed because some browsers allow the paste but then can't read the custom resources. + } catch (error) { + // Noop + } + if (resources.length === 0) { - this.clearResources(); + this.clearResourcesState(); } else { this.resources = resources; this.resourcesStateHash = await this.computeResourcesStateHash(); @@ -181,9 +208,25 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer } async readResources(): Promise { + // Guard access to navigator.clipboard with try/catch + // as we have seen DOMExceptions in certain browsers + // due to security policies. + try { + const items = await getActiveWindow().navigator.clipboard.read(); + for (const item of items) { + if (item.types.includes(`web ${vscodeResourcesMime}`)) { + const blob = await item.getType(`web ${vscodeResourcesMime}`); + const resources = (JSON.parse(await blob.text()) as URI[]).map(x => URI.from(x)); + return resources; + } + } + } catch (error) { + // Noop + } + const resourcesStateHash = await this.computeResourcesStateHash(); if (this.resourcesStateHash !== resourcesStateHash) { - this.clearResources(); // state mismatch, resources no longer valid + this.clearResourcesState(); // state mismatch, resources no longer valid } return this.resources; @@ -204,10 +247,28 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer } async hasResources(): Promise { + // Guard access to navigator.clipboard with try/catch + // as we have seen DOMExceptions in certain browsers + // due to security policies. + try { + const items = await getActiveWindow().navigator.clipboard.read(); + for (const item of items) { + if (item.types.includes(`web ${vscodeResourcesMime}`)) { + return true; + } + } + } catch (error) { + // Noop + } + return this.resources.length > 0; } - private clearResources(): void { + public clearInternalState(): void { + this.clearResourcesState(); + } + + private clearResourcesState(): void { this.resources = []; this.resourcesStateHash = undefined; } diff --git a/patched-vscode/src/vs/platform/clipboard/common/clipboardService.ts b/patched-vscode/src/vs/platform/clipboard/common/clipboardService.ts index c4aea9f7..8cc5cbc3 100644 --- a/patched-vscode/src/vs/platform/clipboard/common/clipboardService.ts +++ b/patched-vscode/src/vs/platform/clipboard/common/clipboardService.ts @@ -46,4 +46,11 @@ export interface IClipboardService { * Find out if resources are copied to the clipboard. */ hasResources(): Promise; + + /** + * Resets the internal state of the clipboard (if any) without touching the real clipboard. + * + * Used for implementations such as web which do not always support using the real clipboard. + */ + clearInternalState?(): void; } diff --git a/patched-vscode/src/vs/platform/commands/test/common/commands.test.ts b/patched-vscode/src/vs/platform/commands/test/common/commands.test.ts index eeb3ed5d..f46f8e1a 100644 --- a/patched-vscode/src/vs/platform/commands/test/common/commands.test.ts +++ b/patched-vscode/src/vs/platform/commands/test/common/commands.test.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 * as assert from 'assert'; +import assert from 'assert'; import { combinedDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/patched-vscode/src/vs/platform/configuration/common/configurationModels.ts b/patched-vscode/src/vs/platform/configuration/common/configurationModels.ts index 8b862b1c..553c312d 100644 --- a/patched-vscode/src/vs/platform/configuration/common/configurationModels.ts +++ b/patched-vscode/src/vs/platform/configuration/common/configurationModels.ts @@ -279,11 +279,18 @@ export class ConfigurationModel implements IConfigurationModel { this.keys.push(key); } if (OVERRIDE_PROPERTY_REGEX.test(key)) { - this.overrides.push({ - identifiers: overrideIdentifiersFromKey(key), + const identifiers = overrideIdentifiersFromKey(key); + const override = { + identifiers, keys: Object.keys(this.contents[key]), contents: toValuesTree(this.contents[key], message => this.logService.error(message)), - }); + }; + const index = this.overrides.findIndex(o => arrays.equals(o.identifiers, identifiers)); + if (index !== -1) { + this.overrides[index] = override; + } else { + this.overrides.push(override); + } } } } diff --git a/patched-vscode/src/vs/platform/configuration/common/configurationRegistry.ts b/patched-vscode/src/vs/platform/configuration/common/configurationRegistry.ts index ed8e56d5..7876af58 100644 --- a/patched-vscode/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/patched-vscode/src/vs/platform/configuration/common/configurationRegistry.ts @@ -70,10 +70,15 @@ export interface IConfigurationRegistry { */ deltaConfiguration(delta: IConfigurationDelta): void; + /** + * Return the registered default configurations + */ + getRegisteredDefaultConfigurations(): IConfigurationDefaults[]; + /** * Return the registered configuration defaults overrides */ - getConfigurationDefaultsOverrides(): Map; + getConfigurationDefaultsOverrides(): Map; /** * Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values. @@ -191,6 +196,11 @@ export interface IConfigurationPropertySchema extends IJSONSchema { */ disallowSyncIgnore?: boolean; + /** + * Disallow extensions to contribute configuration default value for this setting. + */ + disallowConfigurationDefault?: boolean; + /** * Labels for enumeration items */ @@ -233,22 +243,28 @@ export interface IConfigurationNode { restrictedProperties?: string[]; } +export type ConfigurationDefaultValueSource = IExtensionInfo | Map; + export interface IConfigurationDefaults { overrides: IStringDictionary; - source?: IExtensionInfo | string; + source?: IExtensionInfo; } export type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & { defaultDefaultValue?: any; source?: IExtensionInfo; // Source of the Property - defaultValueSource?: IExtensionInfo | string; // Source of the Default Value + defaultValueSource?: ConfigurationDefaultValueSource; // Source of the Default Value }; -export type IConfigurationDefaultOverride = { +export interface IConfigurationDefaultOverride { readonly value: any; - readonly source?: IExtensionInfo | string; // Source of the default override - readonly valuesSources?: Map; // Source of each value in default language overrides -}; + readonly source?: IExtensionInfo; // Source of the default override +} + +export interface IConfigurationDefaultOverrideValue { + readonly value: any; + readonly source?: ConfigurationDefaultValueSource; +} export const allSettings: { properties: IStringDictionary; patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; export const applicationSettings: { properties: IStringDictionary; patternProperties: IStringDictionary } = { properties: {}, patternProperties: {} }; @@ -264,7 +280,8 @@ const contributionRegistry = Registry.as(JSONExtensio class ConfigurationRegistry implements IConfigurationRegistry { - private readonly configurationDefaultsOverrides: Map; + private readonly registeredConfigurationDefaults: IConfigurationDefaults[] = []; + private readonly configurationDefaultsOverrides: Map; private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode; private readonly configurationContributors: IConfigurationNode[]; private readonly configurationProperties: IStringDictionary; @@ -280,7 +297,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event; constructor() { - this.configurationDefaultsOverrides = new Map(); + this.configurationDefaultsOverrides = new Map(); this.defaultLanguageConfigurationOverridesNode = { id: 'defaultOverrides', title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"), @@ -343,43 +360,47 @@ class ConfigurationRegistry implements IConfigurationRegistry { private doRegisterDefaultConfigurations(configurationDefaults: IConfigurationDefaults[], bucket: Set) { + this.registeredConfigurationDefaults.push(...configurationDefaults); + const overrideIdentifiers: string[] = []; for (const { overrides, source } of configurationDefaults) { for (const key in overrides) { bucket.add(key); + const configurationDefaultOverridesForKey = this.configurationDefaultsOverrides.get(key) + ?? this.configurationDefaultsOverrides.set(key, { configurationDefaultOverrides: [] }).get(key)!; + + const value = overrides[key]; + configurationDefaultOverridesForKey.configurationDefaultOverrides.push({ value, source }); + + // Configuration defaults for Override Identifiers if (OVERRIDE_PROPERTY_REGEX.test(key)) { - const configurationDefaultOverride = this.configurationDefaultsOverrides.get(key); - const valuesSources = configurationDefaultOverride?.valuesSources ?? new Map(); - if (source) { - for (const configuration of Object.keys(overrides[key])) { - valuesSources.set(configuration, source); - } + const newDefaultOverride = this.mergeDefaultConfigurationsForOverrideIdentifier(key, value, source, configurationDefaultOverridesForKey.configurationDefaultOverrideValue); + if (!newDefaultOverride) { + continue; } - const defaultValue = { ...(configurationDefaultOverride?.value || {}), ...overrides[key] }; - this.configurationDefaultsOverrides.set(key, { source, value: defaultValue, valuesSources }); - const plainKey = getLanguageTagSettingPlainKey(key); - const property: IRegisteredConfigurationPropertySchema = { - type: 'object', - default: defaultValue, - description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for the {0} language.", plainKey), - $ref: resourceLanguageSettingsSchemaId, - defaultDefaultValue: defaultValue, - source: types.isString(source) ? undefined : source, - defaultValueSource: source - }; + + configurationDefaultOverridesForKey.configurationDefaultOverrideValue = newDefaultOverride; + this.updateDefaultOverrideProperty(key, newDefaultOverride, source); overrideIdentifiers.push(...overrideIdentifiersFromKey(key)); - this.configurationProperties[key] = property; - this.defaultLanguageConfigurationOverridesNode.properties![key] = property; - } else { - this.configurationDefaultsOverrides.set(key, { value: overrides[key], source }); + } + + // Configuration defaults for Configuration Properties + else { + const newDefaultOverride = this.mergeDefaultConfigurationsForConfigurationProperty(key, value, source, configurationDefaultOverridesForKey.configurationDefaultOverrideValue); + if (!newDefaultOverride) { + continue; + } + + configurationDefaultOverridesForKey.configurationDefaultOverrideValue = newDefaultOverride; const property = this.configurationProperties[key]; if (property) { this.updatePropertyDefaultValue(key, property); this.updateSchema(key, property); } } + } } @@ -394,33 +415,149 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private doDeregisterDefaultConfigurations(defaultConfigurations: IConfigurationDefaults[], bucket: Set): void { + for (const defaultConfiguration of defaultConfigurations) { + const index = this.registeredConfigurationDefaults.indexOf(defaultConfiguration); + if (index !== -1) { + this.registeredConfigurationDefaults.splice(index, 1); + } + } for (const { overrides, source } of defaultConfigurations) { for (const key in overrides) { - const configurationDefaultsOverride = this.configurationDefaultsOverrides.get(key); - const id = types.isString(source) ? source : source?.id; - const configurationDefaultsOverrideSourceId = types.isString(configurationDefaultsOverride?.source) ? configurationDefaultsOverride?.source : configurationDefaultsOverride?.source?.id; - if (id !== configurationDefaultsOverrideSourceId) { + const configurationDefaultOverridesForKey = this.configurationDefaultsOverrides.get(key); + if (!configurationDefaultOverridesForKey) { continue; } - bucket.add(key); - this.configurationDefaultsOverrides.delete(key); + + const index = configurationDefaultOverridesForKey.configurationDefaultOverrides + .findIndex(configurationDefaultOverride => source ? configurationDefaultOverride.source?.id === source.id : configurationDefaultOverride.value === overrides[key]); + if (index === -1) { + continue; + } + + configurationDefaultOverridesForKey.configurationDefaultOverrides.splice(index, 1); + if (configurationDefaultOverridesForKey.configurationDefaultOverrides.length === 0) { + this.configurationDefaultsOverrides.delete(key); + } + if (OVERRIDE_PROPERTY_REGEX.test(key)) { - delete this.configurationProperties[key]; - delete this.defaultLanguageConfigurationOverridesNode.properties![key]; + let configurationDefaultOverrideValue: IConfigurationDefaultOverrideValue | undefined; + for (const configurationDefaultOverride of configurationDefaultOverridesForKey.configurationDefaultOverrides) { + configurationDefaultOverrideValue = this.mergeDefaultConfigurationsForOverrideIdentifier(key, configurationDefaultOverride.value, configurationDefaultOverride.source, configurationDefaultOverrideValue); + } + if (configurationDefaultOverrideValue && !types.isEmptyObject(configurationDefaultOverrideValue.value)) { + configurationDefaultOverridesForKey.configurationDefaultOverrideValue = configurationDefaultOverrideValue; + this.updateDefaultOverrideProperty(key, configurationDefaultOverrideValue, source); + } else { + this.configurationDefaultsOverrides.delete(key); + delete this.configurationProperties[key]; + delete this.defaultLanguageConfigurationOverridesNode.properties![key]; + } } else { + let configurationDefaultOverrideValue: IConfigurationDefaultOverrideValue | undefined; + for (const configurationDefaultOverride of configurationDefaultOverridesForKey.configurationDefaultOverrides) { + configurationDefaultOverrideValue = this.mergeDefaultConfigurationsForConfigurationProperty(key, configurationDefaultOverride.value, configurationDefaultOverride.source, configurationDefaultOverrideValue); + } + configurationDefaultOverridesForKey.configurationDefaultOverrideValue = configurationDefaultOverrideValue; const property = this.configurationProperties[key]; if (property) { this.updatePropertyDefaultValue(key, property); this.updateSchema(key, property); } } + bucket.add(key); } } - this.updateOverridePropertyPatternKey(); } + private updateDefaultOverrideProperty(key: string, newDefaultOverride: IConfigurationDefaultOverrideValue, source: IExtensionInfo | undefined): void { + const property: IRegisteredConfigurationPropertySchema = { + type: 'object', + default: newDefaultOverride.value, + description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for the {0} language.", getLanguageTagSettingPlainKey(key)), + $ref: resourceLanguageSettingsSchemaId, + defaultDefaultValue: newDefaultOverride.value, + source, + defaultValueSource: source + }; + this.configurationProperties[key] = property; + this.defaultLanguageConfigurationOverridesNode.properties![key] = property; + } + + private mergeDefaultConfigurationsForOverrideIdentifier(overrideIdentifier: string, configurationValueObject: IStringDictionary, valueSource: IExtensionInfo | undefined, existingDefaultOverride: IConfigurationDefaultOverrideValue | undefined): IConfigurationDefaultOverrideValue | undefined { + const defaultValue = existingDefaultOverride?.value || {}; + const source = existingDefaultOverride?.source ?? new Map(); + + // This should not happen + if (!(source instanceof Map)) { + console.error('objectConfigurationSources is not a Map'); + return undefined; + } + + for (const propertyKey of Object.keys(configurationValueObject)) { + const propertyDefaultValue = configurationValueObject[propertyKey]; + + const isObjectSetting = types.isObject(propertyDefaultValue) && + (types.isUndefined(defaultValue[propertyKey]) || types.isObject(defaultValue[propertyKey])); + + // If the default value is an object, merge the objects and store the source of each keys + if (isObjectSetting) { + defaultValue[propertyKey] = { ...(defaultValue[propertyKey] ?? {}), ...propertyDefaultValue }; + // Track the source of each value in the object + if (valueSource) { + for (const objectKey in propertyDefaultValue) { + source.set(`${propertyKey}.${objectKey}`, valueSource); + } + } + } + + // Primitive values are overridden + else { + defaultValue[propertyKey] = propertyDefaultValue; + if (valueSource) { + source.set(propertyKey, valueSource); + } else { + source.delete(propertyKey); + } + } + } + + return { value: defaultValue, source }; + } + + private mergeDefaultConfigurationsForConfigurationProperty(propertyKey: string, value: any, valuesSource: IExtensionInfo | undefined, existingDefaultOverride: IConfigurationDefaultOverrideValue | undefined): IConfigurationDefaultOverrideValue | undefined { + const property = this.configurationProperties[propertyKey]; + const existingDefaultValue = existingDefaultOverride?.value ?? property?.defaultDefaultValue; + let source: ConfigurationDefaultValueSource | undefined = valuesSource; + + const isObjectSetting = types.isObject(value) && + ( + property !== undefined && property.type === 'object' || + property === undefined && (types.isUndefined(existingDefaultValue) || types.isObject(existingDefaultValue)) + ); + + // If the default value is an object, merge the objects and store the source of each keys + if (isObjectSetting) { + source = existingDefaultOverride?.source ?? new Map(); + + // This should not happen + if (!(source instanceof Map)) { + console.error('defaultValueSource is not a Map'); + return undefined; + } + + for (const objectKey in value) { + if (valuesSource) { + source.set(`${propertyKey}.${objectKey}`, valuesSource); + } + } + value = { ...(types.isObject(existingDefaultValue) ? existingDefaultValue : {}), ...value }; + } + + return { value, source }; + } + public deltaConfiguration(delta: IConfigurationDelta): void { // defaults: remove let defaultsOverrides = false; @@ -569,8 +706,18 @@ class ConfigurationRegistry implements IConfigurationRegistry { return this.excludedConfigurationProperties; } - getConfigurationDefaultsOverrides(): Map { - return this.configurationDefaultsOverrides; + getRegisteredDefaultConfigurations(): IConfigurationDefaults[] { + return [...this.registeredConfigurationDefaults]; + } + + getConfigurationDefaultsOverrides(): Map { + const configurationDefaultsOverrides = new Map(); + for (const [key, value] of this.configurationDefaultsOverrides) { + if (value.configurationDefaultOverrideValue) { + configurationDefaultsOverrides.set(key, value.configurationDefaultOverrideValue); + } + } + return configurationDefaultsOverrides; } private registerJSONConfiguration(configuration: IConfigurationNode) { @@ -671,9 +818,15 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private updatePropertyDefaultValue(key: string, property: IRegisteredConfigurationPropertySchema): void { - const configurationdefaultOverride = this.configurationDefaultsOverrides.get(key); - let defaultValue = configurationdefaultOverride?.value; - let defaultSource = configurationdefaultOverride?.source; + const configurationdefaultOverride = this.configurationDefaultsOverrides.get(key)?.configurationDefaultOverrideValue; + let defaultValue = undefined; + let defaultSource = undefined; + if (configurationdefaultOverride + && (!property.disallowConfigurationDefault || !configurationdefaultOverride.source) // Prevent overriding the default value if the property is disallowed to be overridden by configuration defaults from extensions + ) { + defaultValue = configurationdefaultOverride.value; + defaultSource = configurationdefaultOverride.source; + } if (types.isUndefined(defaultValue)) { defaultValue = property.defaultDefaultValue; defaultSource = undefined; @@ -758,3 +911,36 @@ export function getScopes(): [string, ConfigurationScope | undefined][] { scopes.push(['task', ConfigurationScope.RESOURCE]); return scopes; } + +export function getAllConfigurationProperties(configurationNode: IConfigurationNode[]): IStringDictionary { + const result: IStringDictionary = {}; + for (const configuration of configurationNode) { + const properties = configuration.properties; + if (types.isObject(properties)) { + for (const key in properties) { + result[key] = properties[key]; + } + } + if (configuration.allOf) { + Object.assign(result, getAllConfigurationProperties(configuration.allOf)); + } + } + return result; +} + +export function parseScope(scope: string): ConfigurationScope { + switch (scope) { + case 'application': + return ConfigurationScope.APPLICATION; + case 'machine': + return ConfigurationScope.MACHINE; + case 'resource': + return ConfigurationScope.RESOURCE; + case 'machine-overridable': + return ConfigurationScope.MACHINE_OVERRIDABLE; + case 'language-overridable': + return ConfigurationScope.LANGUAGE_OVERRIDABLE; + default: + return ConfigurationScope.WINDOW; + } +} diff --git a/patched-vscode/src/vs/platform/configuration/common/configurations.ts b/patched-vscode/src/vs/platform/configuration/common/configurations.ts index d6f09a4d..5e3c303a 100644 --- a/patched-vscode/src/vs/platform/configuration/common/configurations.ts +++ b/patched-vscode/src/vs/platform/configuration/common/configurations.ts @@ -61,9 +61,9 @@ export class DefaultConfiguration extends Disposable { const defaultOverrideValue = configurationDefaultsOverrides[key]; const propertySchema = configurationProperties[key]; if (defaultOverrideValue !== undefined) { - this._configurationModel.addValue(key, defaultOverrideValue); + this._configurationModel.setValue(key, defaultOverrideValue); } else if (propertySchema) { - this._configurationModel.addValue(key, propertySchema.default); + this._configurationModel.setValue(key, propertySchema.default); } else { this._configurationModel.removeValue(key); } diff --git a/patched-vscode/src/vs/platform/configuration/test/common/configuration.test.ts b/patched-vscode/src/vs/platform/configuration/test/common/configuration.test.ts index e7b169e7..1f709cf2 100644 --- a/patched-vscode/src/vs/platform/configuration/test/common/configuration.test.ts +++ b/patched-vscode/src/vs/platform/configuration/test/common/configuration.test.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 * as assert from 'assert'; +import assert from 'assert'; import { merge, removeFromValueTree } from 'vs/platform/configuration/common/configuration'; import { mergeChanges } from 'vs/platform/configuration/common/configurationModels'; diff --git a/patched-vscode/src/vs/platform/configuration/test/common/configurationModels.test.ts b/patched-vscode/src/vs/platform/configuration/test/common/configurationModels.test.ts index 50b612d8..ab425c4f 100644 --- a/patched-vscode/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/patched-vscode/src/vs/platform/configuration/test/common/configurationModels.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ResourceMap } from 'vs/base/common/map'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/patched-vscode/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 9fc9e709..0f3bf7e2 100644 --- a/patched-vscode/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/patched-vscode/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -14,6 +14,14 @@ suite('ConfigurationRegistry', () => { const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + setup(() => reset()); + teardown(() => reset()); + + function reset() { + configurationRegistry.deregisterConfigurations(configurationRegistry.getConfigurations()); + configurationRegistry.deregisterDefaultConfigurations(configurationRegistry.getRegisteredDefaultConfigurations()); + } + test('configuration override', async () => { configurationRegistry.registerConfiguration({ 'id': '_test_default', @@ -31,6 +39,24 @@ suite('ConfigurationRegistry', () => { assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, c: 3 }); }); + test('configuration override defaults - prevent overriding default value', async () => { + configurationRegistry.registerConfiguration({ + 'id': '_test_default', + 'type': 'object', + 'properties': { + 'config.preventDefaultValueOverride': { + 'type': 'object', + default: { a: 0 }, + 'disallowConfigurationDefault': true + } + } + }); + + configurationRegistry.registerDefaultConfigurations([{ overrides: { 'config.preventDefaultValueOverride': { a: 1, b: 2 } } }]); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config.preventDefaultValueOverride'].default, { a: 0 }); + }); + test('configuration override defaults - merges defaults', async () => { configurationRegistry.registerDefaultConfigurations([{ overrides: { '[lang]': { a: 1, b: 2 } } }]); configurationRegistry.registerDefaultConfigurations([{ overrides: { '[lang]': { a: 2, c: 3 } } }]); @@ -38,7 +64,7 @@ suite('ConfigurationRegistry', () => { assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, b: 2, c: 3 }); }); - test('configuration defaults - overrides defaults', async () => { + test('configuration defaults - merge object default overrides', async () => { configurationRegistry.registerConfiguration({ 'id': '_test_default', 'type': 'object', @@ -51,7 +77,7 @@ suite('ConfigurationRegistry', () => { configurationRegistry.registerDefaultConfigurations([{ overrides: { 'config': { a: 1, b: 2 } } }]); configurationRegistry.registerDefaultConfigurations([{ overrides: { 'config': { a: 2, c: 3 } } }]); - assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, c: 3 }); + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, b: 2, c: 3 }); }); test('registering multiple settings with same policy', async () => { @@ -79,4 +105,88 @@ suite('ConfigurationRegistry', () => { assert.ok(actual['policy1'] !== undefined); assert.ok(actual['policy2'] === undefined); }); + + test('configuration defaults - deregister merged object default override', async () => { + configurationRegistry.registerConfiguration({ + 'id': '_test_default', + 'type': 'object', + 'properties': { + 'config': { + 'type': 'object', + } + } + }); + + const overrides1 = [{ overrides: { 'config': { a: 1, b: 2 } }, source: { id: 'source1', displayName: 'source1' } }]; + const overrides2 = [{ overrides: { 'config': { a: 2, c: 3 } }, source: { id: 'source2', displayName: 'source2' } }]; + + configurationRegistry.registerDefaultConfigurations(overrides1); + configurationRegistry.registerDefaultConfigurations(overrides2); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, b: 2, c: 3 }); + + configurationRegistry.deregisterDefaultConfigurations(overrides2); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 1, b: 2 }); + + configurationRegistry.deregisterDefaultConfigurations(overrides1); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, {}); + }); + + test('configuration defaults - deregister merged object default override without source', async () => { + configurationRegistry.registerConfiguration({ + 'id': '_test_default', + 'type': 'object', + 'properties': { + 'config': { + 'type': 'object', + } + } + }); + + const overrides1 = [{ overrides: { 'config': { a: 1, b: 2 } } }]; + const overrides2 = [{ overrides: { 'config': { a: 2, c: 3 } } }]; + + configurationRegistry.registerDefaultConfigurations(overrides1); + configurationRegistry.registerDefaultConfigurations(overrides2); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, b: 2, c: 3 }); + + configurationRegistry.deregisterDefaultConfigurations(overrides2); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 1, b: 2 }); + + configurationRegistry.deregisterDefaultConfigurations(overrides1); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, {}); + }); + + test('configuration defaults - deregister merged object default language overrides', async () => { + configurationRegistry.registerConfiguration({ + 'id': '_test_default', + 'type': 'object', + 'properties': { + 'config': { + 'type': 'object', + } + } + }); + + const overrides1 = [{ overrides: { '[lang]': { 'config': { a: 1, b: 2 } } }, source: { id: 'source1', displayName: 'source1' } }]; + const overrides2 = [{ overrides: { '[lang]': { 'config': { a: 2, c: 3 } } }, source: { id: 'source2', displayName: 'source2' } }]; + + configurationRegistry.registerDefaultConfigurations(overrides1); + configurationRegistry.registerDefaultConfigurations(overrides2); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { 'config': { a: 2, b: 2, c: 3 } }); + + configurationRegistry.deregisterDefaultConfigurations(overrides2); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { 'config': { a: 1, b: 2 } }); + + configurationRegistry.deregisterDefaultConfigurations(overrides1); + + assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['[lang]'], undefined); + }); }); diff --git a/patched-vscode/src/vs/platform/configuration/test/common/configurationService.test.ts b/patched-vscode/src/vs/platform/configuration/test/common/configurationService.test.ts index 880e49e8..0eff504b 100644 --- a/patched-vscode/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/patched-vscode/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; diff --git a/patched-vscode/src/vs/platform/configuration/test/common/configurations.test.ts b/patched-vscode/src/vs/platform/configuration/test/common/configurations.test.ts index 378107be..5b237b73 100644 --- a/patched-vscode/src/vs/platform/configuration/test/common/configurations.test.ts +++ b/patched-vscode/src/vs/platform/configuration/test/common/configurations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { equals } from 'vs/base/common/objects'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -22,8 +22,7 @@ suite('DefaultConfiguration', () => { function reset() { configurationRegistry.deregisterConfigurations(configurationRegistry.getConfigurations()); - const configurationDefaultsOverrides = configurationRegistry.getConfigurationDefaultsOverrides(); - configurationRegistry.deregisterDefaultConfigurations([...configurationDefaultsOverrides.keys()].map(key => ({ extensionId: configurationDefaultsOverrides.get(key)?.source, overrides: { [key]: configurationDefaultsOverrides.get(key)?.value } }))); + configurationRegistry.deregisterDefaultConfigurations(configurationRegistry.getRegisteredDefaultConfigurations()); } test('Test registering a property before initialize', async () => { @@ -110,7 +109,7 @@ suite('DefaultConfiguration', () => { assert.ok(equals(actual.getValue('a'), { b: { c: '2' } })); assert.ok(equals(actual.contents, { 'a': { b: { c: '2' } } })); - assert.deepStrictEqual(actual.keys, ['a.b', 'a.b.c']); + assert.deepStrictEqual(actual.keys.sort(), ['a.b', 'a.b.c']); }); test('Test registering the same property again', async () => { @@ -158,7 +157,7 @@ suite('DefaultConfiguration', () => { assert.ok(equals(actual.getValue('[a]'), { 'b': true })); assert.ok(equals(actual.contents, { '[a]': { 'b': true } })); assert.ok(equals(actual.overrides, [{ contents: { 'b': true }, identifiers: ['a'], keys: ['b'] }])); - assert.deepStrictEqual(actual.keys, ['[a]']); + assert.deepStrictEqual(actual.keys.sort(), ['[a]']); assert.strictEqual(actual.getOverrideValue('b', 'a'), true); }); @@ -191,7 +190,7 @@ suite('DefaultConfiguration', () => { assert.ok(equals(actual.getValue('[a]'), { 'b': true })); assert.ok(equals(actual.contents, { 'b': false, '[a]': { 'b': true } })); assert.ok(equals(actual.overrides, [{ contents: { 'b': true }, identifiers: ['a'], keys: ['b'] }])); - assert.deepStrictEqual(actual.keys, ['b', '[a]']); + assert.deepStrictEqual(actual.keys.sort(), ['[a]', 'b']); assert.strictEqual(actual.getOverrideValue('b', 'a'), true); }); @@ -227,7 +226,7 @@ suite('DefaultConfiguration', () => { assert.ok(equals(actual.getValue('[a]'), { 'b': true })); assert.ok(equals(actual.contents, { 'b': false, '[a]': { 'b': true } })); assert.ok(equals(actual.overrides, [{ contents: { 'b': true }, identifiers: ['a'], keys: ['b'] }])); - assert.deepStrictEqual(actual.keys, ['[a]', 'b']); + assert.deepStrictEqual(actual.keys.sort(), ['[a]', 'b']); assert.strictEqual(actual.getOverrideValue('b', 'a'), true); assert.deepStrictEqual(properties, ['b']); }); @@ -263,7 +262,7 @@ suite('DefaultConfiguration', () => { assert.ok(equals(actual.getValue('[a]'), { 'b': true })); assert.ok(equals(actual.contents, { 'b': false, '[a]': { 'b': true } })); assert.ok(equals(actual.overrides, [{ contents: { 'b': true }, identifiers: ['a'], keys: ['b'] }])); - assert.deepStrictEqual(actual.keys, ['b', '[a]']); + assert.deepStrictEqual(actual.keys.sort(), ['[a]', 'b']); assert.strictEqual(actual.getOverrideValue('b', 'a'), true); assert.deepStrictEqual(properties, ['[a]']); }); @@ -299,7 +298,7 @@ suite('DefaultConfiguration', () => { assert.ok(equals(actual.getValue('[a]'), { 'b': true })); assert.ok(equals(actual.contents, { 'b': false, '[a]': { 'b': true } })); assert.ok(equals(actual.overrides, [{ contents: { 'b': true }, identifiers: ['a'], keys: ['b'] }])); - assert.deepStrictEqual(actual.keys, ['b', '[a]']); + assert.deepStrictEqual(actual.keys.sort(), ['[a]', 'b']); assert.strictEqual(actual.getOverrideValue('b', 'a'), true); }); @@ -361,4 +360,54 @@ suite('DefaultConfiguration', () => { assert.deepStrictEqual(testObject.configurationModel.keys, ['b']); assert.strictEqual(testObject.configurationModel.getOverrideValue('b', 'a'), undefined); }); + + test('Test deregistering a merged language object setting', async () => { + const testObject = disposables.add(new DefaultConfiguration(new NullLogService())); + configurationRegistry.registerConfiguration({ + 'id': 'b', + 'order': 1, + 'title': 'b', + 'type': 'object', + 'properties': { + 'b': { + 'description': 'b', + 'type': 'object', + 'default': {}, + } + } + }); + const node1 = { + overrides: { + '[a]': { + 'b': { + 'aa': '1', + 'bb': '2' + } + } + }, + source: { id: 'source1', displayName: 'source1' } + }; + + const node2 = { + overrides: { + '[a]': { + 'b': { + 'bb': '20', + 'cc': '30' + } + } + }, + source: { id: 'source2', displayName: 'source2' } + }; + configurationRegistry.registerDefaultConfigurations([node1]); + configurationRegistry.registerDefaultConfigurations([node2]); + await testObject.initialize(); + + configurationRegistry.deregisterDefaultConfigurations([node1]); + assert.ok(equals(testObject.configurationModel.getValue('[a]'), { 'b': { 'bb': '20', 'cc': '30' } })); + assert.ok(equals(testObject.configurationModel.contents, { '[a]': { 'b': { 'bb': '20', 'cc': '30' } }, 'b': {} })); + assert.ok(equals(testObject.configurationModel.overrides, [{ contents: { 'b': { 'bb': '20', 'cc': '30' } }, identifiers: ['a'], keys: ['b'] }])); + assert.deepStrictEqual(testObject.configurationModel.keys.sort(), ['[a]', 'b']); + assert.ok(equals(testObject.configurationModel.getOverrideValue('b', 'a'), { 'bb': '20', 'cc': '30' })); + }); }); diff --git a/patched-vscode/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/patched-vscode/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 94ee037e..d3e44993 100644 --- a/patched-vscode/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/patched-vscode/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; diff --git a/patched-vscode/src/vs/platform/contextkey/test/browser/contextkey.test.ts b/patched-vscode/src/vs/platform/contextkey/test/browser/contextkey.test.ts index b56d4b87..d2301c19 100644 --- a/patched-vscode/src/vs/platform/contextkey/test/browser/contextkey.test.ts +++ b/patched-vscode/src/vs/platform/contextkey/test/browser/contextkey.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DeferredPromise } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; diff --git a/patched-vscode/src/vs/platform/contextkey/test/common/contextkey.test.ts b/patched-vscode/src/vs/platform/contextkey/test/common/contextkey.test.ts index 8f388fb1..2555701c 100644 --- a/patched-vscode/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/patched-vscode/src/vs/platform/contextkey/test/common/contextkey.test.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 * as assert from 'assert'; +import assert from 'assert'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ContextKeyExpr, ContextKeyExpression, implies } from 'vs/platform/contextkey/common/contextkey'; diff --git a/patched-vscode/src/vs/platform/contextkey/test/common/parser.test.ts b/patched-vscode/src/vs/platform/contextkey/test/common/parser.test.ts index c5be2596..17bfa468 100644 --- a/patched-vscode/src/vs/platform/contextkey/test/common/parser.test.ts +++ b/patched-vscode/src/vs/platform/contextkey/test/common/parser.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Parser } from 'vs/platform/contextkey/common/contextkey'; diff --git a/patched-vscode/src/vs/platform/contextkey/test/common/scanner.test.ts b/patched-vscode/src/vs/platform/contextkey/test/common/scanner.test.ts index df897db9..dacbfbeb 100644 --- a/patched-vscode/src/vs/platform/contextkey/test/common/scanner.test.ts +++ b/patched-vscode/src/vs/platform/contextkey/test/common/scanner.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Scanner, Token, TokenType } from 'vs/platform/contextkey/common/scanner'; diff --git a/patched-vscode/src/vs/platform/contextview/browser/contextMenuService.ts b/patched-vscode/src/vs/platform/contextview/browser/contextMenuService.ts index 907cf944..f70e3499 100644 --- a/patched-vscode/src/vs/platform/contextview/browser/contextMenuService.ts +++ b/patched-vscode/src/vs/platform/contextview/browser/contextMenuService.ts @@ -86,9 +86,8 @@ export namespace ContextMenuMenuDelegate { getActions: () => { const target: IAction[] = []; if (menuId) { - const menu = menuService.createMenu(menuId, contextKeyService ?? globalContextKeyService); - createAndFillInContextMenuActions(menu, menuActionOptions, target); - menu.dispose(); + const menu = menuService.getMenuActions(menuId, contextKeyService ?? globalContextKeyService, menuActionOptions); + createAndFillInContextMenuActions(menu, target); } if (!delegate.getActions) { return target; diff --git a/patched-vscode/src/vs/platform/cssDev/node/cssDevService.ts b/patched-vscode/src/vs/platform/cssDev/node/cssDevService.ts new file mode 100644 index 00000000..98e73883 --- /dev/null +++ b/patched-vscode/src/vs/platform/cssDev/node/cssDevService.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawn } from 'child_process'; +import { relative } from 'path'; +import { isESM } from 'vs/base/common/amd'; +import { FileAccess } from 'vs/base/common/network'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; + +export const ICSSDevelopmentService = createDecorator('ICSSDevelopmentService'); + +export interface ICSSDevelopmentService { + _serviceBrand: undefined; + isEnabled: boolean; + getCssModules(): Promise; +} + +export class CSSDevelopmentService implements ICSSDevelopmentService { + + declare _serviceBrand: undefined; + + private _cssModules?: Promise; + + constructor( + @IEnvironmentService private readonly envService: IEnvironmentService, + @ILogService private readonly logService: ILogService + ) { } + + get isEnabled(): boolean { + return !this.envService.isBuilt && isESM; + } + + getCssModules(): Promise { + this._cssModules ??= this.computeCssModules(); + return this._cssModules; + } + + private async computeCssModules(): Promise { + if (!this.isEnabled) { + return []; + } + + const rg = await import('@vscode/ripgrep'); + return await new Promise((resolve) => { + + const sw = StopWatch.create(); + + const chunks: string[][] = []; + const decoder = new TextDecoder(); + const basePath = FileAccess.asFileUri('').fsPath; + const process = spawn(rg.rgPath, ['-g', '**/*.css', '--files', '--no-ignore', basePath], {}); + + process.stdout.on('data', data => { + const chunk = decoder.decode(data, { stream: true }); + chunks.push(chunk.split('\n').filter(Boolean)); + }); + process.on('error', err => { + this.logService.error('[CSS_DEV] FAILED to compute CSS data', err); + resolve([]); + }); + process.on('close', () => { + const result = chunks.flat().map(path => relative(basePath, path).replace(/\\/g, '/')).filter(Boolean).sort(); + resolve(result); + this.logService.info(`[CSS_DEV] DONE, ${result.length} css modules (${Math.round(sw.elapsed())}ms)`); + }); + }); + } +} diff --git a/patched-vscode/src/vs/platform/diagnostics/common/diagnostics.ts b/patched-vscode/src/vs/platform/diagnostics/common/diagnostics.ts index 7d5af4f9..fb30b762 100644 --- a/patched-vscode/src/vs/platform/diagnostics/common/diagnostics.ts +++ b/patched-vscode/src/vs/platform/diagnostics/common/diagnostics.ts @@ -80,6 +80,8 @@ export interface WorkspaceStats { fileCount: number; maxFilesReached: boolean; launchConfigFiles: WorkspaceStatItem[]; + totalScanTime: number; + totalReaddirCount: number; } export interface PerformanceInfo { diff --git a/patched-vscode/src/vs/platform/diagnostics/node/diagnosticsService.ts b/patched-vscode/src/vs/platform/diagnostics/node/diagnosticsService.ts index 7ca4d9c2..dca3e1ed 100644 --- a/patched-vscode/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/patched-vscode/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -2,6 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; import * as osLib from 'os'; import { Promises } from 'vs/base/common/async'; import { getNodeType, parse, ParseError } from 'vs/base/common/json'; @@ -9,6 +11,7 @@ import { Schemas } from 'vs/base/common/network'; import { basename, join } from 'vs/base/common/path'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { ProcessItem } from 'vs/base/common/processes'; +import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { virtualMachineHint } from 'vs/base/node/id'; import { IDirent, Promises as pfs } from 'vs/base/node/pfs'; @@ -25,10 +28,10 @@ interface ConfigFilePatterns { relativePathPattern?: RegExp; } -const worksapceStatsCache = new Map>(); +const workspaceStatsCache = new Map>(); export async function collectWorkspaceStats(folder: string, filter: string[]): Promise { const cacheKey = `${folder}::${filter.join(':')}`; - const cached = worksapceStatsCache.get(cacheKey); + const cached = workspaceStatsCache.get(cacheKey); if (cached) { return cached; } @@ -52,7 +55,8 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P { tag: 'cmake', filePattern: /^.+\.cmake$/i }, { tag: 'github-actions', filePattern: /^.+\.ya?ml$/i, relativePathPattern: /^\.github(?:\/|\\)workflows$/i }, { tag: 'devcontainer.json', filePattern: /^devcontainer\.json$/i }, - { tag: 'dockerfile', filePattern: /^(dockerfile|docker\-compose\.ya?ml)$/i } + { tag: 'dockerfile', filePattern: /^(dockerfile|docker\-compose\.ya?ml)$/i }, + { tag: 'cursorrules', filePattern: /^\.cursorrules$/i }, ]; const fileTypes = new Map(); @@ -60,11 +64,13 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P const MAX_FILES = 20000; - function collect(root: string, dir: string, filter: string[], token: { count: number; maxReached: boolean }): Promise { + function collect(root: string, dir: string, filter: string[], token: { count: number; maxReached: boolean; readdirCount: number }): Promise { const relativePath = dir.substring(root.length + 1); return Promises.withAsyncBody(async resolve => { let files: IDirent[]; + + token.readdirCount++; try { files = await pfs.readdir(dir, { withFileTypes: true }); } catch (error) { @@ -130,8 +136,8 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P } const statsPromise = Promises.withAsyncBody(async (resolve) => { - const token: { count: number; maxReached: boolean } = { count: 0, maxReached: false }; - + const token: { count: number; maxReached: boolean; readdirCount: number } = { count: 0, maxReached: false, readdirCount: 0 }; + const sw = new StopWatch(true); await collect(folder, folder, filter, token); const launchConfigs = await collectLaunchConfigs(folder); resolve({ @@ -139,11 +145,13 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P fileTypes: asSortedItems(fileTypes), fileCount: token.count, maxFilesReached: token.maxReached, - launchConfigFiles: launchConfigs + launchConfigFiles: launchConfigs, + totalScanTime: sw.elapsed(), + totalReaddirCount: token.readdirCount }); }); - worksapceStatsCache.set(cacheKey, statsPromise); + workspaceStatsCache.set(cacheKey, statsPromise); return statsPromise; } @@ -173,7 +181,7 @@ export async function collectLaunchConfigs(folder: string): Promise(); const launchConfig = join(folder, '.vscode', 'launch.json'); - const contents = await pfs.readFile(launchConfig); + const contents = await fs.promises.readFile(launchConfig); const errors: ParseError[] = []; const json = parse(contents.toString(), errors); @@ -568,6 +576,23 @@ export class DiagnosticsService implements IDiagnosticsService { count: e.count }); }); + + // Workspace stats metadata + type WorkspaceStatsMetadataClassification = { + owner: 'jrieken'; + comment: 'Metadata about workspace metadata collection'; + duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'How did it take to make workspace stats' }; + reachedLimit: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Did making workspace stats reach its limits' }; + fileCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'How many files did workspace stats discover' }; + readdirCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'How many readdir call were needed' }; + }; + type WorkspaceStatsMetadata = { + duration: number; + reachedLimit: boolean; + fileCount: number; + readdirCount: number; + }; + this.telemetryService.publicLog2('workspace.stats.metadata', { duration: stats.totalScanTime, reachedLimit: stats.maxFilesReached, fileCount: stats.fileCount, readdirCount: stats.totalReaddirCount }); } catch { // Report nothing if collecting metadata fails. } diff --git a/patched-vscode/src/vs/platform/dialogs/electron-main/dialogMainService.ts b/patched-vscode/src/vs/platform/dialogs/electron-main/dialogMainService.ts index bc1230a4..2fffe78c 100644 --- a/patched-vscode/src/vs/platform/dialogs/electron-main/dialogMainService.ts +++ b/patched-vscode/src/vs/platform/dialogs/electron-main/dialogMainService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, dialog, FileFilter, MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron'; +import electron from 'electron'; import { Queue } from 'vs/base/common/async'; import { hash } from 'vs/base/common/hash'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; @@ -24,14 +24,14 @@ export interface IDialogMainService { readonly _serviceBrand: undefined; - pickFileFolder(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise; - pickFolder(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise; - pickFile(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise; - pickWorkspace(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise; + pickFileFolder(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise; + pickFolder(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise; + pickFile(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise; + pickWorkspace(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise; - showMessageBox(options: MessageBoxOptions, window?: BrowserWindow): Promise; - showSaveDialog(options: SaveDialogOptions, window?: BrowserWindow): Promise; - showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise; + showMessageBox(options: electron.MessageBoxOptions, window?: electron.BrowserWindow): Promise; + showSaveDialog(options: electron.SaveDialogOptions, window?: electron.BrowserWindow): Promise; + showOpenDialog(options: electron.OpenDialogOptions, window?: electron.BrowserWindow): Promise; } interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions { @@ -40,7 +40,7 @@ interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions { readonly title: string; readonly buttonLabel?: string; - readonly filters?: FileFilter[]; + readonly filters?: electron.FileFilter[]; } export class DialogMainService implements IDialogMainService { @@ -48,8 +48,8 @@ export class DialogMainService implements IDialogMainService { declare readonly _serviceBrand: undefined; private readonly windowFileDialogLocks = new Map>(); - private readonly windowDialogQueues = new Map>(); - private readonly noWindowDialogueQueue = new Queue(); + private readonly windowDialogQueues = new Map>(); + private readonly noWindowDialogueQueue = new Queue(); constructor( @ILogService private readonly logService: ILogService, @@ -57,19 +57,19 @@ export class DialogMainService implements IDialogMainService { ) { } - pickFileFolder(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise { + pickFileFolder(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise { return this.doPick({ ...options, pickFolders: true, pickFiles: true, title: localize('open', "Open") }, window); } - pickFolder(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise { + pickFolder(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise { return this.doPick({ ...options, pickFolders: true, title: localize('openFolder', "Open Folder") }, window); } - pickFile(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise { + pickFile(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise { return this.doPick({ ...options, pickFiles: true, title: localize('openFile', "Open File") }, window); } - pickWorkspace(options: INativeOpenDialogOptions, window?: BrowserWindow): Promise { + pickWorkspace(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise { const title = localize('openWorkspaceTitle', "Open Workspace from File"); const buttonLabel = mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")); const filters = WORKSPACE_FILTER; @@ -77,10 +77,10 @@ export class DialogMainService implements IDialogMainService { return this.doPick({ ...options, pickFiles: true, title, filters, buttonLabel }, window); } - private async doPick(options: IInternalNativeOpenDialogOptions, window?: BrowserWindow): Promise { + private async doPick(options: IInternalNativeOpenDialogOptions, window?: electron.BrowserWindow): Promise { // Ensure dialog options - const dialogOptions: OpenDialogOptions = { + const dialogOptions: electron.OpenDialogOptions = { title: options.title, buttonLabel: options.buttonLabel, filters: options.filters, @@ -105,7 +105,7 @@ export class DialogMainService implements IDialogMainService { } // Show Dialog - const result = await this.showOpenDialog(dialogOptions, (window || BrowserWindow.getFocusedWindow()) ?? undefined); + const result = await this.showOpenDialog(dialogOptions, (window || electron.BrowserWindow.getFocusedWindow()) ?? undefined); if (result && result.filePaths && result.filePaths.length > 0) { return result.filePaths; } @@ -113,14 +113,14 @@ export class DialogMainService implements IDialogMainService { return undefined; } - private getWindowDialogQueue(window?: BrowserWindow): Queue { + private getWindowDialogQueue(window?: electron.BrowserWindow): Queue { // Queue message box requests per window so that one can show // after the other. if (window) { let windowDialogQueue = this.windowDialogQueues.get(window.id); if (!windowDialogQueue) { - windowDialogQueue = new Queue(); + windowDialogQueue = new Queue(); this.windowDialogQueues.set(window.id, windowDialogQueue); } @@ -130,15 +130,15 @@ export class DialogMainService implements IDialogMainService { } } - showMessageBox(rawOptions: MessageBoxOptions, window?: BrowserWindow): Promise { - return this.getWindowDialogQueue(window).queue(async () => { + showMessageBox(rawOptions: electron.MessageBoxOptions, window?: electron.BrowserWindow): Promise { + return this.getWindowDialogQueue(window).queue(async () => { const { options, buttonIndeces } = massageMessageBoxOptions(rawOptions, this.productService); - let result: MessageBoxReturnValue | undefined = undefined; + let result: electron.MessageBoxReturnValue | undefined = undefined; if (window) { - result = await dialog.showMessageBox(window, options); + result = await electron.dialog.showMessageBox(window, options); } else { - result = await dialog.showMessageBox(options); + result = await electron.dialog.showMessageBox(options); } return { @@ -148,7 +148,7 @@ export class DialogMainService implements IDialogMainService { }); } - async showSaveDialog(options: SaveDialogOptions, window?: BrowserWindow): Promise { + async showSaveDialog(options: electron.SaveDialogOptions, window?: electron.BrowserWindow): Promise { // Prevent duplicates of the same dialog queueing at the same time const fileDialogLock = this.acquireFileDialogLock(options, window); @@ -159,12 +159,12 @@ export class DialogMainService implements IDialogMainService { } try { - return await this.getWindowDialogQueue(window).queue(async () => { - let result: SaveDialogReturnValue; + return await this.getWindowDialogQueue(window).queue(async () => { + let result: electron.SaveDialogReturnValue; if (window) { - result = await dialog.showSaveDialog(window, options); + result = await electron.dialog.showSaveDialog(window, options); } else { - result = await dialog.showSaveDialog(options); + result = await electron.dialog.showSaveDialog(options); } result.filePath = this.normalizePath(result.filePath); @@ -190,7 +190,7 @@ export class DialogMainService implements IDialogMainService { return paths.map(path => this.normalizePath(path)); } - async showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise { + async showOpenDialog(options: electron.OpenDialogOptions, window?: electron.BrowserWindow): Promise { // Ensure the path exists (if provided) if (options.defaultPath) { @@ -209,12 +209,12 @@ export class DialogMainService implements IDialogMainService { } try { - return await this.getWindowDialogQueue(window).queue(async () => { - let result: OpenDialogReturnValue; + return await this.getWindowDialogQueue(window).queue(async () => { + let result: electron.OpenDialogReturnValue; if (window) { - result = await dialog.showOpenDialog(window, options); + result = await electron.dialog.showOpenDialog(window, options); } else { - result = await dialog.showOpenDialog(options); + result = await electron.dialog.showOpenDialog(options); } result.filePaths = this.normalizePaths(result.filePaths); @@ -226,7 +226,7 @@ export class DialogMainService implements IDialogMainService { } } - private acquireFileDialogLock(options: SaveDialogOptions | OpenDialogOptions, window?: BrowserWindow): IDisposable | undefined { + private acquireFileDialogLock(options: electron.SaveDialogOptions | electron.OpenDialogOptions, window?: electron.BrowserWindow): IDisposable | undefined { // If no window is provided, allow as many dialogs as // needed since we consider them not modal per window diff --git a/patched-vscode/src/vs/platform/encryption/electron-main/encryptionMainService.ts b/patched-vscode/src/vs/platform/encryption/electron-main/encryptionMainService.ts index c937778c..423f0a2b 100644 --- a/patched-vscode/src/vs/platform/encryption/electron-main/encryptionMainService.ts +++ b/patched-vscode/src/vs/platform/encryption/electron-main/encryptionMainService.ts @@ -25,12 +25,14 @@ export class EncryptionMainService implements IEncryptionMainService { ) { // if this commandLine switch is set, the user has opted in to using basic text encryption if (app.commandLine.getSwitchValue('password-store') === PasswordStoreCLIOption.basic) { + this.logService.trace('[EncryptionMainService] setting usePlainTextEncryption to true...'); safeStorage.setUsePlainTextEncryption?.(true); + this.logService.trace('[EncryptionMainService] set usePlainTextEncryption to true'); } } async encrypt(value: string): Promise { - this.logService.trace('[EncryptionMainService] Encrypting value.'); + this.logService.trace('[EncryptionMainService] Encrypting value...'); try { const result = JSON.stringify(safeStorage.encryptString(value)); this.logService.trace('[EncryptionMainService] Encrypted value.'); @@ -50,7 +52,7 @@ export class EncryptionMainService implements IEncryptionMainService { } const bufferToDecrypt = Buffer.from(parsedValue.data); - this.logService.trace('[EncryptionMainService] Decrypting value.'); + this.logService.trace('[EncryptionMainService] Decrypting value...'); const result = safeStorage.decryptString(bufferToDecrypt); this.logService.trace('[EncryptionMainService] Decrypted value.'); return result; @@ -61,7 +63,10 @@ export class EncryptionMainService implements IEncryptionMainService { } isEncryptionAvailable(): Promise { - return Promise.resolve(safeStorage.isEncryptionAvailable()); + this.logService.trace('[EncryptionMainService] Checking if encryption is available...'); + const result = safeStorage.isEncryptionAvailable(); + this.logService.trace('[EncryptionMainService] Encryption is available: ', result); + return Promise.resolve(result); } getKeyStorageProvider(): Promise { @@ -73,7 +78,9 @@ export class EncryptionMainService implements IEncryptionMainService { } if (safeStorage.getSelectedStorageBackend) { try { + this.logService.trace('[EncryptionMainService] Getting selected storage backend...'); const result = safeStorage.getSelectedStorageBackend() as KnownStorageProvider; + this.logService.trace('[EncryptionMainService] Selected storage backend: ', result); return Promise.resolve(result); } catch (e) { this.logService.error(e); @@ -95,6 +102,8 @@ export class EncryptionMainService implements IEncryptionMainService { throw new Error('Setting plain text encryption is not supported.'); } + this.logService.trace('[EncryptionMainService] Setting usePlainTextEncryption to true...'); safeStorage.setUsePlainTextEncryption(true); + this.logService.trace('[EncryptionMainService] Set usePlainTextEncryption to true'); } } diff --git a/patched-vscode/src/vs/platform/environment/common/argv.ts b/patched-vscode/src/vs/platform/environment/common/argv.ts index d8508f30..818021ff 100644 --- a/patched-vscode/src/vs/platform/environment/common/argv.ts +++ b/patched-vscode/src/vs/platform/environment/common/argv.ts @@ -145,6 +145,4 @@ export interface NativeParsedArgs { 'trace-startup-format'?: string; 'trace-startup-file'?: string; 'trace-startup-duration'?: string; - 'use-gl'?: string; - 'use-angle'?: string; } diff --git a/patched-vscode/src/vs/platform/environment/electron-main/environmentMainService.ts b/patched-vscode/src/vs/platform/environment/electron-main/environmentMainService.ts index 748ff075..dec04406 100644 --- a/patched-vscode/src/vs/platform/environment/electron-main/environmentMainService.ts +++ b/patched-vscode/src/vs/platform/environment/electron-main/environmentMainService.ts @@ -19,9 +19,6 @@ export const IEnvironmentMainService = refineServiceDecorator = {}; - @memoize - get cachedLanguagesPath(): string { return join(this.userDataPath, 'clp'); } - @memoize get backupHome(): string { return join(this.userDataPath, 'Backups'); } diff --git a/patched-vscode/src/vs/platform/environment/node/argv.ts b/patched-vscode/src/vs/platform/environment/node/argv.ts index 0984c8da..16a942af 100644 --- a/patched-vscode/src/vs/platform/environment/node/argv.ts +++ b/patched-vscode/src/vs/platform/environment/node/argv.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as minimist from 'minimist'; +import minimist from 'minimist'; import { isWindows } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; @@ -209,8 +209,6 @@ export const OPTIONS: OptionDescriptions> = { 'trace-startup-format': { type: 'string' }, 'trace-startup-file': { type: 'string' }, 'trace-startup-duration': { type: 'string' }, - 'use-gl': { type: 'string' }, - 'use-angle': { type: 'string' }, _: { type: 'string[]' } // main arguments }; diff --git a/patched-vscode/src/vs/platform/environment/node/argvHelper.ts b/patched-vscode/src/vs/platform/environment/node/argvHelper.ts index d8cefb6d..a94fca91 100644 --- a/patched-vscode/src/vs/platform/environment/node/argvHelper.ts +++ b/patched-vscode/src/vs/platform/environment/node/argvHelper.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; diff --git a/patched-vscode/src/vs/platform/environment/node/stdin.ts b/patched-vscode/src/vs/platform/environment/node/stdin.ts index 56ab1514..b3e71e04 100644 --- a/patched-vscode/src/vs/platform/environment/node/stdin.ts +++ b/patched-vscode/src/vs/platform/environment/node/stdin.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { tmpdir } from 'os'; import { Queue } from 'vs/base/common/async'; import { randomPath } from 'vs/base/common/extpath'; -import { Promises } from 'vs/base/node/pfs'; import { resolveTerminalEncoding } from 'vs/base/node/terminalEncoding'; export function hasStdinWithoutTty() { @@ -43,7 +43,7 @@ export async function readFromStdin(targetPath: string, verbose: boolean, onEnd? let [encoding, iconv] = await Promise.all([ resolveTerminalEncoding(verbose), // respect terminal encoding when piping into file import('@vscode/iconv-lite-umd'), // lazy load encoding module for usage - Promises.appendFile(targetPath, '') // make sure file exists right away (https://github.com/microsoft/vscode/issues/155341) + fs.promises.appendFile(targetPath, '') // make sure file exists right away (https://github.com/microsoft/vscode/issues/155341) ]); if (!iconv.encodingExists(encoding)) { @@ -63,7 +63,7 @@ export async function readFromStdin(targetPath: string, verbose: boolean, onEnd? process.stdin.on('data', chunk => { const chunkStr = decoder.write(chunk); - appendFileQueue.queue(() => Promises.appendFile(targetPath, chunkStr)); + appendFileQueue.queue(() => fs.promises.appendFile(targetPath, chunkStr)); }); process.stdin.on('end', () => { @@ -72,7 +72,7 @@ export async function readFromStdin(targetPath: string, verbose: boolean, onEnd? appendFileQueue.queue(async () => { try { if (typeof end === 'string') { - await Promises.appendFile(targetPath, end); + await fs.promises.appendFile(targetPath, end); } } finally { onEnd?.(); diff --git a/patched-vscode/src/vs/platform/environment/node/userDataPath.js b/patched-vscode/src/vs/platform/environment/node/userDataPath.js index 92898523..2248dce2 100644 --- a/patched-vscode/src/vs/platform/environment/node/userDataPath.js +++ b/patched-vscode/src/vs/platform/environment/node/userDataPath.js @@ -6,12 +6,23 @@ /// //@ts-check +'use strict'; + +// ESM-uncomment-begin +// import * as os from 'os'; +// import * as path from 'path'; +// +// /** @type any */ +// const module = { exports: {} }; +// ESM-uncomment-end + (function () { - 'use strict'; /** - * @typedef {import('../../environment/common/argv').NativeParsedArgs} NativeParsedArgs - * + * @import { NativeParsedArgs } from '../../environment/common/argv' + */ + + /** * @param {typeof import('path')} path * @param {typeof import('os')} os * @param {string} cwd @@ -115,11 +126,17 @@ return factory(path, os, process.cwd()); // amd }); } else if (typeof module === 'object' && typeof module.exports === 'object') { + // ESM-comment-begin const path = require('path'); const os = require('os'); + // ESM-comment-end module.exports = factory(path, os, process.env['VSCODE_CWD'] || process.cwd()); // commonjs } else { throw new Error('Unknown context'); } }()); + +// ESM-uncomment-begin +// export const getUserDataPath = module.exports.getUserDataPath; +// ESM-uncomment-end diff --git a/patched-vscode/src/vs/platform/environment/test/electron-main/environmentMainService.test.ts b/patched-vscode/src/vs/platform/environment/test/electron-main/environmentMainService.test.ts index 78fd7354..268f5ce5 100644 --- a/patched-vscode/src/vs/platform/environment/test/electron-main/environmentMainService.test.ts +++ b/patched-vscode/src/vs/platform/environment/test/electron-main/environmentMainService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { EnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import product from 'vs/platform/product/common/product'; import { isLinux } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/platform/environment/test/node/argv.test.ts b/patched-vscode/src/vs/platform/environment/test/node/argv.test.ts index a188b52b..a82be960 100644 --- a/patched-vscode/src/vs/platform/environment/test/node/argv.test.ts +++ b/patched-vscode/src/vs/platform/environment/test/node/argv.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { formatOptions, Option, OptionDescriptions, Subcommand, parseArgs, ErrorReporter } from 'vs/platform/environment/node/argv'; import { addArg } from 'vs/platform/environment/node/argvHelper'; diff --git a/patched-vscode/src/vs/platform/environment/test/node/environmentService.test.ts b/patched-vscode/src/vs/platform/environment/test/node/environmentService.test.ts index ffe418fc..6f256621 100644 --- a/patched-vscode/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/patched-vscode/src/vs/platform/environment/test/node/environmentService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { parseExtensionHostDebugPort } from 'vs/platform/environment/common/environmentService'; import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv'; diff --git a/patched-vscode/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts b/patched-vscode/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts index 68128a21..e9b5ba5c 100644 --- a/patched-vscode/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts +++ b/patched-vscode/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { isWindows } from 'vs/base/common/platform'; +import assert from 'assert'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { flakySuite } from 'vs/base/test/common/testUtils'; function testErrorMessage(module: string): string { @@ -13,13 +13,33 @@ function testErrorMessage(module: string): string { flakySuite('Native Modules (all platforms)', () => { - test('kerberos', async () => { - const kerberos = await import('kerberos'); + (isMacintosh ? test.skip : test)('kerberos', async () => { // Somehow fails on macOS ARM? + const { default: kerberos } = await import('kerberos'); assert.ok(typeof kerberos.initializeClient === 'function', testErrorMessage('kerberos')); }); + test('minimist', async () => { + const { default: minimist } = await import('minimist'); + assert.ok(typeof minimist === 'function', testErrorMessage('minimist')); + }); + + test('yauzl', async () => { + const { default: yauzl } = await import('yauzl'); + assert.ok(typeof yauzl.ZipFile === 'function', testErrorMessage('yauzl')); + }); + + test('yazl', async () => { + const { default: yazl } = await import('yazl'); + assert.ok(typeof yazl.ZipFile === 'function', testErrorMessage('yazl')); + }); + + test('v8-inspect-profiler', async () => { + const { default: profiler } = await import('v8-inspect-profiler'); + assert.ok(typeof profiler.startProfiling === 'function', testErrorMessage('v8-inspect-profiler')); + }); + test('native-is-elevated', async () => { - const isElevated = await import('native-is-elevated'); + const { default: isElevated } = await import('native-is-elevated'); assert.ok(typeof isElevated === 'function', testErrorMessage('native-is-elevated ')); const result = isElevated(); @@ -28,6 +48,7 @@ flakySuite('Native Modules (all platforms)', () => { test('native-keymap', async () => { const keyMap = await import('native-keymap'); + assert.ok(typeof keyMap.onDidChangeKeyboardLayout === 'function', testErrorMessage('native-keymap')); assert.ok(typeof keyMap.getCurrentKeyboardLayout === 'function', testErrorMessage('native-keymap')); const result = keyMap.getCurrentKeyboardLayout(); @@ -39,12 +60,27 @@ flakySuite('Native Modules (all platforms)', () => { assert.ok(typeof watchDog.start === 'function', testErrorMessage('native-watchdog')); }); - (process.type === 'renderer' ? test.skip /* TODO@electron module is not context aware yet and thus cannot load in Electron renderer used by tests */ : test)('node-pty', async () => { + test('@vscode/sudo-prompt', async () => { + const prompt = await import('@vscode/sudo-prompt'); + assert.ok(typeof prompt.exec === 'function', testErrorMessage('@vscode/sudo-prompt')); + }); + + test('@vscode/policy-watcher', async () => { + const watcher = await import('@vscode/policy-watcher'); + assert.ok(typeof watcher.createWatcher === 'function', testErrorMessage('@vscode/policy-watcher')); + }); + + test('node-pty', async () => { const nodePty = await import('node-pty'); assert.ok(typeof nodePty.spawn === 'function', testErrorMessage('node-pty')); }); - (process.type === 'renderer' ? test.skip /* TODO@electron module is not context aware yet and thus cannot load in Electron renderer used by tests */ : test)('@vscode/spdlog', async () => { + test('open', async () => { + const { default: open } = await import('open'); + assert.ok(typeof open === 'function', testErrorMessage('open')); + }); + + test('@vscode/spdlog', async () => { const spdlog = await import('@vscode/spdlog'); assert.ok(typeof spdlog.createRotatingLogger === 'function', testErrorMessage('@vscode/spdlog')); assert.ok(typeof spdlog.version === 'number', testErrorMessage('@vscode/spdlog')); @@ -55,31 +91,74 @@ flakySuite('Native Modules (all platforms)', () => { assert.ok(typeof parcelWatcher.subscribe === 'function', testErrorMessage('@parcel/watcher')); }); + test('@vscode/deviceid', async () => { + const deviceIdPackage = await import('@vscode/deviceid'); + assert.ok(typeof deviceIdPackage.getDeviceId === 'function', testErrorMessage('@vscode/deviceid')); + }); + + test('@vscode/ripgrep', async () => { + const ripgrep = await import('@vscode/ripgrep'); + assert.ok(typeof ripgrep.rgPath === 'string', testErrorMessage('@vscode/ripgrep')); + }); + + test('vscode-regexpp', async () => { + const regexpp = await import('vscode-regexpp'); + assert.ok(typeof regexpp.RegExpParser === 'function', testErrorMessage('vscode-regexpp')); + }); + test('@vscode/sqlite3', async () => { + // ESM-comment-begin const sqlite3 = await import('@vscode/sqlite3'); + // ESM-comment-end + // ESM-uncomment-begin + // const { default: sqlite3 } = await import('@vscode/sqlite3'); + // ESM-uncomment-end assert.ok(typeof sqlite3.Database === 'function', testErrorMessage('@vscode/sqlite3')); }); - test('vsda', async () => { - try { - const vsda: any = globalThis._VSCODE_NODE_MODULES['vsda']; - const signer = new vsda.signer(); - const signed = await signer.sign('value'); - assert.ok(typeof signed === 'string', testErrorMessage('vsda')); - } catch (error) { - if (error.code !== 'MODULE_NOT_FOUND') { - throw error; + test('http-proxy-agent', async () => { + // ESM-comment-begin + const mod = await import('http-proxy-agent'); + // ESM-comment-end + // ESM-uncomment-begin + // const { default: mod } = await import('http-proxy-agent'); + // ESM-uncomment-end + assert.ok(typeof mod.HttpProxyAgent === 'function', testErrorMessage('http-proxy-agent')); + }); + + test('https-proxy-agent', async () => { + // ESM-comment-begin + const mod = await import('https-proxy-agent'); + // ESM-comment-end + // ESM-uncomment-begin + // const { default: mod } = await import('https-proxy-agent'); + // ESM-uncomment-end + assert.ok(typeof mod.HttpsProxyAgent === 'function', testErrorMessage('https-proxy-agent')); + }); + + test('@vscode/proxy-agent', async () => { + const proxyAgent = await import('@vscode/proxy-agent'); + // This call will load `@vscode/proxy-agent` which is a native module that we want to test on Windows + const windowsCerts = await proxyAgent.loadSystemCertificates({ + log: { + trace: () => { }, + debug: () => { }, + info: () => { }, + warn: () => { }, + error: () => { } } - } + }); + assert.ok(windowsCerts.length > 0, testErrorMessage('@vscode/proxy-agent')); }); }); (!isWindows ? suite.skip : suite)('Native Modules (Windows)', () => { - (process.type === 'renderer' ? test.skip /* TODO@electron module is not context aware yet and thus cannot load in Electron renderer used by tests */ : test)('@vscode/windows-mutex', async () => { + test('@vscode/windows-mutex', async () => { const mutex = await import('@vscode/windows-mutex'); assert.ok(mutex && typeof mutex.isActive === 'function', testErrorMessage('@vscode/windows-mutex')); assert.ok(typeof mutex.isActive === 'function', testErrorMessage('@vscode/windows-mutex')); + assert.ok(typeof mutex.Mutex === 'function', testErrorMessage('@vscode/windows-mutex')); }); test('windows-foreground-love', async () => { @@ -112,22 +191,4 @@ flakySuite('Native Modules (all platforms)', () => { const result = windowsRegistry.GetStringRegKey('HKEY_LOCAL_MACHINE', 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion', 'EditionID'); assert.ok(typeof result === 'string' || typeof result === 'undefined', testErrorMessage('@vscode/windows-registry')); }); - - test('@vscode/windows-ca-certs', async () => { - // @ts-ignore we do not directly depend on this module anymore - // but indirectly from our dependency to `@vscode/proxy-agent` - // we still want to ensure this module can work properly. - const windowsCerts = await import('@vscode/windows-ca-certs'); - const store = new windowsCerts.Crypt32(); - assert.ok(windowsCerts, testErrorMessage('@vscode/windows-ca-certs')); - let certCount = 0; - try { - while (store.next()) { - certCount++; - } - } finally { - store.done(); - } - assert(certCount > 0); - }); }); diff --git a/patched-vscode/src/vs/platform/environment/test/node/userDataPath.test.ts b/patched-vscode/src/vs/platform/environment/test/node/userDataPath.test.ts index 644260cf..72278e46 100644 --- a/patched-vscode/src/vs/platform/environment/test/node/userDataPath.test.ts +++ b/patched-vscode/src/vs/platform/environment/test/node/userDataPath.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv'; import { getUserDataPath } from 'vs/platform/environment/node/userDataPath'; diff --git a/patched-vscode/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/patched-vscode/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 2ac86759..42f230be 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -9,6 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationError, getErrorMessage, isCancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; import { isWeb } from 'vs/base/common/platform'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -18,10 +19,13 @@ import { IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError, IProductVersion, ExtensionGalleryErrorCode, - EXTENSION_INSTALL_SOURCE_CONTEXT + EXTENSION_INSTALL_SOURCE_CONTEXT, + DidUpdateExtensionMetadata, + UninstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { areApiProposalsCompatible } from 'vs/platform/extensions/common/extensionValidator'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -46,6 +50,7 @@ export interface IInstallExtensionTask { export type UninstallExtensionTaskOptions = UninstallOptions & { readonly profileLocation: URI }; export interface IUninstallExtensionTask { + readonly options: UninstallExtensionTaskOptions; readonly extension: ILocalExtension; run(): Promise; waitUntilTaskIsFinished(): Promise; @@ -73,7 +78,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl protected _onDidUninstallExtension = this._register(new Emitter()); get onDidUninstallExtension() { return this._onDidUninstallExtension.event; } - protected readonly _onDidUpdateExtensionMetadata = this._register(new Emitter()); + protected readonly _onDidUpdateExtensionMetadata = this._register(new Emitter()); get onDidUpdateExtensionMetadata() { return this._onDidUpdateExtensionMetadata.event; } private readonly participants: IExtensionManagementParticipant[] = []; @@ -129,7 +134,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion, options.productVersion ?? { version: this.productService.version, date: this.productService.date }); installableExtensions.push({ ...compatible, options }); } catch (error) { - results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error }); + results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error, profileLocation: options.profileLocation ?? this.getCurrentExtensionsManifestLocation() }); } })); @@ -140,9 +145,9 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return results; } - async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise { + async uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id); - return this.uninstallExtension(extension, options); + return this.uninstallExtensions([{ extension, options }]); } async toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise { @@ -160,7 +165,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const existing = (await this.getInstalled(ExtensionType.User, profile.extensionsResource)) .find(e => areSameExtensions(e.identifier, extension.identifier)); if (existing) { - this._onDidUpdateExtensionMetadata.fire(existing); + this._onDidUpdateExtensionMetadata.fire({ local: existing, profileLocation: profile.extensionsResource }); } else { this._onDidUninstallExtension.fire({ identifier: extension.identifier, profileLocation: profile.extensionsResource }); } @@ -194,6 +199,24 @@ export abstract class AbstractExtensionManagementService extends Disposable impl this.participants.push(participant); } + async resetPinnedStateForAllUserExtensions(pinned: boolean): Promise { + try { + await this.joinAllSettled(this.userDataProfilesService.profiles.map( + async profile => { + const extensions = await this.getInstalled(ExtensionType.User, profile.extensionsResource); + await this.joinAllSettled(extensions.map( + async extension => { + if (extension.pinned !== pinned) { + await this.updateMetadata(extension, { pinned }, profile.extensionsResource); + } + })); + })); + } catch (error) { + this.logService.error('Error while resetting pinned state for all user extensions', getErrorMessage(error)); + throw error; + } + } + protected async installExtensions(extensions: InstallableExtension[]): Promise { const installExtensionResultsMap = new Map(); const installingExtensionsMap = new Map(); @@ -205,7 +228,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const key = `${getGalleryExtensionId(manifest.publisher, manifest.name)}-${options.profileLocation.toString()}`; installingExtensionsMap.set(key, { task: installExtensionTask, root }); this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation }); - this.logService.info('Installing extension:', installExtensionTask.identifier.id, options.profileLocation.toString()); + this.logService.info('Installing extension:', installExtensionTask.identifier.id, options); // only cache gallery extensions tasks if (!URI.isUri(extension)) { this.installingExtensions.set(getInstallExtensionTaskKey(extension, options.profileLocation), { task: installExtensionTask, waitingTasks: [] }); @@ -547,6 +570,10 @@ export abstract class AbstractExtensionManagementService extends Disposable impl compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease, productVersion); if (!compatibleExtension) { + const incompatibleApiProposalsMessages: string[] = []; + if (!areApiProposalsCompatible(extension.properties.enabledApiProposals ?? [], incompatibleApiProposalsMessages)) { + throw new ExtensionManagementError(nls.localize('incompatibleAPI', "Can't install '{0}' extension. {1}", extension.displayName ?? extension.identifier.id, incompatibleApiProposalsMessages[0]), ExtensionManagementErrorCode.IncompatibleApi); + } /** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */ if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) { throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.displayName ?? extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound); @@ -591,43 +618,23 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return compatibleExtension; } - private async uninstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise { - const uninstallOptions: UninstallExtensionTaskOptions = { - ...options, - profileLocation: extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation() - }; - const getUninstallExtensionTaskKey = (identifier: IExtensionIdentifier) => `${identifier.id.toLowerCase()}${uninstallOptions.versionOnly ? `-${extension.manifest.version}` : ''}${uninstallOptions.profileLocation ? `@${uninstallOptions.profileLocation.toString()}` : ''}`; - const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension.identifier)); - if (uninstallExtensionTask) { - this.logService.info('Extensions is already requested to uninstall', extension.identifier.id); - return uninstallExtensionTask.waitUntilTaskIsFinished(); - } + async uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise { + + const getUninstallExtensionTaskKey = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions) => `${extension.identifier.id.toLowerCase()}${uninstallOptions.versionOnly ? `-${extension.manifest.version}` : ''}@${uninstallOptions.profileLocation.toString()}`; - const createUninstallExtensionTask = (extension: ILocalExtension): IUninstallExtensionTask => { + const createUninstallExtensionTask = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions): IUninstallExtensionTask => { const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions); - this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension.identifier), uninstallExtensionTask); - if (uninstallOptions.profileLocation) { - this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString()); - } else { - this.logService.info('Uninstalling extension:', `${extension.identifier.id}@${extension.manifest.version}`); - } + this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension, uninstallOptions), uninstallExtensionTask); + this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString()); this._onUninstallExtension.fire({ identifier: extension.identifier, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped }); return uninstallExtensionTask; }; - const postUninstallExtension = (extension: ILocalExtension, error?: ExtensionManagementError): void => { + const postUninstallExtension = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions, error?: ExtensionManagementError): void => { if (error) { - if (uninstallOptions.profileLocation) { - this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString(), error.message); - } else { - this.logService.error('Failed to uninstall extension:', `${extension.identifier.id}@${extension.manifest.version}`, error.message); - } + this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString(), error.message); } else { - if (uninstallOptions.profileLocation) { - this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString()); - } else { - this.logService.info('Successfully uninstalled extension:', `${extension.identifier.id}@${extension.manifest.version}`); - } + this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString()); } reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error }); this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped }); @@ -635,64 +642,91 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const allTasks: IUninstallExtensionTask[] = []; const processedTasks: IUninstallExtensionTask[] = []; + const alreadyRequestedUninstalls: Promise[] = []; - try { - allTasks.push(createUninstallExtensionTask(extension)); - const installed = await this.getInstalled(ExtensionType.User, uninstallOptions.profileLocation); - if (uninstallOptions.donotIncludePack) { - this.logService.info('Uninstalling the extension without including packed extension', `${extension.identifier.id}@${extension.manifest.version}`); + const installedExtensionsMap = new ResourceMap(); + + for (const { extension, options } of extensions) { + const uninstallOptions: UninstallExtensionTaskOptions = { + ...options, + profileLocation: extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options?.profileLocation ?? this.getCurrentExtensionsManifestLocation() + }; + const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension, uninstallOptions)); + if (uninstallExtensionTask) { + this.logService.info('Extensions is already requested to uninstall', extension.identifier.id); + alreadyRequestedUninstalls.push(uninstallExtensionTask.waitUntilTaskIsFinished()); } else { - const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed); - for (const packedExtension of packedExtensions) { - if (this.uninstallingExtensions.has(getUninstallExtensionTaskKey(packedExtension.identifier))) { - this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id); - } else { - allTasks.push(createUninstallExtensionTask(packedExtension)); - } - } + allTasks.push(createUninstallExtensionTask(extension, uninstallOptions)); } + } - if (uninstallOptions.donotCheckDependents) { - this.logService.info('Uninstalling the extension without checking dependents', `${extension.identifier.id}@${extension.manifest.version}`); - } else { - this.checkForDependents(allTasks.map(task => task.extension), installed, extension); + try { + for (const task of allTasks.slice(0)) { + let installed = installedExtensionsMap.get(task.options.profileLocation); + if (!installed) { + installedExtensionsMap.set(task.options.profileLocation, installed = await this.getInstalled(ExtensionType.User, task.options.profileLocation)); + } + + if (task.options.donotIncludePack) { + this.logService.info('Uninstalling the extension without including packed extension', `${task.extension.identifier.id}@${task.extension.manifest.version}`); + } else { + const packedExtensions = this.getAllPackExtensionsToUninstall(task.extension, installed); + for (const packedExtension of packedExtensions) { + if (this.uninstallingExtensions.has(getUninstallExtensionTaskKey(packedExtension, task.options))) { + this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id); + } else { + allTasks.push(createUninstallExtensionTask(packedExtension, task.options)); + } + } + } + if (task.options.donotCheckDependents) { + this.logService.info('Uninstalling the extension without checking dependents', `${task.extension.identifier.id}@${task.extension.manifest.version}`); + } else { + this.checkForDependents(allTasks.map(task => task.extension), installed, task.extension); + } } // Uninstall extensions in parallel and wait until all extensions are uninstalled / failed await this.joinAllSettled(allTasks.map(async task => { try { await task.run(); - await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, uninstallOptions, CancellationToken.None))); + await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, task.options, CancellationToken.None))); // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. if (task.extension.identifier.uuid) { try { await this.galleryService.reportStatistic(task.extension.manifest.publisher, task.extension.manifest.name, task.extension.manifest.version, StatisticType.Uninstall); } catch (error) { /* ignore */ } } - postUninstallExtension(task.extension); } catch (e) { const error = toExtensionManagementError(e); - postUninstallExtension(task.extension, error); + postUninstallExtension(task.extension, task.options, error); throw error; } finally { processedTasks.push(task); } })); + if (alreadyRequestedUninstalls.length) { + await this.joinAllSettled(alreadyRequestedUninstalls); + } + + for (const task of allTasks) { + postUninstallExtension(task.extension, task.options); + } } catch (e) { const error = toExtensionManagementError(e); for (const task of allTasks) { // cancel the tasks try { task.cancel(); } catch (error) { /* ignore */ } if (!processedTasks.includes(task)) { - postUninstallExtension(task.extension, error); + postUninstallExtension(task.extension, task.options, error); } } throw error; } finally { // Remove tasks from cache for (const task of allTasks) { - if (!this.uninstallingExtensions.delete(getUninstallExtensionTaskKey(task.extension.identifier))) { + if (!this.uninstallingExtensions.delete(getUninstallExtensionTaskKey(task.extension, task.options))) { this.logService.warn('Uninstallation task is not found in the cache', task.extension.identifier.id); } } @@ -773,7 +807,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract getTargetPlatform(): Promise; abstract zip(extension: ILocalExtension): Promise; - abstract unzip(zipLocation: URI): Promise; abstract getManifest(vsix: URI): Promise; abstract install(vsix: URI, options?: InstallOptions): Promise; abstract installFromLocation(location: URI, profileLocation: URI): Promise; @@ -784,7 +817,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract reinstallFromGallery(extension: ILocalExtension): Promise; abstract cleanUp(): Promise; - abstract updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise; + abstract updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI): Promise; protected abstract getCurrentExtensionsManifestLocation(): URI; protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask; diff --git a/patched-vscode/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/patched-vscode/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 61c8816e..7637899a 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -16,15 +16,15 @@ export class GlobalExtensionEnablementService extends Disposable implements IGlo private _onDidChangeEnablement = new Emitter<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }>(); readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }> = this._onDidChangeEnablement.event; - private readonly storageManger: StorageManager; + private readonly storageManager: StorageManager; constructor( @IStorageService storageService: IStorageService, @IExtensionManagementService extensionManagementService: IExtensionManagementService, ) { super(); - this.storageManger = this._register(new StorageManager(storageService)); - this._register(this.storageManger.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' }))); + this.storageManager = this._register(new StorageManager(storageService)); + this._register(this.storageManager.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' }))); this._register(extensionManagementService.onDidInstallExtensions(e => e.forEach(({ local, operation }) => { if (local && operation === InstallOperation.Migrate) { this._removeFromDisabledExtensions(local.identifier); /* Reset migrated extensions */ @@ -84,11 +84,11 @@ export class GlobalExtensionEnablementService extends Disposable implements IGlo } private _getExtensions(storageId: string): IExtensionIdentifier[] { - return this.storageManger.get(storageId, StorageScope.PROFILE); + return this.storageManager.get(storageId, StorageScope.PROFILE); } private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void { - this.storageManger.set(storageId, extensions, StorageScope.PROFILE); + this.storageManager.set(storageId, extensions, StorageScope.PROFILE); } } diff --git a/patched-vscode/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/patched-vscode/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 5548b8ee..36565d2e 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -12,13 +12,13 @@ import { isWeb, platform } from 'vs/base/common/platform'; import { arch } from 'vs/base/common/process'; import { isBoolean } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; +import { IHeaders, IRequestContext, IRequestOptions, isOfflineError } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; -import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; +import { areApiProposalsCompatible, isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -209,10 +209,12 @@ const PropertyType = { ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack', Engine: 'Microsoft.VisualStudio.Code.Engine', PreRelease: 'Microsoft.VisualStudio.Code.PreRelease', + EnabledApiProposals: 'Microsoft.VisualStudio.Code.EnabledApiProposals', LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages', WebExtension: 'Microsoft.VisualStudio.Code.WebExtension', SponsorLink: 'Microsoft.VisualStudio.Code.SponsorLink', SupportLink: 'Microsoft.VisualStudio.Services.Links.Support', + ExecutesCode: 'Microsoft.VisualStudio.Code.ExecutesCode', }; interface ICriterium { @@ -430,6 +432,17 @@ function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean { return values.length > 0 && values[0].value === 'true'; } +function executesCode(version: IRawGalleryExtensionVersion): boolean | undefined { + const values = version.properties ? version.properties.filter(p => p.key === PropertyType.ExecutesCode) : []; + return values.length > 0 ? values[0].value === 'true' : undefined; +} + +function getEnabledApiProposals(version: IRawGalleryExtensionVersion): string[] { + const values = version.properties ? version.properties.filter(p => p.key === PropertyType.EnabledApiProposals) : []; + const value = (values.length > 0 && values[0].value) || ''; + return value ? value.split(',') : []; +} + function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] { const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : []; const value = (values.length > 0 && values[0].value) || ''; @@ -548,9 +561,11 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller dependencies: getExtensions(version, PropertyType.Dependency), extensionPack: getExtensions(version, PropertyType.ExtensionPack), engine: getEngine(version), + enabledApiProposals: getEnabledApiProposals(version), localizedLanguages: getLocalizedLanguages(version), targetPlatform: getTargetPlatformForExtensionVersion(version), - isPreReleaseVersion: isPreReleaseVersion(version) + isPreReleaseVersion: isPreReleaseVersion(version), + executesCode: executesCode(version) }, hasPreReleaseVersion: isPreReleaseVersion(latestVersion), hasReleaseVersion: true, @@ -579,6 +594,7 @@ interface IRawExtensionsControlManifest { additionalInfo?: string; }>; search?: ISearchPrefferedResults[]; + extensionsEnabledWithPreRelease?: string[]; } abstract class AbstractExtensionGalleryService implements IExtensionGalleryService { @@ -589,7 +605,8 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi private readonly extensionsGallerySearchUrl: string | undefined; private readonly extensionsControlUrl: string | undefined; - private readonly commonHeadersPromise: Promise>; + private readonly commonHeadersPromise: Promise; + private readonly extensionsEnabledWithApiProposalVersion: string[]; constructor( storageService: IStorageService | undefined, @@ -606,6 +623,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi this.extensionsGalleryUrl = isPPEEnabled ? config.servicePPEUrl : config?.serviceUrl; this.extensionsGallerySearchUrl = isPPEEnabled ? undefined : config?.searchUrl; this.extensionsControlUrl = config?.controlUrl; + this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? []; this.commonHeadersPromise = resolveMarketplaceHeaders( productService.version, productService, @@ -629,6 +647,36 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi async getExtensions(extensionInfos: ReadonlyArray, arg1: any, arg2?: any): Promise { const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions; const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken; + const result = await this.doGetExtensions(extensionInfos, options, token); + + const uuids = result.map(r => r.identifier.uuid); + const extensionInfosByName: IExtensionInfo[] = []; + for (const e of extensionInfos) { + if (e.uuid && !uuids.includes(e.uuid)) { + extensionInfosByName.push({ ...e, uuid: undefined }); + } + } + + if (extensionInfosByName.length) { + // report telemetry data for additional query + this.telemetryService.publicLog2< + { count: number }, + { + owner: 'sandy081'; + comment: 'Report the query to the the Marketplace for fetching extensions by name'; + readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to fetch' }; + }>('galleryService:additionalQueryByName', { + count: extensionInfosByName.length + }); + + const extensions = await this.doGetExtensions(extensionInfosByName, options, token); + result.push(...extensions); + } + + return result; + } + + private async doGetExtensions(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise { const names: string[] = []; const ids: string[] = [], includePreReleases: (IExtensionIdentifier & { includePreRelease: boolean })[] = [], versions: (IExtensionIdentifier & { version: string })[] = []; let isQueryForReleaseVersionFromPreReleaseVersion = true; for (const extensionInfo of extensionInfos) { @@ -704,7 +752,26 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } engine = manifest.engines.vscode; } - return isEngineValid(engine, productVersion.version, productVersion.date); + + if (!isEngineValid(engine, productVersion.version, productVersion.date)) { + return false; + } + + if (!this.areApiProposalsCompatible(extension.identifier, extension.properties.enabledApiProposals)) { + return false; + } + + return true; + } + + private areApiProposalsCompatible(extensionIdentifier: IExtensionIdentifier, enabledApiProposals: string[] | undefined): boolean { + if (!enabledApiProposals) { + return true; + } + if (!this.extensionsEnabledWithApiProposalVersion.includes(extensionIdentifier.id.toLowerCase())) { + return true; + } + return areApiProposalsCompatible(enabledApiProposals); } private async isValidVersion(extension: string, rawGalleryExtensionVersion: IRawGalleryExtensionVersion, versionType: 'release' | 'prerelease' | 'any', compatible: boolean, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { @@ -915,7 +982,18 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi continue; } // Allow any version if includePreRelease flag is set otherwise only release versions are allowed - if (await this.isValidVersion(getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), rawGalleryExtensionVersion, includePreRelease ? 'any' : 'release', criteria.compatible, allTargetPlatforms, criteria.targetPlatform, criteria.productVersion)) { + if (await this.isValidVersion( + extensionIdentifier.id, + rawGalleryExtensionVersion, + includePreRelease ? 'any' : 'release', + criteria.compatible, + allTargetPlatforms, + criteria.targetPlatform, + criteria.productVersion) + ) { + if (criteria.compatible && !this.areApiProposalsCompatible(extensionIdentifier, getEnabledApiProposals(rawGalleryExtensionVersion))) { + continue; + } return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, queryContext); } if (version && rawGalleryExtensionVersion.version === version) { @@ -981,9 +1059,9 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return { galleryExtensions, total, - context: { + context: context.res.headers['activityid'] ? { [ACTIVITY_HEADER_NAME]: context.res.headers['activityid'] - } + } : {} }; } return { galleryExtensions: [], total }; @@ -994,7 +1072,11 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi throw e; } else { const errorMessage = getErrorMessage(e); - errorCode = errorMessage.startsWith('XHR timeout') ? ExtensionGalleryErrorCode.Timeout : ExtensionGalleryErrorCode.Failed; + errorCode = isOfflineError(e) + ? ExtensionGalleryErrorCode.Offline + : errorMessage.startsWith('XHR timeout') + ? ExtensionGalleryErrorCode.Timeout + : ExtensionGalleryErrorCode.Failed; throw new ExtensionGalleryError(errorMessage, errorCode); } } finally { @@ -1137,15 +1219,15 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return ''; } - async getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise { + async getAllCompatibleVersions(extensionIdentifier: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise { let query = new Query() .withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, 1); - if (extension.identifier.uuid) { - query = query.withFilter(FilterType.ExtensionId, extension.identifier.uuid); + if (extensionIdentifier.uuid) { + query = query.withFilter(FilterType.ExtensionId, extensionIdentifier.uuid); } else { - query = query.withFilter(FilterType.ExtensionName, extension.identifier.id); + query = query.withFilter(FilterType.ExtensionName, extensionIdentifier.id); } const { galleryExtensions } = await this.queryRawGalleryExtensions(query, CancellationToken.None); @@ -1161,7 +1243,15 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const validVersions: IRawGalleryExtensionVersion[] = []; await Promise.all(galleryExtensions[0].versions.map(async (version) => { try { - if (await this.isValidVersion(extension.identifier.id, version, includePreRelease ? 'any' : 'release', true, allTargetPlatforms, targetPlatform)) { + if ( + (await this.isValidVersion( + extensionIdentifier.id, + version, includePreRelease ? 'any' : 'release', + true, + allTargetPlatforms, + targetPlatform)) + && this.areApiProposalsCompatible(extensionIdentifier, getEnabledApiProposals(version)) + ) { validVersions.push(version); } } catch (error) { /* Ignore error and skip version */ } @@ -1262,6 +1352,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const malicious: IExtensionIdentifier[] = []; const deprecated: IStringDictionary = {}; const search: ISearchPrefferedResults[] = []; + const extensionsEnabledWithPreRelease: string[] = []; if (result) { for (const id of result.malicious) { malicious.push({ id }); @@ -1293,9 +1384,14 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi search.push(s); } } + if (Array.isArray(result.extensionsEnabledWithPreRelease)) { + for (const id of result.extensionsEnabledWithPreRelease) { + extensionsEnabledWithPreRelease.push(id.toLowerCase()); + } + } } - return { malicious, deprecated, search }; + return { malicious, deprecated, search, extensionsEnabledWithPreRelease }; } } diff --git a/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagement.ts b/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagement.ts index 183f2582..83ed0c51 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -159,9 +159,11 @@ export interface IGalleryExtensionProperties { dependencies?: string[]; extensionPack?: string[]; engine?: string; + enabledApiProposals?: string[]; localizedLanguages?: string[]; targetPlatform: TargetPlatform; isPreReleaseVersion: boolean; + executesCode?: boolean; } export interface IGalleryExtensionAsset { @@ -326,6 +328,7 @@ export interface IExtensionsControlManifest { readonly malicious: IExtensionIdentifier[]; readonly deprecated: IStringDictionary; readonly search: ISearchPrefferedResults[]; + readonly extensionsEnabledWithPreRelease?: string[]; } export const enum InstallOperation { @@ -367,7 +370,7 @@ export interface IExtensionGalleryService { getExtensions(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise; isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion?: IProductVersion): Promise; getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion?: IProductVersion): Promise; - getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; + getAllCompatibleVersions(extensionIdentifier: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise; download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise; downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise; reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise; @@ -381,7 +384,7 @@ export interface IExtensionGalleryService { export interface InstallExtensionEvent { readonly identifier: IExtensionIdentifier; readonly source: URI | IGalleryExtension; - readonly profileLocation?: URI; + readonly profileLocation: URI; readonly applicationScoped?: boolean; readonly workspaceScoped?: boolean; } @@ -393,14 +396,14 @@ export interface InstallExtensionResult { readonly local?: ILocalExtension; readonly error?: Error; readonly context?: IStringDictionary; - readonly profileLocation?: URI; + readonly profileLocation: URI; readonly applicationScoped?: boolean; readonly workspaceScoped?: boolean; } export interface UninstallExtensionEvent { readonly identifier: IExtensionIdentifier; - readonly profileLocation?: URI; + readonly profileLocation: URI; readonly applicationScoped?: boolean; readonly workspaceScoped?: boolean; } @@ -408,16 +411,22 @@ export interface UninstallExtensionEvent { export interface DidUninstallExtensionEvent { readonly identifier: IExtensionIdentifier; readonly error?: string; - readonly profileLocation?: URI; + readonly profileLocation: URI; readonly applicationScoped?: boolean; readonly workspaceScoped?: boolean; } +export interface DidUpdateExtensionMetadata { + readonly profileLocation: URI; + readonly local: ILocalExtension; +} + export const enum ExtensionGalleryErrorCode { Timeout = 'Timeout', Cancelled = 'Cancelled', Failed = 'Failed', DownloadFailedWriting = 'DownloadFailedWriting', + Offline = 'Offline', } export class ExtensionGalleryError extends Error { @@ -432,6 +441,7 @@ export const enum ExtensionManagementErrorCode { Deprecated = 'Deprecated', Malicious = 'Malicious', Incompatible = 'Incompatible', + IncompatibleApi = 'IncompatibleApi', IncompatibleTargetPlatform = 'IncompatibleTargetPlatform', ReleaseVersionNotFound = 'ReleaseVersionNotFound', Invalid = 'Invalid', @@ -482,12 +492,20 @@ export type InstallOptions = { profileLocation?: URI; installOnlyNewlyAddedFromExtensionPack?: boolean; productVersion?: IProductVersion; + keepExisting?: boolean; /** * Context passed through to InstallExtensionResult */ context?: IStringDictionary; }; -export type UninstallOptions = { readonly donotIncludePack?: boolean; readonly donotCheckDependents?: boolean; readonly versionOnly?: boolean; readonly remove?: boolean; readonly profileLocation?: URI }; + +export type UninstallOptions = { + readonly profileLocation?: URI; + readonly donotIncludePack?: boolean; + readonly donotCheckDependents?: boolean; + readonly versionOnly?: boolean; + readonly remove?: boolean; +}; export interface IExtensionManagementParticipant { postInstall(local: ILocalExtension, source: URI | IGalleryExtension, options: InstallOptions, token: CancellationToken): Promise; @@ -495,6 +513,7 @@ export interface IExtensionManagementParticipant { } export type InstallExtensionInfo = { readonly extension: IGalleryExtension; readonly options: InstallOptions }; +export type UninstallExtensionInfo = { readonly extension: ILocalExtension; readonly options?: UninstallOptions }; export const IExtensionManagementService = createDecorator('extensionManagementService'); export interface IExtensionManagementService { @@ -504,10 +523,9 @@ export interface IExtensionManagementService { onDidInstallExtensions: Event; onUninstallExtension: Event; onDidUninstallExtension: Event; - onDidUpdateExtensionMetadata: Event; + onDidUpdateExtensionMetadata: Event; zip(extension: ILocalExtension): Promise; - unzip(zipLocation: URI): Promise; getManifest(vsix: URI): Promise; install(vsix: URI, options?: InstallOptions): Promise; canInstall(extension: IGalleryExtension): Promise; @@ -516,12 +534,14 @@ export interface IExtensionManagementService { installFromLocation(location: URI, profileLocation: URI): Promise; installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise; uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise; + uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise; toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise; reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise; getExtensionsControlManifest(): Promise; copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; - updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise; + updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI): Promise; + resetPinnedStateForAllUserExtensions(pinned: boolean): Promise; download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise; diff --git a/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 9241f417..06442013 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -146,7 +146,7 @@ export class ExtensionManagementCLI { if (areSameExtensions(oldVersion.identifier, newVersion.identifier) && gt(newVersion.version, oldVersion.manifest.version)) { extensionsToUpdate.push({ extension: newVersion, - options: { operation: InstallOperation.Update, installPreReleaseVersion: oldVersion.preRelease, profileLocation } + options: { operation: InstallOperation.Update, installPreReleaseVersion: oldVersion.preRelease, profileLocation, isApplicationScoped: oldVersion.isApplicationScoped } }); } } @@ -224,7 +224,7 @@ export class ExtensionManagementCLI { } extensionsToInstall.push({ extension: gallery, - options: { ...installOptions, installGivenVersion: !!version }, + options: { ...installOptions, installGivenVersion: !!version, isApplicationScoped: installOptions.isApplicationScoped || installedExtension?.isApplicationScoped }, }); })); @@ -253,7 +253,7 @@ export class ExtensionManagementCLI { const valid = await this.validateVSIX(manifest, force, installOptions.profileLocation, installedExtensions); if (valid) { try { - await this.extensionManagementService.install(vsix, installOptions); + await this.extensionManagementService.install(vsix, { ...installOptions, installGivenVersion: true }); this.logger.info(localize('successVsixInstall', "Extension '{0}' was successfully installed.", basename(vsix))); } catch (error) { if (isCancellationError(error)) { diff --git a/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 6c3e289d..2785a41f 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -9,7 +9,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo, IProductVersion, DidUpdateExtensionMetadata, UninstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI; @@ -43,7 +43,7 @@ export class ExtensionManagementChannel implements IServerChannel { onDidInstallExtensions: Event; onUninstallExtension: Event; onDidUninstallExtension: Event; - onDidUpdateExtensionMetadata: Event; + onDidUpdateExtensionMetadata: Event; constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: any) => IURITransformer | null) { this.onInstallExtension = Event.buffer(service.onInstallExtension, true); @@ -89,7 +89,12 @@ export class ExtensionManagementChannel implements IServerChannel { }); } case 'onDidUpdateExtensionMetadata': { - return Event.map(this.onDidUpdateExtensionMetadata, e => transformOutgoingExtension(e, uriTransformer)); + return Event.map(this.onDidUpdateExtensionMetadata, e => { + return { + local: transformOutgoingExtension(e.local, uriTransformer), + profileLocation: transformOutgoingURI(e.profileLocation, uriTransformer) + }; + }); } } @@ -104,9 +109,6 @@ export class ExtensionManagementChannel implements IServerChannel { const uri = await this.service.zip(extension); return transformOutgoingURI(uri, uriTransformer); } - case 'unzip': { - return this.service.unzip(transformIncomingURI(args[0], uriTransformer)); - } case 'install': { return this.service.install(transformIncomingURI(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer)); } @@ -135,6 +137,10 @@ export class ExtensionManagementChannel implements IServerChannel { case 'uninstall': { return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer)); } + case 'uninstallExtensions': { + const arg: UninstallExtensionInfo[] = args[0]; + return this.service.uninstallExtensions(arg.map(({ extension, options }) => ({ extension: transformIncomingExtension(extension, uriTransformer), options: transformIncomingOptions(options, uriTransformer) }))); + } case 'reinstallFromGallery': { return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); } @@ -153,6 +159,9 @@ export class ExtensionManagementChannel implements IServerChannel { const e = await this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1], transformIncomingURI(args[2], uriTransformer)); return transformOutgoingExtension(e, uriTransformer); } + case 'resetPinnedStateForAllUserExtensions': { + return this.service.resetPinnedStateForAllUserExtensions(args[0]); + } case 'getExtensionsControlManifest': { return this.service.getExtensionsControlManifest(); } @@ -168,7 +177,11 @@ export class ExtensionManagementChannel implements IServerChannel { } } -export type ExtensionEventResult = InstallExtensionEvent | InstallExtensionResult | UninstallExtensionEvent | DidUninstallExtensionEvent; +export interface ExtensionEventResult { + readonly profileLocation: URI; + readonly local?: ILocalExtension; + readonly applicationScoped?: boolean; +} export class ExtensionManagementChannelClient extends Disposable implements IExtensionManagementService { @@ -186,7 +199,7 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt private readonly _onDidUninstallExtension = this._register(new Emitter()); get onDidUninstallExtension() { return this._onDidUninstallExtension.event; } - private readonly _onDidUpdateExtensionMetadata = this._register(new Emitter()); + private readonly _onDidUpdateExtensionMetadata = this._register(new Emitter()); get onDidUpdateExtensionMetadata() { return this._onDidUpdateExtensionMetadata.event; } constructor(private readonly channel: IChannel) { @@ -195,16 +208,12 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt this._register(this.channel.listen('onDidInstallExtensions')(results => this.fireEvent(this._onDidInstallExtensions, results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))))); this._register(this.channel.listen('onUninstallExtension')(e => this.fireEvent(this._onUninstallExtension, { ...e, profileLocation: URI.revive(e.profileLocation) }))); this._register(this.channel.listen('onDidUninstallExtension')(e => this.fireEvent(this._onDidUninstallExtension, { ...e, profileLocation: URI.revive(e.profileLocation) }))); - this._register(this.channel.listen('onDidUpdateExtensionMetadata')(e => this._onDidUpdateExtensionMetadata.fire(transformIncomingExtension(e, null)))); + this._register(this.channel.listen('onDidUpdateExtensionMetadata')(e => this.fireEvent(this._onDidUpdateExtensionMetadata, { profileLocation: URI.revive(e.profileLocation), local: transformIncomingExtension(e.local, null) }))); } - protected fireEvent(event: Emitter, data: InstallExtensionEvent): void; - protected fireEvent(event: Emitter, data: InstallExtensionResult[]): void; - protected fireEvent(event: Emitter, data: UninstallExtensionEvent): void; - protected fireEvent(event: Emitter, data: DidUninstallExtensionEvent): void; - protected fireEvent(event: Emitter, data: ExtensionEventResult): void; - protected fireEvent(event: Emitter, data: ExtensionEventResult[]): void; - protected fireEvent(event: Emitter, data: E): void { + protected fireEvent(event: Emitter, data: E): void; + protected fireEvent(event: Emitter, data: E[]): void; + protected fireEvent(event: Emitter, data: E | E[]): void { event.fire(data); } @@ -233,10 +242,6 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(result))); } - unzip(zipLocation: URI): Promise { - return Promise.resolve(this.channel.call('unzip', [zipLocation])); - } - install(vsix: URI, options?: InstallOptions): Promise { return Promise.resolve(this.channel.call('install', [vsix, options])).then(local => transformIncomingExtension(local, null)); } @@ -270,6 +275,14 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt return Promise.resolve(this.channel.call('uninstall', [extension, options])); } + uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise { + if (extensions.some(e => e.extension.isWorkspaceScoped)) { + throw new Error('Cannot uninstall a workspace extension'); + } + return Promise.resolve(this.channel.call('uninstallExtensions', [extensions])); + + } + reinstallFromGallery(extension: ILocalExtension): Promise { return Promise.resolve(this.channel.call('reinstallFromGallery', [extension])).then(local => transformIncomingExtension(local, null)); } @@ -284,6 +297,10 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt .then(extension => transformIncomingExtension(extension, null)); } + resetPinnedStateForAllUserExtensions(pinned: boolean): Promise { + return this.channel.call('resetPinnedStateForAllUserExtensions', [pinned]); + } + toggleAppliationScope(local: ILocalExtension, fromProfileLocation: URI): Promise { return this.channel.call('toggleAppliationScope', [local, fromProfileLocation]) .then(extension => transformIncomingExtension(extension, null)); diff --git a/patched-vscode/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/patched-vscode/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index e92897f1..7d4c51d2 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -24,7 +24,7 @@ import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IProductVersion, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, ExtensionIdentifierMap, parseEnabledApiProposalNames } from 'vs/platform/extensions/common/extensions'; import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator'; import { FileOperationResult, IFileService, toFileOperationResult } from 'vs/platform/files/common/files'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -554,14 +554,19 @@ type NlsConfiguration = { class ExtensionsScanner extends Disposable { + private readonly extensionsEnabledWithApiProposalVersion: string[]; + constructor( private readonly obsoleteFile: URI, @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, @IFileService protected readonly fileService: IFileService, + @IProductService productService: IProductService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, @ILogService protected readonly logService: ILogService ) { super(); + this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? []; } async scanExtensions(input: ExtensionScannerInput): Promise { @@ -653,7 +658,7 @@ class ExtensionsScanner extends Disposable { const type = metadata?.isSystem ? ExtensionType.System : input.type; const isBuiltin = type === ExtensionType.System || !!metadata?.isBuiltin; manifest = await this.translateManifest(input.location, manifest, ExtensionScannerInput.createNlsConfiguration(input)); - const extension: IRelaxedScannedExtension = { + let extension: IRelaxedScannedExtension = { type, identifier, manifest, @@ -665,7 +670,14 @@ class ExtensionsScanner extends Disposable { isValid: true, validations: [] }; - return input.validate ? this.validate(extension, input) : extension; + if (input.validate) { + extension = this.validate(extension, input); + } + if (manifest.enabledApiProposals && (!this.environmentService.isBuilt || this.extensionsEnabledWithApiProposalVersion.includes(id.toLowerCase()))) { + manifest.originalEnabledApiProposals = manifest.enabledApiProposals; + manifest.enabledApiProposals = parseEnabledApiProposalNames([...manifest.enabledApiProposals]); + } + return extension; } } catch (e) { if (input.type !== ExtensionType.System) { @@ -677,7 +689,8 @@ class ExtensionsScanner extends Disposable { validate(extension: IRelaxedScannedExtension, input: ExtensionScannerInput): IRelaxedScannedExtension { let isValid = true; - const validations = validateExtensionManifest(input.productVersion, input.productDate, input.location, extension.manifest, extension.isBuiltin); + const validateApiVersion = this.environmentService.isBuilt && this.extensionsEnabledWithApiProposalVersion.includes(extension.identifier.id.toLowerCase()); + const validations = validateExtensionManifest(input.productVersion, input.productDate, input.location, extension.manifest, extension.isBuiltin, validateApiVersion); for (const [severity, message] of validations) { if (severity === Severity.Error) { isValid = false; @@ -689,7 +702,7 @@ class ExtensionsScanner extends Disposable { return extension; } - async scanExtensionManifest(extensionLocation: URI): Promise { + private async scanExtensionManifest(extensionLocation: URI): Promise { const manifestLocation = joinPath(extensionLocation, 'package.json'); let content; try { @@ -713,7 +726,7 @@ class ExtensionsScanner extends Disposable { return null; } if (getNodeType(manifest) !== 'object') { - this.logService.error(this.formatMessage(extensionLocation, localize('jsonParseInvalidType', "Invalid manifest file {0}: Not an JSON object.", manifestLocation.path))); + this.logService.error(this.formatMessage(extensionLocation, localize('jsonParseInvalidType', "Invalid manifest file {0}: Not a JSON object.", manifestLocation.path))); return null; } return manifest; @@ -878,9 +891,11 @@ class CachedExtensionsScanner extends ExtensionsScanner { @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IFileService fileService: IFileService, + @IProductService productService: IProductService, + @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService ) { - super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, logService); + super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, productService, environmentService, logService); } override async scanExtensions(input: ExtensionScannerInput): Promise { @@ -888,7 +903,7 @@ class CachedExtensionsScanner extends ExtensionsScanner { const cacheContents = await this.readExtensionCache(cacheFile); this.input = input; if (cacheContents && cacheContents.input && ExtensionScannerInput.equals(cacheContents.input, this.input)) { - this.logService.debug('Using cached extensions scan result', input.location.toString()); + this.logService.debug('Using cached extensions scan result', input.type === ExtensionType.System ? 'system' : 'user', input.location.toString()); this.cacheValidatorThrottler.trigger(() => this.validateCache()); return cacheContents.result.map((extension) => { // revive URI object diff --git a/patched-vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/patched-vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 275d5c3c..75ab2baa 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { Promises, Queue } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -12,7 +13,7 @@ import { CancellationError, getErrorMessage } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ResourceSet } from 'vs/base/common/map'; +import { ResourceMap, ResourceSet } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; @@ -110,12 +111,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return URI.file(location); } - async unzip(zipLocation: URI): Promise { - this.logService.trace('ExtensionManagementService#unzip', zipLocation.toString()); - const local = await this.install(zipLocation); - return local.identifier; - } - async getManifest(vsix: URI): Promise { const { location, cleanup } = await this.downloadVsix(vsix); const zipPath = path.resolve(location.fsPath); @@ -186,7 +181,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return extensionsToInstall; } - async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise { + async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI): Promise { this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); if (metadata.isPreReleaseVersion) { metadata.preRelease = true; @@ -204,7 +199,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } local = await this.extensionsScanner.updateMetadata(local, metadata, profileLocation); this.manifestCache.invalidate(profileLocation); - this._onDidUpdateExtensionMetadata.fire(local); + this._onDidUpdateExtensionMetadata.fire({ local, profileLocation }); return local; } @@ -293,7 +288,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask { - return new UninstallExtensionInProfileTask(extension, options.profileLocation, this.extensionsProfileScannerService); + return new UninstallExtensionInProfileTask(extension, options, this.extensionsProfileScannerService); } private async downloadAndExtractGalleryExtension(extensionKey: ExtensionKey, gallery: IGalleryExtension, operation: InstallOperation, options: InstallExtensionTaskOptions, token: CancellationToken): Promise { @@ -305,7 +300,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } // validate manifest - await getManifest(location.fsPath); + const manifest = await getManifest(location.fsPath); + if (!new ExtensionKey(gallery.identifier, gallery.version).equals(new ExtensionKey({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, manifest.version))) { + throw new ExtensionManagementError(nls.localize('invalidManifest', "Cannot install '{0}' extension because of manifest mismatch with Marketplace", gallery.identifier.id), ExtensionManagementErrorCode.Invalid); + } const local = await this.extensionsScanner.extractUserExtension( extensionKey, @@ -353,7 +351,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi pinned: options.installGivenVersion ? true : !!options.pinned, source: 'vsix', }, - true, + options.keepExisting ?? true, token); return { local }; } @@ -363,7 +361,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi const collectFilesFromDirectory = async (dir: string): Promise => { let entries = await pfs.Promises.readdir(dir); entries = entries.map(e => path.join(dir, e)); - const stats = await Promise.all(entries.map(e => pfs.Promises.stat(e))); + const stats = await Promise.all(entries.map(e => fs.promises.stat(e))); let promise: Promise = Promise.resolve([]); stats.forEach((stat, index) => { const entry = entries[index]; @@ -380,7 +378,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi }; const files = await collectFilesFromDirectory(extension.location.fsPath); - return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })); + return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })); } private async onDidChangeExtensionsFromAnotherSource({ added, removed }: DidChangeProfileExtensionsEvent): Promise { @@ -484,6 +482,19 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } } +type UpdateMetadataErrorClassification = { + owner: 'sandy081'; + comment: 'Update metadata error'; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension identifier' }; + code?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code' }; + isProfile?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Is writing into profile' }; +}; +type UpdateMetadataErrorEvent = { + extensionId: string; + code?: string; + isProfile?: boolean; +}; + export class ExtensionsScanner extends Disposable { private readonly uninstalledResource: URI; @@ -492,12 +503,16 @@ export class ExtensionsScanner extends Disposable { private readonly _onExtract = this._register(new Emitter()); readonly onExtract = this._onExtract.event; + private scanAllExtensionPromise = new ResourceMap>(); + private scanUserExtensionsPromise = new ResourceMap>(); + constructor( private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise, @IFileService private readonly fileService: IFileService, @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, ) { super(); @@ -515,9 +530,21 @@ export class ExtensionsScanner extends Disposable { const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation, productVersion }; let scannedExtensions: IScannedExtension[] = []; if (type === null || type === ExtensionType.System) { - scannedExtensions.push(...await this.extensionsScannerService.scanAllExtensions({ includeInvalid: true }, userScanOptions, false)); + let scanAllExtensionsPromise = this.scanAllExtensionPromise.get(profileLocation); + if (!scanAllExtensionsPromise) { + scanAllExtensionsPromise = this.extensionsScannerService.scanAllExtensions({ includeInvalid: true, useCache: true }, userScanOptions, false) + .finally(() => this.scanAllExtensionPromise.delete(profileLocation)); + this.scanAllExtensionPromise.set(profileLocation, scanAllExtensionsPromise); + } + scannedExtensions.push(...await scanAllExtensionsPromise); } else if (type === ExtensionType.User) { - scannedExtensions.push(...await this.extensionsScannerService.scanUserExtensions(userScanOptions)); + let scanUserExtensionsPromise = this.scanUserExtensionsPromise.get(profileLocation); + if (!scanUserExtensionsPromise) { + scanUserExtensionsPromise = this.extensionsScannerService.scanUserExtensions(userScanOptions) + .finally(() => this.scanUserExtensionsPromise.delete(profileLocation)); + this.scanUserExtensionsPromise.set(profileLocation, scanUserExtensionsPromise); + } + scannedExtensions.push(...await scanUserExtensionsPromise); } scannedExtensions = type !== null ? scannedExtensions.filter(r => r.type === type) : scannedExtensions; return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); @@ -552,69 +579,67 @@ export class ExtensionsScanner extends Disposable { const tempLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, `.${generateUuid()}`)); const extensionLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, folderName)); - let exists = await this.fileService.exists(extensionLocation); + if (await this.fileService.exists(extensionLocation)) { + if (!removeIfExists) { + try { + return await this.scanLocalExtension(extensionLocation, ExtensionType.User); + } catch (error) { + this.logService.warn(`Error while scanning the existing extension at ${extensionLocation.path}. Deleting the existing extension and extracting it.`, getErrorMessage(error)); + } + } - if (exists && removeIfExists) { try { await this.deleteExtensionFromLocation(extensionKey.id, extensionLocation, 'removeExisting'); } catch (error) { throw new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionLocation.fsPath, extensionKey.id), ExtensionManagementErrorCode.Delete); } - exists = false; } - if (exists) { + try { + if (token.isCancellationRequested) { + throw new CancellationError(); + } + + // Extract try { - await this.extensionsScannerService.updateMetadata(extensionLocation, metadata); + this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionLocation.fsPath}`); + await extract(zipPath, tempLocation.fsPath, { sourcePath: 'extension', overwrite: true }, token); + this.logService.info(`Extracted extension to ${extensionLocation}:`, extensionKey.id); + } catch (e) { + throw fromExtractError(e); + } + + try { + await this.extensionsScannerService.updateMetadata(tempLocation, metadata); } catch (error) { + this.telemetryService.publicLog2('extension:extract', { extensionId: extensionKey.id, code: `${toFileOperationResult(error)}` }); throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); } - } else { - try { - if (token.isCancellationRequested) { - throw new CancellationError(); - } - - // Extract - try { - this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionLocation.fsPath}`); - await extract(zipPath, tempLocation.fsPath, { sourcePath: 'extension', overwrite: true }, token); - this.logService.info(`Extracted extension to ${extensionLocation}:`, extensionKey.id); - } catch (e) { - throw fromExtractError(e); - } - - try { - await this.extensionsScannerService.updateMetadata(tempLocation, metadata); - } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); - } - if (token.isCancellationRequested) { - throw new CancellationError(); - } + if (token.isCancellationRequested) { + throw new CancellationError(); + } - // Rename - try { - this.logService.trace(`Started renaming the extension from ${tempLocation.fsPath} to ${extensionLocation.fsPath}`); - await this.rename(tempLocation.fsPath, extensionLocation.fsPath); - this.logService.info('Renamed to', extensionLocation.fsPath); - } catch (error) { - if (error.code === 'ENOTEMPTY') { - this.logService.info(`Rename failed because extension was installed by another source. So ignoring renaming.`, extensionKey.id); - try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } - } else { - this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, tempLocation); - throw error; - } + // Rename + try { + this.logService.trace(`Started renaming the extension from ${tempLocation.fsPath} to ${extensionLocation.fsPath}`); + await this.rename(tempLocation.fsPath, extensionLocation.fsPath); + this.logService.info('Renamed to', extensionLocation.fsPath); + } catch (error) { + if (error.code === 'ENOTEMPTY') { + this.logService.info(`Rename failed because extension was installed by another source. So ignoring renaming.`, extensionKey.id); + try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } + } else { + this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, tempLocation); + throw error; } + } - this._onExtract.fire(extensionLocation); + this._onExtract.fire(extensionLocation); - } catch (error) { - try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } - throw error; - } + } catch (error) { + try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } + throw error; } return this.scanLocalExtension(extensionLocation, ExtensionType.User); @@ -642,6 +667,7 @@ export class ExtensionsScanner extends Disposable { await this.extensionsScannerService.updateMetadata(local.location, metadata); } } catch (error) { + this.telemetryService.publicLog2('extension:extract', { extensionId: local.identifier.id, code: `${toFileOperationResult(error)}`, isProfile: !!profileLocation }); throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); } return this.scanLocalExtension(local.location, local.type, profileLocation); @@ -759,7 +785,7 @@ export class ExtensionsScanner extends Disposable { } } - private async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { + async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { try { if (profileLocation) { const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation }); @@ -912,13 +938,9 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask { - const installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation, this.options.productVersion); - return installed.find(i => areSameExtensions(i.identifier, this.identifier)); - } - protected async doRun(token: CancellationToken): Promise { - const existingExtension = await this.getExistingExtension(); + const installed = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation, this.options.productVersion); + const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.identifier)); if (existingExtension) { this._operation = InstallOperation.Update; } @@ -945,16 +967,15 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask implem constructor( readonly extension: ILocalExtension, - private readonly profileLocation: URI, + readonly options: UninstallExtensionTaskOptions, private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, ) { super(); } protected async doRun(token: CancellationToken): Promise { - await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.profileLocation); + await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.options.profileLocation); } } diff --git a/patched-vscode/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts b/patched-vscode/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts index b50638db..30d22f4e 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { importAMDNodeModule } from 'vs/amdX'; import { getErrorMessage } from 'vs/base/common/errors'; import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -35,8 +36,8 @@ declare module vsceSign { export function verify(vsixFilePath: string, signatureArchiveFilePath: string, verbose: boolean): Promise; } -export const enum ExtensionSignatureVerificationCode { - 'None' = 'None', +export enum ExtensionSignatureVerificationCode { + 'Success' = 'Success', 'RequiredArgumentMissing' = 'RequiredArgumentMissing', 'InvalidArgument' = 'InvalidArgument', 'PackageIsUnreadable' = 'PackageIsUnreadable', @@ -51,7 +52,6 @@ export const enum ExtensionSignatureVerificationCode { 'SignatureArchiveIsInvalidZip' = 'SignatureArchiveIsInvalidZip', 'SignatureArchiveHasSameSignatureFile' = 'SignatureArchiveHasSameSignatureFile', - 'Success' = 'Success', 'PackageIntegrityCheckFailed' = 'PackageIntegrityCheckFailed', 'SignatureIsInvalid' = 'SignatureIsInvalid', 'SignatureManifestIsInvalid' = 'SignatureManifestIsInvalid', @@ -96,19 +96,24 @@ export class ExtensionSignatureVerificationService implements IExtensionSignatur private vsceSign(): Promise { if (!this.moduleLoadingPromise) { - this.moduleLoadingPromise = new Promise( - (resolve, reject) => require( - ['@vscode/vsce-sign'], - async (obj) => { - const instance = obj; - - return resolve(instance); - }, reject)); + this.moduleLoadingPromise = this.resolveVsceSign(); } return this.moduleLoadingPromise; } + private async resolveVsceSign(): Promise { + // ESM-uncomment-begin + // if (typeof importAMDNodeModule === 'function') { /* fixes unused import, remove me */} + // const mod = '@vscode/vsce-sign'; + // return import(mod); + // ESM-uncomment-end + + // ESM-comment-begin + return importAMDNodeModule('@vscode/vsce-sign', 'src/main.js'); + // ESM-comment-end + } + public async verify(extension: IGalleryExtension, vsixFilePath: string, signatureArchiveFilePath: string, clientTargetPlatform?: TargetPlatform): Promise { let module: typeof vsceSign; const extensionId = extension.identifier.id; diff --git a/patched-vscode/src/vs/platform/extensionManagement/node/extensionsWatcher.ts b/patched-vscode/src/vs/platform/extensionManagement/node/extensionsWatcher.ts index ba8e026b..a56087b0 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/node/extensionsWatcher.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/node/extensionsWatcher.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getErrorMessage } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { combinedDisposable, Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import { ResourceSet } from 'vs/base/common/map'; @@ -40,7 +41,7 @@ export class ExtensionsWatcher extends Disposable { private readonly logService: ILogService, ) { super(); - this.initialize().then(null, error => logService.error(error)); + this.initialize().then(null, error => logService.error('Error while initializing Extensions Watcher', getErrorMessage(error))); } private async initialize(): Promise { diff --git a/patched-vscode/src/vs/platform/extensionManagement/test/common/configRemotes.test.ts b/patched-vscode/src/vs/platform/extensionManagement/test/common/configRemotes.test.ts index ce93c6e7..178293d8 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/test/common/configRemotes.test.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/test/common/configRemotes.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { getDomainsOfRemotes, getRemotes } from 'vs/platform/extensionManagement/common/configRemotes'; diff --git a/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts index cebafab4..e4f187c3 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { isUUID } from 'vs/base/common/uuid'; @@ -52,6 +52,7 @@ suite('Extension Gallery Service', () => { test('marketplace machine id', async () => { const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, NullTelemetryService); + assert.ok(headers['X-Market-User-Id']); assert.ok(isUUID(headers['X-Market-User-Id'])); const headers2 = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, NullTelemetryService); assert.strictEqual(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); diff --git a/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts b/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts index d0813771..1c3d62f7 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionManagement.test.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionManagement.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionKey } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; diff --git a/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionNls.test.ts b/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionNls.test.ts index a3c2603a..64aad4fb 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionNls.test.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionNls.test.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { deepClone } from 'vs/base/common/objects'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; -import { IExtensionManifest, IConfiguration } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { NullLogger } from 'vs/platform/log/common/log'; const manifest: IExtensionManifest = { @@ -62,7 +63,7 @@ suite('Localize Manifest', () => { assert.strictEqual(localizedManifest.contributes?.commands?.[0].title, 'Test Command'); assert.strictEqual(localizedManifest.contributes?.commands?.[0].category, 'Test Category'); assert.strictEqual(localizedManifest.contributes?.authentication?.[0].label, 'Test Authentication'); - assert.strictEqual((localizedManifest.contributes?.configuration as IConfiguration).title, 'Test Configuration'); + assert.strictEqual((localizedManifest.contributes?.configuration as IConfigurationNode).title, 'Test Configuration'); }); test('replaces template strings with fallback if not found in translations', function () { @@ -81,7 +82,7 @@ suite('Localize Manifest', () => { assert.strictEqual(localizedManifest.contributes?.commands?.[0].title, 'Test Command'); assert.strictEqual(localizedManifest.contributes?.commands?.[0].category, 'Test Category'); assert.strictEqual(localizedManifest.contributes?.authentication?.[0].label, 'Test Authentication'); - assert.strictEqual((localizedManifest.contributes?.configuration as IConfiguration).title, 'Test Configuration'); + assert.strictEqual((localizedManifest.contributes?.configuration as IConfigurationNode).title, 'Test Configuration'); }); test('replaces template strings - command title & categories become ILocalizedString', function () { @@ -111,7 +112,7 @@ suite('Localize Manifest', () => { // Everything else stays as a string. assert.strictEqual(localizedManifest.contributes?.authentication?.[0].label, 'Testauthentifizierung'); - assert.strictEqual((localizedManifest.contributes?.configuration as IConfiguration).title, 'Testkonfiguration'); + assert.strictEqual((localizedManifest.contributes?.configuration as IConfigurationNode).title, 'Testkonfiguration'); }); test('replaces template strings - is best effort #164630', function () { diff --git a/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts b/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts index 5e71b857..d94305a8 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { VSBuffer } from 'vs/base/common/buffer'; import { joinPath } from 'vs/base/common/resources'; diff --git a/patched-vscode/src/vs/platform/extensionManagement/test/node/extensionDownloader.test.ts b/patched-vscode/src/vs/platform/extensionManagement/test/node/extensionDownloader.test.ts index e803de72..bae93bde 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/test/node/extensionDownloader.test.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/test/node/extensionDownloader.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { platform } from 'vs/base/common/platform'; import { arch } from 'vs/base/common/process'; diff --git a/patched-vscode/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts b/patched-vscode/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts index 9e72c1c1..8c0569fe 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.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 * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { dirname, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts b/patched-vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts index 343051cc..120b9964 100644 --- a/patched-vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts +++ b/patched-vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts @@ -6,7 +6,6 @@ import { isWeb } from 'vs/base/common/platform'; import { format2 } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { IHeaders } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; @@ -99,7 +98,7 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi : version, path: 'extension' })); - return this._isWebExtensionResourceEndPoint(uri) ? URI.joinPath(URI.parse(window.location.href), uri.path) : uri; + return this._isWebExtensionResourceEndPoint(uri) ? uri.with({ scheme: RemoteAuthorities.getPreferredWebSchema() }) : uri; } return undefined; } @@ -110,8 +109,8 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi return !!this._extensionGalleryAuthority && this._extensionGalleryAuthority === this._getExtensionGalleryAuthority(uri); } - protected async getExtensionGalleryRequestHeaders(): Promise { - const headers: IHeaders = { + protected async getExtensionGalleryRequestHeaders(): Promise> { + const headers: Record = { 'X-Client-Name': `${this._productService.applicationName}${isWeb ? '-web' : ''}`, 'X-Client-Version': this._productService.version }; diff --git a/patched-vscode/src/vs/platform/extensions/common/extensionValidator.ts b/patched-vscode/src/vs/platform/extensions/common/extensionValidator.ts index cee5eaee..5fae8b4b 100644 --- a/patched-vscode/src/vs/platform/extensions/common/extensionValidator.ts +++ b/patched-vscode/src/vs/platform/extensions/common/extensionValidator.ts @@ -8,7 +8,8 @@ import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import * as semver from 'vs/base/common/semver/semver'; -import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, parseApiProposals } from 'vs/platform/extensions/common/extensions'; +import { allApiProposals } from 'vs/platform/extensions/common/extensionsApiProposals'; export interface IParsedVersion { hasCaret: boolean; @@ -239,7 +240,7 @@ export function isValidVersion(_inputVersion: string | INormalizedVersion, _inpu type ProductDate = string | Date | undefined; -export function validateExtensionManifest(productVersion: string, productDate: ProductDate, extensionLocation: URI, extensionManifest: IExtensionManifest, extensionIsBuiltin: boolean): readonly [Severity, string][] { +export function validateExtensionManifest(productVersion: string, productDate: ProductDate, extensionLocation: URI, extensionManifest: IExtensionManifest, extensionIsBuiltin: boolean, validateApiVersion: boolean): readonly [Severity, string][] { const validations: [Severity, string][] = []; if (typeof extensionManifest.publisher !== 'undefined' && typeof extensionManifest.publisher !== 'string') { validations.push([Severity.Error, nls.localize('extensionDescription.publisher', "property publisher must be of type `string`.")]); @@ -314,12 +315,22 @@ export function validateExtensionManifest(productVersion: string, productDate: P } const notices: string[] = []; - const isValid = isValidExtensionVersion(productVersion, productDate, extensionManifest, extensionIsBuiltin, notices); - if (!isValid) { + const validExtensionVersion = isValidExtensionVersion(productVersion, productDate, extensionManifest, extensionIsBuiltin, notices); + if (!validExtensionVersion) { for (const notice of notices) { validations.push([Severity.Error, notice]); } } + + if (validateApiVersion && extensionManifest.enabledApiProposals?.length) { + const incompatibleNotices: string[] = []; + if (!areApiProposalsCompatible([...extensionManifest.enabledApiProposals], incompatibleNotices)) { + for (const notice of incompatibleNotices) { + validations.push([Severity.Error, notice]); + } + } + } + return validations; } @@ -338,6 +349,34 @@ export function isEngineValid(engine: string, version: string, date: ProductDate return engine === '*' || isVersionValid(version, date, engine); } +export function areApiProposalsCompatible(apiProposals: string[]): boolean; +export function areApiProposalsCompatible(apiProposals: string[], notices: string[]): boolean; +export function areApiProposalsCompatible(apiProposals: string[], productApiProposals: Readonly<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>): boolean; +export function areApiProposalsCompatible(apiProposals: string[], arg1?: any): boolean { + if (apiProposals.length === 0) { + return true; + } + const notices: string[] | undefined = Array.isArray(arg1) ? arg1 : undefined; + const productApiProposals: Readonly<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }> = (notices ? undefined : arg1) ?? allApiProposals; + const incompatibleNotices: string[] = []; + const parsedProposals = parseApiProposals(apiProposals); + for (const { proposalName, version } of parsedProposals) { + const existingProposal = productApiProposals[proposalName]; + if (!existingProposal) { + continue; + } + if (!version) { + continue; + } + if (existingProposal.version !== version) { + incompatibleNotices.push(nls.localize('apiProposalMismatch', "Extension is using an API proposal '{0}' that is not compatible with the current version of VS Code.", proposalName)); + } + } + notices?.push(...incompatibleNotices); + return incompatibleNotices.length === 0; + +} + function isVersionValid(currentVersion: string, date: ProductDate, requestedVersion: string, notices: string[] = []): boolean { const desiredVersion = normalizeVersion(parseVersion(requestedVersion)); diff --git a/patched-vscode/src/vs/platform/extensions/common/extensions.ts b/patched-vscode/src/vs/platform/extensions/common/extensions.ts index cfe5313b..822260bd 100644 --- a/patched-vscode/src/vs/platform/extensions/common/extensions.ts +++ b/patched-vscode/src/vs/platform/extensions/common/extensions.ts @@ -21,19 +21,6 @@ export interface ICommand { category?: string | ILocalizedString; } -export interface IConfigurationProperty { - description: string; - type: string | string[]; - default?: any; -} - -export interface IConfiguration { - id?: string; - order?: number; - title?: string; - properties: { [key: string]: IConfigurationProperty }; -} - export interface IDebugger { label?: string; type: string; @@ -41,7 +28,7 @@ export interface IDebugger { } export interface IGrammar { - language: string; + language?: string; } export interface IJSONValidation { @@ -182,7 +169,7 @@ export interface ILocalizationContribution { export interface IExtensionContributions { commands?: ICommand[]; - configuration?: IConfiguration | IConfiguration[]; + configuration?: any; debuggers?: IDebugger[]; grammars?: IGrammar[]; jsonValidation?: IJSONValidation[]; @@ -239,7 +226,9 @@ export interface IExtensionIdentifier { } export const EXTENSION_CATEGORIES = [ + 'AI', 'Azure', + 'Chat', 'Data Science', 'Debuggers', 'Extension Packs', @@ -256,8 +245,6 @@ export const EXTENSION_CATEGORIES = [ 'Testing', 'Themes', 'Visualization', - 'AI', - 'Chat', 'Other', ]; @@ -284,6 +271,7 @@ export interface IRelaxedExtensionManifest { contributes?: IExtensionContributions; repository?: { url: string }; bugs?: { url: string }; + originalEnabledApiProposals?: readonly string[]; enabledApiProposals?: readonly string[]; api?: string; scripts?: { [key: string]: string }; @@ -492,6 +480,17 @@ export function isResolverExtension(manifest: IExtensionManifest, remoteAuthorit return false; } +export function parseApiProposals(enabledApiProposals: string[]): { proposalName: string; version?: number }[] { + return enabledApiProposals.map(proposal => { + const [proposalName, version] = proposal.split('@'); + return { proposalName, version: version ? parseInt(version) : undefined }; + }); +} + +export function parseEnabledApiProposalNames(enabledApiProposals: string[]): string[] { + return enabledApiProposals.map(proposal => proposal.split('@')[0]); +} + export const IBuiltinExtensionsScannerService = createDecorator('IBuiltinExtensionsScannerService'); export interface IBuiltinExtensionsScannerService { readonly _serviceBrand: undefined; diff --git a/patched-vscode/src/vs/platform/extensions/common/extensionsApiProposals.ts b/patched-vscode/src/vs/platform/extensions/common/extensionsApiProposals.ts new file mode 100644 index 00000000..0ec12d48 --- /dev/null +++ b/patched-vscode/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -0,0 +1,412 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. + +const _allApiProposals = { + activeComment: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.activeComment.d.ts', + }, + aiRelatedInformation: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiRelatedInformation.d.ts', + }, + aiTextSearchProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts', + }, + aiTextSearchProviderNew: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProviderNew.d.ts', + }, + attributableCoverage: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.attributableCoverage.d.ts', + }, + authLearnMore: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authLearnMore.d.ts', + }, + authSession: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', + }, + canonicalUriProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', + }, + chatParticipantAdditions: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts', + }, + chatParticipantPrivate: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts', + version: 2 + }, + chatProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', + }, + chatTab: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', + }, + chatVariableResolver: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts', + }, + codeActionAI: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', + }, + codeActionRanges: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', + }, + codiconDecoration: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', + }, + commentReactor: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts', + }, + commentReveal: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReveal.d.ts', + }, + commentThreadApplicability: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentThreadApplicability.d.ts', + }, + commentingRangeHint: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentingRangeHint.d.ts', + }, + commentsDraftState: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts', + }, + contribAccessibilityHelpContent: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribAccessibilityHelpContent.d.ts', + }, + contribChatParticipantDetection: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribChatParticipantDetection.d.ts', + }, + contribCommentEditorActionsMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts', + }, + contribCommentPeekContext: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts', + }, + contribCommentThreadAdditionalMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts', + }, + contribCommentsViewThreadMenus: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentsViewThreadMenus.d.ts', + }, + contribDebugCreateConfiguration: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribDebugCreateConfiguration.d.ts', + }, + contribDiffEditorGutterToolBarMenus: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribDiffEditorGutterToolBarMenus.d.ts', + }, + contribEditSessions: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts', + }, + contribEditorContentMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', + }, + contribIssueReporter: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts', + }, + contribLabelFormatterWorkspaceTooltip: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', + }, + contribMenuBarHome: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', + }, + contribMergeEditorMenus: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts', + }, + contribMultiDiffEditorMenus: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMultiDiffEditorMenus.d.ts', + }, + contribNotebookStaticPreloads: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts', + }, + contribRemoteHelp: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', + }, + contribShareMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', + }, + contribSourceControlHistoryItemChangesMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemChangesMenu.d.ts', + }, + contribSourceControlHistoryItemGroupMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemGroupMenu.d.ts', + }, + contribSourceControlHistoryItemMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryItemMenu.d.ts', + }, + contribSourceControlHistoryTitleMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlHistoryTitleMenu.d.ts', + }, + contribSourceControlInputBoxMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts', + }, + contribSourceControlTitleMenu: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlTitleMenu.d.ts', + }, + contribStatusBarItems: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts', + }, + contribViewContainerTitle: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewContainerTitle.d.ts', + }, + contribViewsRemote: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', + }, + contribViewsWelcome: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', + }, + createFileSystemWatcher: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts', + }, + customEditorMove: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', + }, + debugVisualization: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugVisualization.d.ts', + }, + defaultChatParticipant: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts', + }, + diffCommand: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', + }, + diffContentOptions: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts', + }, + documentFiltersExclusive: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts', + }, + documentPaste: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentPaste.d.ts', + }, + editSessionIdentityProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts', + }, + editorHoverVerbosityLevel: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorHoverVerbosityLevel.d.ts', + }, + editorInsets: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts', + }, + embeddings: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.embeddings.d.ts', + }, + extensionRuntime: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts', + }, + extensionsAny: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionsAny.d.ts', + }, + externalUriOpener: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts', + }, + fileComments: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileComments.d.ts', + }, + fileSearchProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts', + }, + fileSearchProviderNew: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts', + }, + findFiles2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts', + }, + findFiles2New: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2New.d.ts', + }, + findTextInFiles: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', + }, + findTextInFilesNew: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFilesNew.d.ts', + }, + fsChunks: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', + }, + idToken: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts', + }, + inlineCompletionsAdditions: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts', + }, + inlineEdit: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts', + }, + interactive: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts', + }, + interactiveWindow: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', + }, + ipc: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', + }, + languageModelSystem: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts', + }, + languageStatusText: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts', + }, + lmTools: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.lmTools.d.ts', + version: 6 + }, + mappedEditsProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', + }, + multiDocumentHighlightProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', + }, + newSymbolNamesProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts', + }, + notebookCellExecution: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecution.d.ts', + }, + notebookCellExecutionState: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', + }, + notebookControllerAffinityHidden: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts', + }, + notebookDeprecated: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts', + }, + notebookExecution: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookExecution.d.ts', + }, + notebookKernelSource: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts', + }, + notebookLiveShare: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts', + }, + notebookMessaging: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts', + }, + notebookMime: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts', + }, + notebookReplDocument: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookReplDocument.d.ts', + }, + notebookVariableProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts', + }, + portsAttributes: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts', + }, + profileContentHandlers: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts', + }, + quickDiffProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts', + }, + quickInputButtonLocation: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts', + }, + quickPickItemTooltip: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts', + }, + quickPickSortByLabel: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', + }, + resolvers: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', + }, + scmActionButton: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', + }, + scmHistoryProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts', + }, + scmMultiDiffEditor: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts', + }, + scmSelectedProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts', + }, + scmTextDocument: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmTextDocument.d.ts', + }, + scmValidation: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts', + }, + shareProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.shareProvider.d.ts', + }, + showLocal: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.showLocal.d.ts', + }, + speech: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.speech.d.ts', + }, + tabInputMultiDiff: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputMultiDiff.d.ts', + }, + tabInputTextMerge: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts', + }, + taskPresentationGroup: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', + }, + telemetry: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts', + }, + terminalDataWriteEvent: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', + }, + terminalDimensions: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', + }, + terminalExecuteCommandEvent: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalExecuteCommandEvent.d.ts', + }, + terminalQuickFixProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts', + }, + terminalSelection: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts', + }, + testObserver: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts', + }, + testRelatedCode: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testRelatedCode.d.ts', + }, + textSearchCompleteNew: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchCompleteNew.d.ts', + }, + textSearchProvider: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', + }, + textSearchProviderNew: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProviderNew.d.ts', + }, + timeline: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', + }, + tokenInformation: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts', + }, + treeViewActiveItem: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts', + }, + treeViewMarkdownMessage: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewMarkdownMessage.d.ts', + }, + treeViewReveal: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts', + }, + tunnelFactory: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts', + }, + tunnels: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts', + }, + workspaceTrust: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts', + } +}; +export const allApiProposals = Object.freeze<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>(_allApiProposals); +export type ApiProposalName = keyof typeof _allApiProposals; diff --git a/patched-vscode/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/patched-vscode/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index fec3b6ed..4dae8d5c 100644 --- a/patched-vscode/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/patched-vscode/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -3,18 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Promises } from 'vs/base/common/async'; import { canceled } from 'vs/base/common/errors'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; import { Event } from 'vs/base/common/event'; -import { ILogService } from 'vs/platform/log/common/log'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { Promises } from 'vs/base/common/async'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WindowUtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { +export class ExtensionHostStarter extends Disposable implements IDisposable, IExtensionHostStarter { readonly _serviceBrand: undefined; @@ -29,16 +29,18 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter @IWindowsMainService private readonly _windowsMainService: IWindowsMainService, @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { + super(); // On shutdown: gracefully await extension host shutdowns - this._lifecycleMainService.onWillShutdown(e => { + this._register(this._lifecycleMainService.onWillShutdown(e => { this._shutdown = true; e.join('extHostStarter', this._waitForAllExit(6000)); - }); + })); } - dispose(): void { + override dispose(): void { // Intentionally not killing the extension host processes + super.dispose(); } private _getExtHost(id: string): WindowUtilityProcess { @@ -72,7 +74,8 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter const id = String(++ExtensionHostStarter._lastId); const extHost = new WindowUtilityProcess(this._logService, this._windowsMainService, this._telemetryService, this._lifecycleMainService); this._extHosts.set(id, extHost); - extHost.onExit(({ pid, code, signal }) => { + const disposable = extHost.onExit(({ pid, code, signal }) => { + disposable.dispose(); this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); setTimeout(() => { extHost.dispose(); @@ -110,6 +113,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter execArgv: opts.execArgv, allowLoadingUnsignedLibraries: true, forceAllocationsToV8Sandbox: true, + respondToAuthRequestsFromMainProcess: true, correlationId: id }); const pid = await Event.toPromise(extHost.onSpawn); diff --git a/patched-vscode/src/vs/platform/extensions/test/common/extensionValidator.test.ts b/patched-vscode/src/vs/platform/extensions/test/common/extensionValidator.test.ts index 90607837..6ac5821e 100644 --- a/patched-vscode/src/vs/platform/extensions/test/common/extensionValidator.test.ts +++ b/patched-vscode/src/vs/platform/extensions/test/common/extensionValidator.test.ts @@ -2,10 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { INormalizedVersion, IParsedVersion, isValidExtensionVersion, isValidVersion, isValidVersionStr, normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator'; +import { areApiProposalsCompatible, INormalizedVersion, IParsedVersion, isValidExtensionVersion, isValidVersion, isValidVersionStr, normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator'; suite('Extension Version Validator', () => { @@ -423,4 +423,21 @@ suite('Extension Version Validator', () => { }; assert.strictEqual(isValidExtensionVersion('1.44.0', undefined, manifest, false, []), false); }); + + test('areApiProposalsCompatible', () => { + assert.strictEqual(areApiProposalsCompatible([]), true); + assert.strictEqual(areApiProposalsCompatible([], ['hello']), true); + assert.strictEqual(areApiProposalsCompatible([], {}), true); + assert.strictEqual(areApiProposalsCompatible(['proposal1'], {}), true); + assert.strictEqual(areApiProposalsCompatible(['proposal1'], { 'proposal1': { proposal: '' } }), true); + assert.strictEqual(areApiProposalsCompatible(['proposal1'], { 'proposal1': { proposal: '', version: 1 } }), true); + assert.strictEqual(areApiProposalsCompatible(['proposal1@1'], { 'proposal1': { proposal: '', version: 1 } }), true); + assert.strictEqual(areApiProposalsCompatible(['proposal1'], { 'proposal2': { proposal: '' } }), true); + assert.strictEqual(areApiProposalsCompatible(['proposal1', 'proposal2'], {}), true); + assert.strictEqual(areApiProposalsCompatible(['proposal1', 'proposal2'], { 'proposal1': { proposal: '' } }), true); + + assert.strictEqual(areApiProposalsCompatible(['proposal1@1'], { 'proposal1': { proposal: '', version: 2 } }), false); + assert.strictEqual(areApiProposalsCompatible(['proposal1@1'], { 'proposal1': { proposal: '' } }), false); + }); + }); diff --git a/patched-vscode/src/vs/platform/extensions/test/common/extensions.test.ts b/patched-vscode/src/vs/platform/extensions/test/common/extensions.test.ts new file mode 100644 index 00000000..7b81268b --- /dev/null +++ b/patched-vscode/src/vs/platform/extensions/test/common/extensions.test.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { parseEnabledApiProposalNames } from 'vs/platform/extensions/common/extensions'; + +suite('Parsing Enabled Api Proposals', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('parsingEnabledApiProposals', () => { + assert.deepStrictEqual(['activeComment', 'commentsDraftState'], parseEnabledApiProposalNames(['activeComment', 'commentsDraftState'])); + assert.deepStrictEqual(['activeComment', 'commentsDraftState'], parseEnabledApiProposalNames(['activeComment', 'commentsDraftState@1'])); + assert.deepStrictEqual(['activeComment', 'commentsDraftState'], parseEnabledApiProposalNames(['activeComment', 'commentsDraftState@'])); + assert.deepStrictEqual(['activeComment', 'commentsDraftState'], parseEnabledApiProposalNames(['activeComment', 'commentsDraftState@randomstring'])); + assert.deepStrictEqual(['activeComment', 'commentsDraftState'], parseEnabledApiProposalNames(['activeComment', 'commentsDraftState@1234'])); + assert.deepStrictEqual(['activeComment', 'commentsDraftState'], parseEnabledApiProposalNames(['activeComment', 'commentsDraftState@1234_random'])); + }); + +}); diff --git a/patched-vscode/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/patched-vscode/src/vs/platform/externalTerminal/node/externalTerminalService.ts index 5086c95a..9f85b145 100644 --- a/patched-vscode/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/patched-vscode/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -56,8 +56,9 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl const cmdArgs = ['/c', 'start', '/wait']; if (exec.indexOf(' ') >= 0) { // The "" argument is the window title. Without this, exec doesn't work when the path - // contains spaces - cmdArgs.push('""'); + // contains spaces. #6590 + // Title is Execution Path. #220129 + cmdArgs.push(exec); } cmdArgs.push(exec); // Add starting directory parameter for Windows Terminal (see #90734) diff --git a/patched-vscode/src/vs/platform/files/common/files.ts b/patched-vscode/src/vs/platform/files/common/files.ts index 149665bb..82d4f9ba 100644 --- a/patched-vscode/src/vs/platform/files/common/files.ts +++ b/patched-vscode/src/vs/platform/files/common/files.ts @@ -1460,7 +1460,7 @@ export interface IGlobPatterns { } export interface IFilesConfiguration { - files: IFilesConfigurationNode; + files?: IFilesConfigurationNode; } export interface IFilesConfigurationNode { @@ -1470,6 +1470,7 @@ export interface IFilesConfigurationNode { watcherInclude: string[]; encoding: string; autoGuessEncoding: boolean; + candidateGuessEncodings: string[]; defaultLanguage: string; trimTrailingWhitespace: boolean; autoSave: string; diff --git a/patched-vscode/src/vs/platform/files/common/watcher.ts b/patched-vscode/src/vs/platform/files/common/watcher.ts index 05ff156d..eef16ccf 100644 --- a/patched-vscode/src/vs/platform/files/common/watcher.ts +++ b/patched-vscode/src/vs/platform/files/common/watcher.ts @@ -181,16 +181,14 @@ export interface IUniversalWatcher extends IWatcher { export abstract class AbstractWatcherClient extends Disposable { - private static readonly MAX_RESTARTS_PER_REQUEST_ERROR = 3; // how often we give a request a chance to restart on error - private static readonly MAX_RESTARTS_PER_UNKNOWN_ERROR = 10; // how often we give the watcher a chance to restart on unknown errors (like crash) + private static readonly MAX_RESTARTS = 5; private watcher: IWatcher | undefined; private readonly watcherDisposables = this._register(new MutableDisposable()); private requests: IWatchRequest[] | undefined = undefined; - private restartsPerRequestError = new Map(); - private restartsPerUnknownError = 0; + private restartCounter = 0; constructor( private readonly onFileChanges: (changes: IFileChange[]) => void, @@ -224,41 +222,51 @@ export abstract class AbstractWatcherClient extends Disposable { protected onError(error: string, failedRequest?: IUniversalWatchRequest): void { - // Restart on error (up to N times, if enabled) - if (this.options.restartOnError && this.requests?.length) { - - // A request failed - if (failedRequest) { - const restartsPerRequestError = this.restartsPerRequestError.get(failedRequest.path) ?? 0; - if (restartsPerRequestError < AbstractWatcherClient.MAX_RESTARTS_PER_REQUEST_ERROR) { - this.error(`restarting watcher from error in watch request (retrying request): ${error} (${JSON.stringify(failedRequest)})`); - this.restartsPerRequestError.set(failedRequest.path, restartsPerRequestError + 1); - this.restart(this.requests); - } else { - this.error(`restarting watcher from error in watch request (skipping request): ${error} (${JSON.stringify(failedRequest)})`); - this.restart(this.requests.filter(request => request.path !== failedRequest.path)); - } - } - - // Any request failed or process crashed - else { - if (this.restartsPerUnknownError < AbstractWatcherClient.MAX_RESTARTS_PER_UNKNOWN_ERROR) { - this.error(`restarting watcher after unknown global error: ${error}`); - this.restartsPerUnknownError++; - this.restart(this.requests); - } else { - this.error(`giving up attempting to restart watcher after error: ${error}`); - } + // Restart on error (up to N times, if possible) + if (this.canRestart(error, failedRequest)) { + if (this.restartCounter < AbstractWatcherClient.MAX_RESTARTS && this.requests) { + this.error(`restarting watcher after unexpected error: ${error}`); + this.restart(this.requests); + } else { + this.error(`gave up attempting to restart watcher after unexpected error: ${error}`); } } - // Do not attempt to restart if not enabled + // Do not attempt to restart otherwise, report the error else { this.error(error); } } + private canRestart(error: string, failedRequest?: IUniversalWatchRequest): boolean { + if (!this.options.restartOnError) { + return false; // disabled by options + } + + if (failedRequest) { + // do not treat a failing request as a reason to restart the entire + // watcher. it is possible that from a large amount of watch requests + // some fail and we would constantly restart all requests only because + // of that. rather, continue the watcher and leave the failed request + return false; + } + + if ( + error.indexOf('No space left on device') !== -1 || + error.indexOf('EMFILE') !== -1 + ) { + // do not restart when the error indicates that the system is running + // out of handles for file watching. this is not recoverable anyway + // and needs changes to the system before continuing + return false; + } + + return true; + } + private restart(requests: IUniversalWatchRequest[]): void { + this.restartCounter++; + this.init(); this.watch(requests); } diff --git a/patched-vscode/src/vs/platform/files/node/diskFileSystemProvider.ts b/patched-vscode/src/vs/platform/files/node/diskFileSystemProvider.ts index 180aa7e2..9993c5f8 100644 --- a/patched-vscode/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/patched-vscode/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; -import { gracefulify } from 'graceful-fs'; +import { Stats, promises } from 'fs'; import { Barrier, retry } from 'vs/base/common/async'; import { ResourceMap } from 'vs/base/common/map'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -24,22 +23,9 @@ import { readFileIntoStream } from 'vs/platform/files/common/io'; import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, ILogMessage } from 'vs/platform/files/common/watcher'; import { ILogService } from 'vs/platform/log/common/log'; import { AbstractDiskFileSystemProvider, IDiskFileSystemProviderOptions } from 'vs/platform/files/common/diskFileSystemProvider'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; import { UniversalWatcherClient } from 'vs/platform/files/node/watcher/watcherClient'; import { NodeJSWatcherClient } from 'vs/platform/files/node/watcher/nodejs/nodejsClient'; -/** - * Enable graceful-fs very early from here to have it enabled - * in all contexts that leverage the disk file system provider. - */ -(() => { - try { - gracefulify(fs); - } catch (error) { - console.error(`Error enabling graceful-fs: ${toErrorMessage(error)}`); - } -})(); - export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, @@ -139,7 +125,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple } } - private toType(entry: fs.Stats | IDirent, symbolicLink?: { dangling: boolean }): FileType { + private toType(entry: Stats | IDirent, symbolicLink?: { dangling: boolean }): FileType { // Signal file type by checking for file / directory, except: // - symbolic links pointing to nonexistent files are FileType.Unknown @@ -217,7 +203,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple const filePath = this.toFilePath(resource); - return await Promises.readFile(filePath); + return await promises.readFile(filePath); } catch (error) { throw this.toFileSystemProviderError(error); } finally { @@ -368,7 +354,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple try { const { stat } = await SymlinkSupport.stat(filePath); if (!(stat.mode & 0o200 /* File mode indicating writable by owner */)) { - await Promises.chmod(filePath, stat.mode | 0o200); + await promises.chmod(filePath, stat.mode | 0o200); } } catch (error) { if (error.code !== 'ENOENT') { @@ -387,7 +373,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple // by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows // (see https://github.com/microsoft/vscode/issues/931) and prevent removing alternate data streams // (see https://github.com/microsoft/vscode/issues/6363) - await Promises.truncate(filePath, 0); + await promises.truncate(filePath, 0); // After a successful truncate() the flag can be set to 'r+' which will not truncate. flags = 'r+'; @@ -609,7 +595,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple async mkdir(resource: URI): Promise { try { - await Promises.mkdir(this.toFilePath(resource)); + await promises.mkdir(this.toFilePath(resource)); } catch (error) { throw this.toFileSystemProviderError(error); } @@ -627,7 +613,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple await Promises.rm(filePath, RimRafMode.MOVE, rmMoveToPath); } else { try { - await Promises.unlink(filePath); + await promises.unlink(filePath); } catch (unlinkError) { // `fs.unlink` will throw when used on directories @@ -645,7 +631,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple } if (isDirectory) { - await Promises.rmdir(filePath); + await promises.rmdir(filePath); } else { throw unlinkError; } @@ -792,10 +778,10 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple locks.add(await this.createResourceLock(to)); if (mkdir) { - await Promises.mkdir(dirname(toFilePath), { recursive: true }); + await promises.mkdir(dirname(toFilePath), { recursive: true }); } - await Promises.copyFile(fromFilePath, toFilePath); + await promises.copyFile(fromFilePath, toFilePath); } catch (error) { if (error.code === 'ENOENT' && !mkdir) { return this.doCloneFile(from, to, true); diff --git a/patched-vscode/src/vs/platform/files/node/watcher/baseWatcher.ts b/patched-vscode/src/vs/platform/files/node/watcher/baseWatcher.ts index 3576f20d..c5665396 100644 --- a/patched-vscode/src/vs/platform/files/node/watcher/baseWatcher.ts +++ b/patched-vscode/src/vs/platform/files/node/watcher/baseWatcher.ts @@ -9,7 +9,7 @@ import { ILogMessage, IRecursiveWatcherWithSubscribe, IUniversalWatchRequest, IW import { Emitter, Event } from 'vs/base/common/event'; import { FileChangeType, IFileChange } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { DeferredPromise } from 'vs/base/common/async'; +import { DeferredPromise, ThrottledDelayer } from 'vs/base/common/async'; export abstract class BaseWatcher extends Disposable implements IWatcher { @@ -28,6 +28,8 @@ export abstract class BaseWatcher extends Disposable implements IWatcher { private readonly suspendedWatchRequests = this._register(new DisposableMap()); private readonly suspendedWatchRequestsWithPolling = new Set(); + private readonly updateWatchersDelayer = this._register(new ThrottledDelayer(this.getUpdateWatchersDelay())); + protected readonly suspendedWatchRequestPollingInterval: number = 5007; // node.js default private joinWatch = new DeferredPromise(); @@ -88,17 +90,21 @@ export abstract class BaseWatcher extends Disposable implements IWatcher { } } - return await this.updateWatchers(); + return await this.updateWatchers(false /* not delayed */); } finally { this.joinWatch.complete(); } } - private updateWatchers(): Promise { - return this.doWatch([ + private updateWatchers(delayed: boolean): Promise { + return this.updateWatchersDelayer.trigger(() => this.doWatch([ ...this.allNonCorrelatedWatchRequests, ...Array.from(this.allCorrelatedWatchRequests.values()).filter(request => !this.suspendedWatchRequests.has(request.correlationId)) - ]); + ]), delayed ? this.getUpdateWatchersDelay() : 0); + } + + protected getUpdateWatchersDelay(): number { + return 800; } isSuspended(request: IUniversalWatchRequest): 'polling' | boolean { @@ -130,14 +136,14 @@ export abstract class BaseWatcher extends Disposable implements IWatcher { this.monitorSuspendedWatchRequest(request, disposables); - this.updateWatchers(); + this.updateWatchers(true /* delay this call as we might accumulate many failing watch requests on startup */); } private resumeWatchRequest(request: IWatchRequestWithCorrelation): void { this.suspendedWatchRequests.deleteAndDispose(request.correlationId); this.suspendedWatchRequestsWithPolling.delete(request.correlationId); - this.updateWatchers(); + this.updateWatchers(false); } private monitorSuspendedWatchRequest(request: IWatchRequestWithCorrelation, disposables: DisposableStore): void { diff --git a/patched-vscode/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts b/patched-vscode/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts index c71f1581..eec6a223 100644 --- a/patched-vscode/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts +++ b/patched-vscode/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { watch } from 'fs'; +import { watch, promises } from 'fs'; import { RunOnceWorker, ThrottledWorker } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { isEqualOrParent } from 'vs/base/common/extpath'; @@ -82,7 +82,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { return; } - const stat = await Promises.stat(realPath); + const stat = await promises.stat(realPath); if (this.cts.token.isCancellationRequested) { return; diff --git a/patched-vscode/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/patched-vscode/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index afabd7ae..46d213c1 100644 --- a/patched-vscode/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/patched-vscode/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -226,7 +226,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS if (request.pollingInterval) { this.startPolling(request, request.pollingInterval); } else { - this.startWatching(request); + await this.startWatching(request); } } } @@ -322,7 +322,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS pollingWatcher.schedule(0); } - private startWatching(request: IRecursiveWatchRequest, restarts = 0): void { + private async startWatching(request: IRecursiveWatchRequest, restarts = 0): Promise { const cts = new CancellationTokenSource(); const instance = new DeferredPromise(); @@ -349,36 +349,38 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS // Path checks for symbolic links / wrong casing const { realPath, realPathDiffers, realPathLength } = this.normalizePath(request); - parcelWatcher.subscribe(realPath, (error, parcelEvents) => { - if (watcher.token.isCancellationRequested) { - return; // return early when disposed - } + try { + const parcelWatcherInstance = await parcelWatcher.subscribe(realPath, (error, parcelEvents) => { + if (watcher.token.isCancellationRequested) { + return; // return early when disposed + } - // In any case of an error, treat this like a unhandled exception - // that might require the watcher to restart. We do not really know - // the state of parcel at this point and as such will try to restart - // up to our maximum of restarts. - if (error) { - this.onUnexpectedError(error, request); - } + // In any case of an error, treat this like a unhandled exception + // that might require the watcher to restart. We do not really know + // the state of parcel at this point and as such will try to restart + // up to our maximum of restarts. + if (error) { + this.onUnexpectedError(error, request); + } + + // Handle & emit events + this.onParcelEvents(parcelEvents, watcher, realPathDiffers, realPathLength); + }, { + backend: ParcelWatcher.PARCEL_WATCHER_BACKEND, + ignore: watcher.request.excludes + }); - // Handle & emit events - this.onParcelEvents(parcelEvents, watcher, realPathDiffers, realPathLength); - }, { - backend: ParcelWatcher.PARCEL_WATCHER_BACKEND, - ignore: watcher.request.excludes - }).then(parcelWatcher => { this.trace(`Started watching: '${realPath}' with backend '${ParcelWatcher.PARCEL_WATCHER_BACKEND}'`); - instance.complete(parcelWatcher); - }).catch(error => { + instance.complete(parcelWatcherInstance); + } catch (error) { this.onUnexpectedError(error, request); instance.complete(undefined); watcher.notifyWatchFailed(); this._onDidWatchFail.fire(request); - }); + } } private onParcelEvents(parcelEvents: parcelWatcher.Event[], watcher: ParcelWatcherInstance, realPathDiffers: boolean, realPathLength: number): void { @@ -662,7 +664,7 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS if (watcher.request.pollingInterval) { this.startPolling(watcher.request, watcher.request.pollingInterval, watcher.restarts + 1); } else { - this.startWatching(watcher.request, watcher.restarts + 1); + await this.startWatching(watcher.request, watcher.restarts + 1); } } finally { restartPromise.complete(); diff --git a/patched-vscode/src/vs/platform/files/test/browser/fileService.test.ts b/patched-vscode/src/vs/platform/files/test/browser/fileService.test.ts index 114e98ad..166cf549 100644 --- a/patched-vscode/src/vs/platform/files/test/browser/fileService.test.ts +++ b/patched-vscode/src/vs/platform/files/test/browser/fileService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DeferredPromise, timeout } from 'vs/base/common/async'; import { bufferToReadable, bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; diff --git a/patched-vscode/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts b/patched-vscode/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts index 9290520e..0ee13e7c 100644 --- a/patched-vscode/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts +++ b/patched-vscode/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IndexedDB } from 'vs/base/browser/indexedDB'; import { bufferToReadable, bufferToStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -189,7 +189,7 @@ flakySuite('IndexedDBFileSystemProvider', function () { assert.strictEqual(value.mtime, undefined); assert.strictEqual(value.ctime, undefined); } else { - assert.ok(!'Unexpected value ' + basename(value.resource)); + assert.fail('Unexpected value ' + basename(value.resource)); } }); }); diff --git a/patched-vscode/src/vs/platform/files/test/common/files.test.ts b/patched-vscode/src/vs/platform/files/test/common/files.test.ts index 1de7f863..245d2222 100644 --- a/patched-vscode/src/vs/platform/files/test/common/files.test.ts +++ b/patched-vscode/src/vs/platform/files/test/common/files.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { isEqual, isEqualOrParent } from 'vs/base/common/extpath'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/platform/files/test/common/watcher.test.ts b/patched-vscode/src/vs/platform/files/test/common/watcher.test.ts index 23776f54..6d7bb7f3 100644 --- a/patched-vscode/src/vs/platform/files/test/common/watcher.test.ts +++ b/patched-vscode/src/vs/platform/files/test/common/watcher.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isLinux, isWindows } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/platform/files/test/node/diskFileService.integrationTest.ts b/patched-vscode/src/vs/platform/files/test/node/diskFileService.integrationTest.ts index fbf1ea0d..681e390e 100644 --- a/patched-vscode/src/vs/platform/files/test/node/diskFileService.integrationTest.ts +++ b/patched-vscode/src/vs/platform/files/test/node/diskFileService.integrationTest.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { createReadStream, existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs'; +import assert from 'assert'; +import { createReadStream, existsSync, readdirSync, readFileSync, statSync, writeFileSync, promises } from 'fs'; import { tmpdir } from 'os'; import { timeout } from 'vs/base/common/async'; import { bufferToReadable, bufferToStream, streamToBuffer, streamToBufferReadableStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; @@ -273,7 +273,7 @@ flakySuite('Disk File Service', function () { assert.strictEqual(value.mtime, undefined); assert.strictEqual(value.ctime, undefined); } else { - assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); + assert.fail('Unexpected value ' + basename(value.resource.fsPath)); } }); }); @@ -317,7 +317,7 @@ flakySuite('Disk File Service', function () { assert.ok(value.mtime > 0); assert.ok(value.ctime > 0); } else { - assert.ok(!'Unexpected value ' + basename(value.resource.fsPath)); + assert.fail('Unexpected value ' + basename(value.resource.fsPath)); } }); }); @@ -429,7 +429,7 @@ flakySuite('Disk File Service', function () { test('resolve - folder symbolic link', async () => { const link = URI.file(join(testDir, 'deep-link')); - await Promises.symlink(join(testDir, 'deep'), link.fsPath, 'junction'); + await promises.symlink(join(testDir, 'deep'), link.fsPath, 'junction'); const resolved = await service.resolve(link); assert.strictEqual(resolved.children!.length, 4); @@ -439,7 +439,7 @@ flakySuite('Disk File Service', function () { (isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('resolve - file symbolic link', async () => { const link = URI.file(join(testDir, 'lorem.txt-linked')); - await Promises.symlink(join(testDir, 'lorem.txt'), link.fsPath); + await promises.symlink(join(testDir, 'lorem.txt'), link.fsPath); const resolved = await service.resolve(link); assert.strictEqual(resolved.isDirectory, false); @@ -447,7 +447,7 @@ flakySuite('Disk File Service', function () { }); test('resolve - symbolic link pointing to nonexistent file does not break', async () => { - await Promises.symlink(join(testDir, 'foo'), join(testDir, 'bar'), 'junction'); + await promises.symlink(join(testDir, 'foo'), join(testDir, 'bar'), 'junction'); const resolved = await service.resolve(URI.file(testDir)); assert.strictEqual(resolved.isDirectory, true); @@ -530,7 +530,7 @@ flakySuite('Disk File Service', function () { (isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('deleteFile - symbolic link (exists)', async () => { const target = URI.file(join(testDir, 'lorem.txt')); const link = URI.file(join(testDir, 'lorem.txt-linked')); - await Promises.symlink(target.fsPath, link.fsPath); + await promises.symlink(target.fsPath, link.fsPath); const source = await service.resolve(link); @@ -552,7 +552,7 @@ flakySuite('Disk File Service', function () { (isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('deleteFile - symbolic link (pointing to nonexistent file)', async () => { const target = URI.file(join(testDir, 'foo')); const link = URI.file(join(testDir, 'bar')); - await Promises.symlink(target.fsPath, link.fsPath); + await promises.symlink(target.fsPath, link.fsPath); let event: FileOperationEvent; disposables.add(service.onDidRunOperation(e => event = e)); @@ -1692,7 +1692,7 @@ flakySuite('Disk File Service', function () { (isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('readFile - dangling symbolic link - https://github.com/microsoft/vscode/issues/116049', async () => { const link = URI.file(join(testDir, 'small.js-link')); - await Promises.symlink(join(testDir, 'small.js'), link.fsPath); + await promises.symlink(join(testDir, 'small.js'), link.fsPath); let error: FileOperationError | undefined = undefined; try { @@ -1833,7 +1833,7 @@ flakySuite('Disk File Service', function () { (isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('writeFile - atomic writing does not break symlinks', async () => { const link = URI.file(join(testDir, 'lorem.txt-linked')); - await Promises.symlink(join(testDir, 'lorem.txt'), link.fsPath); + await promises.symlink(join(testDir, 'lorem.txt'), link.fsPath); const content = 'Updates to the lorem file'; await service.writeFile(link, VSBuffer.fromString(content), { atomic: { postfix: '.vsctmp' } }); @@ -2006,7 +2006,7 @@ flakySuite('Disk File Service', function () { // Here since `close` is not called, all other writes are // waiting on the barrier to release, so doing a readFile // should give us a consistent view of the file contents - assert.strictEqual((await Promises.readFile(resource.fsPath)).toString(), content); + assert.strictEqual((await promises.readFile(resource.fsPath)).toString(), content); } finally { await provider.close(fd); } @@ -2030,10 +2030,10 @@ flakySuite('Disk File Service', function () { try { await provider.write(fd1, 0, VSBuffer.fromString(newContent).buffer, 0, VSBuffer.fromString(newContent).buffer.byteLength); - assert.strictEqual((await Promises.readFile(resource1.fsPath)).toString(), newContent); + assert.strictEqual((await promises.readFile(resource1.fsPath)).toString(), newContent); await provider.write(fd2, 0, VSBuffer.fromString(newContent).buffer, 0, VSBuffer.fromString(newContent).buffer.byteLength); - assert.strictEqual((await Promises.readFile(resource2.fsPath)).toString(), newContent); + assert.strictEqual((await promises.readFile(resource2.fsPath)).toString(), newContent); } finally { await Promise.allSettled([ await provider.close(fd1), @@ -2059,7 +2059,7 @@ flakySuite('Disk File Service', function () { assert.ok(error); // expected because `new-folder` does not exist - await Promises.mkdir(newFolder); + await promises.mkdir(newFolder); const content = readFileSync(URI.file(join(testDir, 'lorem.txt')).fsPath); const newContent = content.toString() + content.toString(); @@ -2069,7 +2069,7 @@ flakySuite('Disk File Service', function () { try { await provider.write(fd, 0, newContentBuffer, 0, newContentBuffer.byteLength); - assert.strictEqual((await Promises.readFile(newResource.fsPath)).toString(), newContent); + assert.strictEqual((await promises.readFile(newResource.fsPath)).toString(), newContent); } finally { await provider.close(fd); } @@ -2291,8 +2291,8 @@ flakySuite('Disk File Service', function () { const content = await service.writeFile(lockedFile, VSBuffer.fromString('Locked File')); assert.strictEqual(content.locked, false); - const stats = await Promises.stat(lockedFile.fsPath); - await Promises.chmod(lockedFile.fsPath, stats.mode & ~0o200); + const stats = await promises.stat(lockedFile.fsPath); + await promises.chmod(lockedFile.fsPath, stats.mode & ~0o200); let stat = await service.stat(lockedFile); assert.strictEqual(stat.locked, true); diff --git a/patched-vscode/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts b/patched-vscode/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts index 836b67ed..c53f1542 100644 --- a/patched-vscode/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts +++ b/patched-vscode/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import * as fs from 'fs'; +import assert from 'assert'; import { tmpdir } from 'os'; import { basename, dirname, join } from 'vs/base/common/path'; import { Promises, RimRafMode } from 'vs/base/node/pfs'; -import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { FileChangeFilter, FileChangeType } from 'vs/platform/files/common/files'; import { INonRecursiveWatchRequest, IRecursiveWatcherWithSubscribe } from 'vs/platform/files/common/watcher'; import { watchFileContents } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcherLib'; @@ -29,7 +30,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // mocha but generally). as such they will run only on demand // whenever we update the watcher library. -((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('File Watcher (node.js)', () => { +suite.skip('File Watcher (node.js)', () => { class TestNodeJSWatcher extends NodeJSWatcher { @@ -40,6 +41,10 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int readonly onWatchFail = this._onDidWatchFail.event; + protected override getUpdateWatchersDelay(): number { + return 0; + } + protected override async doWatch(requests: INonRecursiveWatchRequest[]): Promise { await super.doWatch(requests); for (const watcher of this.watchers) { @@ -154,7 +159,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // New folder const newFolderPath = join(testDir, 'New Folder'); changeFuture = awaitEvent(watcher, newFolderPath, FileChangeType.ADDED); - await Promises.mkdir(newFolderPath); + await fs.promises.mkdir(newFolderPath); await changeFuture; // Rename file @@ -216,7 +221,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // Copy file const copiedFilepath = join(testDir, 'copiedFile.txt'); changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.ADDED); - await Promises.copyFile(movedFilepath, copiedFilepath); + await fs.promises.copyFile(movedFilepath, copiedFilepath); await changeFuture; // Copy folder @@ -238,12 +243,12 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // Delete file changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.DELETED); - await Promises.unlink(copiedFilepath); + await fs.promises.unlink(copiedFilepath); await changeFuture; // Delete folder changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.DELETED); - await Promises.rmdir(copiedFolderpath); + await fs.promises.rmdir(copiedFolderpath); await changeFuture; watcher.dispose(); @@ -266,7 +271,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // Delete file changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED); - await Promises.unlink(filePath); + await fs.promises.unlink(filePath); await changeFuture; // Recreate watcher @@ -286,7 +291,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // Delete + Recreate file const newFilePath = join(testDir, 'lorem.txt'); const changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED); - await Promises.unlink(newFilePath); + await fs.promises.unlink(newFilePath); Promises.writeFile(newFilePath, 'Hello Atomic World'); await changeFuture; }); @@ -298,7 +303,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // Delete + Recreate file const newFilePath = join(filePath); const changeFuture: Promise = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED); - await Promises.unlink(newFilePath); + await fs.promises.unlink(newFilePath); Promises.writeFile(newFilePath, 'Hello Atomic World'); await changeFuture; }); @@ -359,9 +364,9 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int const deleteFuture3: Promise = awaitEvent(watcher, newFilePath3, FileChangeType.DELETED); await Promise.all([ - await Promises.unlink(newFilePath1), - await Promises.unlink(newFilePath2), - await Promises.unlink(newFilePath3) + await fs.promises.unlink(newFilePath1), + await fs.promises.unlink(newFilePath2), + await fs.promises.unlink(newFilePath3) ]); await Promise.all([deleteFuture1, deleteFuture2, deleteFuture3]); @@ -440,7 +445,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (folder watch)', async function () { const link = join(testDir, 'deep-linked'); const linkTarget = join(testDir, 'deep'); - await Promises.symlink(linkTarget, link); + await fs.promises.symlink(linkTarget, link); await watcher.watch([{ path: link, excludes: [], recursive: false }]); @@ -467,14 +472,14 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // Delete file changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, correlationId, expectedCount); - await Promises.unlink(await Promises.realpath(filePath)); // support symlinks + await fs.promises.unlink(await Promises.realpath(filePath)); // support symlinks await changeFuture; } (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (file watch)', async function () { const link = join(testDir, 'lorem.txt-linked'); const linkTarget = join(testDir, 'lorem.txt'); - await Promises.symlink(linkTarget, link); + await fs.promises.symlink(linkTarget, link); await watcher.watch([{ path: link, excludes: [], recursive: false }]); @@ -579,7 +584,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int const onDidWatchFail = Event.toPromise(watcher.onWatchFail); const changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1); - Promises.unlink(filePath); + fs.promises.unlink(filePath); await onDidWatchFail; await changeFuture; assert.strictEqual(instance.failed, true); @@ -634,7 +639,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int let changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, 1); let onDidWatch = Event.toPromise(watcher.onDidWatch); - await Promises.mkdir(folderPath); + await fs.promises.mkdir(folderPath); await changeFuture; await onDidWatch; @@ -645,12 +650,12 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int if (!isMacintosh) { // macOS does not report DELETE events for folders onDidWatchFail = Event.toPromise(watcher.onWatchFail); - await Promises.rmdir(folderPath); + await fs.promises.rmdir(folderPath); await onDidWatchFail; changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, 1); onDidWatch = Event.toPromise(watcher.onDidWatch); - await Promises.mkdir(folderPath); + await fs.promises.mkdir(folderPath); await changeFuture; await onDidWatch; @@ -673,7 +678,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int const changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, 1); const onDidWatch = Event.toPromise(watcher.onDidWatch); - await Promises.mkdir(folderPath); + await fs.promises.mkdir(folderPath); await changeFuture; await onDidWatch; @@ -773,7 +778,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // Delete file changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1); - await Promises.unlink(filePath); + await fs.promises.unlink(filePath); await changeFuture; }); @@ -789,7 +794,7 @@ import { TestParcelWatcher } from 'vs/platform/files/test/node/parcelWatcher.int // Delete file changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1); - await Promises.unlink(filePath); + await fs.promises.unlink(filePath); await changeFuture; }); }); diff --git a/patched-vscode/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts b/patched-vscode/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts index db85e28f..4370d82b 100644 --- a/patched-vscode/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts +++ b/patched-vscode/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { realpathSync } from 'fs'; +import assert from 'assert'; +import { realpathSync, promises } from 'fs'; import { tmpdir } from 'os'; import { timeout } from 'vs/base/common/async'; import { dirname, join } from 'vs/base/common/path'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { Promises, RimRafMode } from 'vs/base/node/pfs'; -import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { FileChangeFilter, FileChangeType, IFileChange } from 'vs/platform/files/common/files'; import { ParcelWatcher } from 'vs/platform/files/node/watcher/parcel/parcelWatcher'; import { IRecursiveWatchRequest } from 'vs/platform/files/common/watcher'; @@ -42,6 +42,10 @@ export class TestParcelWatcher extends ParcelWatcher { return this.removeDuplicateRequests(requests, false /* validate paths skipped for tests */).map(request => request.path); } + protected override getUpdateWatchersDelay(): number { + return 0; + } + protected override async doWatch(requests: IRecursiveWatchRequest[]): Promise { await super.doWatch(requests); await this.whenReady(); @@ -61,7 +65,7 @@ export class TestParcelWatcher extends ParcelWatcher { // mocha but generally). as such they will run only on demand // whenever we update the watcher library. -((process.env['BUILD_SOURCEVERSION'] || process.env['CI']) ? suite.skip : flakySuite)('File Watcher (parcel)', () => { +suite.skip('File Watcher (parcel)', () => { let testDir: string; let watcher: TestParcelWatcher; @@ -216,7 +220,7 @@ export class TestParcelWatcher extends ParcelWatcher { assert.strictEqual(instance.include(newFolderPath), true); assert.strictEqual(instance.exclude(newFolderPath), false); changeFuture = awaitEvent(watcher, newFolderPath, FileChangeType.ADDED); - await Promises.mkdir(newFolderPath); + await promises.mkdir(newFolderPath); await changeFuture; assert.strictEqual(subscriptions1.get(newFolderPath), FileChangeType.ADDED); assert.strictEqual(subscriptions2.has(newFolderPath), false /* subscription was disposed before the event */); @@ -286,7 +290,7 @@ export class TestParcelWatcher extends ParcelWatcher { // Copy file const copiedFilepath = join(testDir, 'deep', 'copiedFile.txt'); changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.ADDED); - await Promises.copyFile(movedFilepath, copiedFilepath); + await promises.copyFile(movedFilepath, copiedFilepath); await changeFuture; // Copy folder @@ -308,30 +312,30 @@ export class TestParcelWatcher extends ParcelWatcher { // Read file does not emit event changeFuture = awaitEvent(watcher, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-read-file'); - await Promises.readFile(anotherNewFilePath); + await promises.readFile(anotherNewFilePath); await Promise.race([timeout(100), changeFuture]); // Stat file does not emit event changeFuture = awaitEvent(watcher, anotherNewFilePath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); - await Promises.stat(anotherNewFilePath); + await promises.stat(anotherNewFilePath); await Promise.race([timeout(100), changeFuture]); // Stat folder does not emit event changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.UPDATED, 'unexpected-event-from-stat'); - await Promises.stat(copiedFolderpath); + await promises.stat(copiedFolderpath); await Promise.race([timeout(100), changeFuture]); // Delete file changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.DELETED); disposables.add(instance.subscribe(copiedFilepath, change => subscriptions1.set(change.resource.fsPath, change.type))); - await Promises.unlink(copiedFilepath); + await promises.unlink(copiedFilepath); await changeFuture; assert.strictEqual(subscriptions1.get(copiedFilepath), FileChangeType.DELETED); // Delete folder changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.DELETED); disposables.add(instance.subscribe(copiedFolderpath, change => subscriptions1.set(change.resource.fsPath, change.type))); - await Promises.rmdir(copiedFolderpath); + await promises.rmdir(copiedFolderpath); await changeFuture; assert.strictEqual(subscriptions1.get(copiedFolderpath), FileChangeType.DELETED); @@ -344,7 +348,7 @@ export class TestParcelWatcher extends ParcelWatcher { // Delete + Recreate file const newFilePath = join(testDir, 'deep', 'conway.js'); const changeFuture = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED); - await Promises.unlink(newFilePath); + await promises.unlink(newFilePath); Promises.writeFile(newFilePath, 'Hello Atomic World'); await changeFuture; }); @@ -369,13 +373,13 @@ export class TestParcelWatcher extends ParcelWatcher { // Delete file changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, undefined, correlationId, expectedCount); - await Promises.unlink(filePath); + await promises.unlink(filePath); await changeFuture; } test('multiple events', async function () { await watcher.watch([{ path: testDir, excludes: [], recursive: true }]); - await Promises.mkdir(join(testDir, 'deep-multiple')); + await promises.mkdir(join(testDir, 'deep-multiple')); // multiple add @@ -445,12 +449,12 @@ export class TestParcelWatcher extends ParcelWatcher { const deleteFuture6 = awaitEvent(watcher, newFilePath6, FileChangeType.DELETED); await Promise.all([ - await Promises.unlink(newFilePath1), - await Promises.unlink(newFilePath2), - await Promises.unlink(newFilePath3), - await Promises.unlink(newFilePath4), - await Promises.unlink(newFilePath5), - await Promises.unlink(newFilePath6) + await promises.unlink(newFilePath1), + await promises.unlink(newFilePath2), + await promises.unlink(newFilePath3), + await promises.unlink(newFilePath4), + await promises.unlink(newFilePath5), + await promises.unlink(newFilePath6) ]); await Promise.all([deleteFuture1, deleteFuture2, deleteFuture3, deleteFuture4, deleteFuture5, deleteFuture6]); @@ -559,7 +563,7 @@ export class TestParcelWatcher extends ParcelWatcher { (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (root)', async function () { const link = join(testDir, 'deep-linked'); const linkTarget = join(testDir, 'deep'); - await Promises.symlink(linkTarget, link); + await promises.symlink(linkTarget, link); await watcher.watch([{ path: link, excludes: [], recursive: true }]); @@ -569,7 +573,7 @@ export class TestParcelWatcher extends ParcelWatcher { (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (via extra watch)', async function () { const link = join(testDir, 'deep-linked'); const linkTarget = join(testDir, 'deep'); - await Promises.symlink(linkTarget, link); + await promises.symlink(linkTarget, link); await watcher.watch([{ path: testDir, excludes: [], recursive: true }, { path: link, excludes: [], recursive: true }]); @@ -613,7 +617,7 @@ export class TestParcelWatcher extends ParcelWatcher { // Restore watched path await timeout(1500); // node.js watcher used for monitoring folder restore is async - await Promises.mkdir(watchedPath); + await promises.mkdir(watchedPath); await timeout(1500); // restart is delayed await watcher.whenReady(); @@ -772,7 +776,7 @@ export class TestParcelWatcher extends ParcelWatcher { let changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, undefined, 1); let onDidWatch = Event.toPromise(watcher.onDidWatch); - await Promises.mkdir(folderPath); + await promises.mkdir(folderPath); await changeFuture; await onDidWatch; @@ -787,7 +791,7 @@ export class TestParcelWatcher extends ParcelWatcher { changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, undefined, 1); onDidWatch = Event.toPromise(watcher.onDidWatch); - await Promises.mkdir(folderPath); + await promises.mkdir(folderPath); await changeFuture; await onDidWatch; @@ -821,7 +825,7 @@ export class TestParcelWatcher extends ParcelWatcher { const changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED, undefined, 1); const onDidWatch = Event.toPromise(watcher.onDidWatch); - await Promises.mkdir(folderPath); + await promises.mkdir(folderPath); await changeFuture; await onDidWatch; @@ -860,7 +864,7 @@ export class TestParcelWatcher extends ParcelWatcher { // Delete file changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, undefined, 1); - await Promises.unlink(filePath); + await promises.unlink(filePath); await changeFuture; }); }); diff --git a/patched-vscode/src/vs/platform/hover/test/browser/nullHoverService.ts b/patched-vscode/src/vs/platform/hover/test/browser/nullHoverService.ts index 6b7b7283..44c73f8a 100644 --- a/patched-vscode/src/vs/platform/hover/test/browser/nullHoverService.ts +++ b/patched-vscode/src/vs/platform/hover/test/browser/nullHoverService.ts @@ -10,7 +10,7 @@ export const NullHoverService: IHoverService = { _serviceBrand: undefined, hideHover: () => undefined, showHover: () => undefined, - setupUpdatableHover: () => Disposable.None as any, + setupManagedHover: () => Disposable.None as any, showAndFocusLastHover: () => undefined, - triggerUpdatableHover: () => undefined + showManagedHover: () => undefined }; diff --git a/patched-vscode/src/vs/platform/instantiation/common/instantiationService.ts b/patched-vscode/src/vs/platform/instantiation/common/instantiationService.ts index 4b313ed3..a61ef743 100644 --- a/patched-vscode/src/vs/platform/instantiation/common/instantiationService.ts +++ b/patched-vscode/src/vs/platform/instantiation/common/instantiationService.ts @@ -216,8 +216,15 @@ export class InstantiationService implements IInstantiationService { let cycleCount = 0; const stack = [{ id, desc, _trace }]; + const seen = new Set(); while (stack.length) { const item = stack.pop()!; + + if (seen.has(String(item.id))) { + continue; + } + seen.add(String(item.id)); + graph.lookupOrInsertNode(item); // a weak but working heuristic for cycle checks diff --git a/patched-vscode/src/vs/platform/instantiation/test/common/graph.test.ts b/patched-vscode/src/vs/platform/instantiation/test/common/graph.test.ts index c5abc22c..99e8e0e7 100644 --- a/patched-vscode/src/vs/platform/instantiation/test/common/graph.test.ts +++ b/patched-vscode/src/vs/platform/instantiation/test/common/graph.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Graph } from 'vs/platform/instantiation/common/graph'; diff --git a/patched-vscode/src/vs/platform/instantiation/test/common/instantiationService.test.ts b/patched-vscode/src/vs/platform/instantiation/test/common/instantiationService.test.ts index fd5bb77d..70718ba7 100644 --- a/patched-vscode/src/vs/platform/instantiation/test/common/instantiationService.test.ts +++ b/patched-vscode/src/vs/platform/instantiation/test/common/instantiationService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/platform/issue/common/issue.ts b/patched-vscode/src/vs/platform/issue/common/issue.ts index 81f3a808..26f8e800 100644 --- a/patched-vscode/src/vs/platform/issue/common/issue.ts +++ b/patched-vscode/src/vs/platform/issue/common/issue.ts @@ -19,7 +19,7 @@ export interface WindowData { zoomLevel: number; } -export const enum IssueType { +export const enum OldIssueType { Bug, PerformanceIssue, FeatureRequest @@ -31,7 +31,7 @@ export enum IssueSource { Marketplace = 'marketplace' } -export interface IssueReporterStyles extends WindowStyles { +export interface OldIssueReporterStyles extends WindowStyles { textLinkColor?: string; textLinkActiveForeground?: string; inputBackground?: string; @@ -49,7 +49,7 @@ export interface IssueReporterStyles extends WindowStyles { sliderActiveColor?: string; } -export interface IssueReporterExtensionData { +export interface OldIssueReporterExtensionData { name: string; publisher: string | undefined; version: string; @@ -65,10 +65,10 @@ export interface IssueReporterExtensionData { uri?: UriComponents; } -export interface IssueReporterData extends WindowData { - styles: IssueReporterStyles; - enabledExtensions: IssueReporterExtensionData[]; - issueType?: IssueType; +export interface OldIssueReporterData extends WindowData { + styles: OldIssueReporterStyles; + enabledExtensions: OldIssueReporterExtensionData[]; + issueType?: OldIssueType; issueSource?: IssueSource; extensionId?: string; experiments?: string; @@ -109,9 +109,9 @@ export interface ProcessExplorerData extends WindowData { applicationName: string; } -export interface IssueReporterWindowConfiguration extends ISandboxConfiguration { +export interface OldIssueReporterWindowConfiguration extends ISandboxConfiguration { disableExtensions: boolean; - data: IssueReporterData; + data: OldIssueReporterData; os: { type: string; arch: string; @@ -127,18 +127,24 @@ export const IIssueMainService = createDecorator('issueServic export interface IIssueMainService { readonly _serviceBrand: undefined; - stopTracing(): Promise; - openReporter(data: IssueReporterData): Promise; - openProcessExplorer(data: ProcessExplorerData): Promise; - getSystemStatus(): Promise; - // Used by the issue reporter - - $getSystemInfo(): Promise; - $getPerformanceInfo(): Promise; + openReporter(data: OldIssueReporterData): Promise; $reloadWithExtensionsDisabled(): Promise; $showConfirmCloseDialog(): Promise; $showClipboardDialog(): Promise; - $sendReporterMenu(extensionId: string, extensionName: string): Promise; + $sendReporterMenu(extensionId: string, extensionName: string): Promise; $closeReporter(): Promise; } + +export const IProcessMainService = createDecorator('processService'); + +export interface IProcessMainService { + readonly _serviceBrand: undefined; + getSystemStatus(): Promise; + stopTracing(): Promise; + openProcessExplorer(data: ProcessExplorerData): Promise; + + // Used by the process explorer + $getSystemInfo(): Promise; + $getPerformanceInfo(): Promise; +} diff --git a/patched-vscode/src/vs/platform/issue/electron-main/issueMainService.ts b/patched-vscode/src/vs/platform/issue/electron-main/issueMainService.ts index 62e2d9ff..cb73c220 100644 --- a/patched-vscode/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/patched-vscode/src/vs/platform/issue/electron-main/issueMainService.ts @@ -3,34 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, BrowserWindowConstructorOptions, contentTracing, Display, IpcMainEvent, screen } from 'electron'; +import { BrowserWindow, BrowserWindowConstructorOptions, Display, screen } from 'electron'; import { arch, release, type } from 'os'; import { raceTimeout } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { randomPath } from 'vs/base/common/extpath'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; -import { listProcesses } from 'vs/base/node/ps'; import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; -import { localize } from 'vs/nls'; -import { IDiagnosticsService, isRemoteDiagnosticError, PerformanceInfo, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { IDiagnosticsMainService } from 'vs/platform/diagnostics/electron-main/diagnosticsMainService'; +import { getNLSLanguage, getNLSMessages, localize } from 'vs/nls'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { IIssueMainService, IssueReporterData, IssueReporterWindowConfiguration, ProcessExplorerData, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue'; +import { IIssueMainService, OldIssueReporterData, OldIssueReporterWindowConfiguration } from 'vs/platform/issue/common/issue'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; import product from 'vs/platform/product/common/product'; -import { IProductService } from 'vs/platform/product/common/productService'; import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; -import { IStateService } from 'vs/platform/state/node/state'; -import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; import { ICodeWindow, IWindowState } from 'vs/platform/window/electron-main/window'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; - -const processExplorerWindowState = 'issue.processExplorerWindowState'; +import { isESM } from 'vs/base/common/amd'; +import { ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; interface IBrowserWindowOptions { backgroundColor: string | undefined; @@ -50,104 +43,26 @@ export class IssueMainService implements IIssueMainService { private issueReporterWindow: BrowserWindow | null = null; private issueReporterParentWindow: BrowserWindow | null = null; - private processExplorerWindow: BrowserWindow | null = null; - private processExplorerParentWindow: BrowserWindow | null = null; - constructor( private userEnv: IProcessEnvironment, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILogService private readonly logService: ILogService, - @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, - @IDiagnosticsMainService private readonly diagnosticsMainService: IDiagnosticsMainService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService, - @IProductService private readonly productService: IProductService, - @IStateService private readonly stateService: IStateService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - ) { - this.registerListeners(); - } - - //#region Register Listeners - - private registerListeners(): void { - validatedIpcMain.on('vscode:listProcesses', async event => { - const processes = []; - - try { - processes.push({ name: localize('local', "Local"), rootProcess: await listProcesses(process.pid) }); - - const remoteDiagnostics = await this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: true }); - remoteDiagnostics.forEach(data => { - if (isRemoteDiagnosticError(data)) { - processes.push({ - name: data.hostName, - rootProcess: data - }); - } else { - if (data.processes) { - processes.push({ - name: data.hostName, - rootProcess: data.processes - }); - } - } - }); - } catch (e) { - this.logService.error(`Listing processes failed: ${e}`); - } - - this.safeSend(event, 'vscode:listProcessesResponse', processes); - }); - - validatedIpcMain.on('vscode:workbenchCommand', (_: unknown, commandInfo: { id: any; from: any; args: any }) => { - const { id, from, args } = commandInfo; - - let parentWindow: BrowserWindow | null; - switch (from) { - case 'processExplorer': - parentWindow = this.processExplorerParentWindow; - break; - default: - // The issue reporter does not use this anymore. - throw new Error(`Unexpected command source: ${from}`); - } - - parentWindow?.webContents.send('vscode:runAction', { id, from, args }); - }); - - validatedIpcMain.on('vscode:closeProcessExplorer', event => { - this.processExplorerWindow?.close(); - }); - - validatedIpcMain.on('vscode:pidToNameRequest', async event => { - const mainProcessInfo = await this.diagnosticsMainService.getMainDiagnostics(); - - const pidToNames: [number, string][] = []; - for (const window of mainProcessInfo.windows) { - pidToNames.push([window.pid, `window [${window.id}] (${window.title})`]); - } - - for (const { pid, name } of UtilityProcess.getAll()) { - pidToNames.push([pid, name]); - } - - this.safeSend(event, 'vscode:pidToNameResponse', pidToNames); - }); - } - - //#endregion + @ICSSDevelopmentService private readonly cssDevelopmentService: ICSSDevelopmentService, + ) { } //#region Used by renderer - async openReporter(data: IssueReporterData): Promise { + async openReporter(data: OldIssueReporterData): Promise { if (!this.issueReporterWindow) { this.issueReporterParentWindow = BrowserWindow.getFocusedWindow(); if (this.issueReporterParentWindow) { const issueReporterDisposables = new DisposableStore(); - const issueReporterWindowConfigUrl = issueReporterDisposables.add(this.protocolMainService.createIPCObjectUrl()); + const issueReporterWindowConfigUrl = issueReporterDisposables.add(this.protocolMainService.createIPCObjectUrl()); const position = this.getWindowPosition(this.issueReporterParentWindow, 700, 800); this.issueReporterWindow = this.createBrowserWindow(position, issueReporterWindowConfigUrl, { @@ -169,11 +84,16 @@ export class IssueMainService implements IIssueMainService { arch: arch(), release: release(), }, - product + product, + nls: { + messages: getNLSMessages(), + language: getNLSLanguage() + }, + cssModules: this.cssDevelopmentService.isEnabled ? await this.cssDevelopmentService.getCssModules() : undefined }); this.issueReporterWindow.loadURL( - FileAccess.asBrowserUri(`vs/workbench/contrib/issue/electron-sandbox/issueReporter${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true) + FileAccess.asBrowserUri(`vs/workbench/contrib/issue/electron-sandbox/issueReporter${this.environmentMainService.isBuilt ? '' : '-dev'}.${isESM ? 'esm.' : ''}html`).toString(true) ); this.issueReporterWindow.on('close', () => { @@ -196,125 +116,9 @@ export class IssueMainService implements IIssueMainService { } } - async openProcessExplorer(data: ProcessExplorerData): Promise { - if (!this.processExplorerWindow) { - this.processExplorerParentWindow = BrowserWindow.getFocusedWindow(); - if (this.processExplorerParentWindow) { - const processExplorerDisposables = new DisposableStore(); - - const processExplorerWindowConfigUrl = processExplorerDisposables.add(this.protocolMainService.createIPCObjectUrl()); - - const savedPosition = this.stateService.getItem(processExplorerWindowState, undefined); - const position = isStrictWindowState(savedPosition) ? savedPosition : this.getWindowPosition(this.processExplorerParentWindow, 800, 500); - - this.processExplorerWindow = this.createBrowserWindow(position, processExplorerWindowConfigUrl, { - backgroundColor: data.styles.backgroundColor, - title: localize('processExplorer', "Process Explorer"), - zoomLevel: data.zoomLevel, - alwaysOnTop: true - }, 'process-explorer'); - - // Store into config object URL - processExplorerWindowConfigUrl.update({ - appRoot: this.environmentMainService.appRoot, - windowId: this.processExplorerWindow.id, - userEnv: this.userEnv, - data, - product - }); - - this.processExplorerWindow.loadURL( - FileAccess.asBrowserUri(`vs/code/electron-sandbox/processExplorer/processExplorer${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true) - ); - - this.processExplorerWindow.on('close', () => { - this.processExplorerWindow = null; - processExplorerDisposables.dispose(); - }); - - this.processExplorerParentWindow.on('close', () => { - if (this.processExplorerWindow) { - this.processExplorerWindow.close(); - this.processExplorerWindow = null; - - processExplorerDisposables.dispose(); - } - }); - - const storeState = () => { - if (!this.processExplorerWindow) { - return; - } - const size = this.processExplorerWindow.getSize(); - const position = this.processExplorerWindow.getPosition(); - if (!size || !position) { - return; - } - const state: IWindowState = { - width: size[0], - height: size[1], - x: position[0], - y: position[1] - }; - this.stateService.setItem(processExplorerWindowState, state); - }; - - this.processExplorerWindow.on('moved', storeState); - this.processExplorerWindow.on('resized', storeState); - } - } - - if (this.processExplorerWindow) { - this.focusWindow(this.processExplorerWindow); - } - } - - async stopTracing(): Promise { - if (!this.environmentMainService.args.trace) { - return; // requires tracing to be on - } - - const path = await contentTracing.stopRecording(`${randomPath(this.environmentMainService.userHome.fsPath, this.productService.applicationName)}.trace.txt`); - - // Inform user to report an issue - await this.dialogMainService.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created the trace file"), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize({ key: 'trace.ok', comment: ['&& denotes a mnemonic'] }, "&&OK")], - }, BrowserWindow.getFocusedWindow() ?? undefined); - - // Show item in explorer - this.nativeHostMainService.showItemInFolder(undefined, path); - } - - async getSystemStatus(): Promise { - const [info, remoteData] = await Promise.all([this.diagnosticsMainService.getMainDiagnostics(), this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]); - - return this.diagnosticsService.getDiagnostics(info, remoteData); - } - //#endregion //#region used by issue reporter window - - async $getSystemInfo(): Promise { - const [info, remoteData] = await Promise.all([this.diagnosticsMainService.getMainDiagnostics(), this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]); - const msg = await this.diagnosticsService.getSystemInfo(info, remoteData); - return msg; - } - - async $getPerformanceInfo(): Promise { - try { - const [info, remoteData] = await Promise.all([this.diagnosticsMainService.getMainDiagnostics(), this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]); - return await this.diagnosticsService.getPerformanceInfo(info, remoteData); - } catch (error) { - this.logService.warn('issueService#getPerformanceInfo ', error.message); - - throw error; - } - } - async $reloadWithExtensionsDisabled(): Promise { if (this.issueReporterParentWindow) { try { @@ -373,26 +177,22 @@ export class IssueMainService implements IIssueMainService { return window; } - async $sendReporterMenu(extensionId: string, extensionName: string): Promise { + async $sendReporterMenu(extensionId: string, extensionName: string): Promise { const window = this.issueReporterWindowCheck(); const replyChannel = `vscode:triggerReporterMenu`; const cts = new CancellationTokenSource(); window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); - const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once(`vscode:triggerReporterMenuResponse:${extensionId}`, (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { + const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once(`vscode:triggerReporterMenuResponse:${extensionId}`, (_: unknown, data: OldIssueReporterData | undefined) => resolve(data))), 5000, () => { this.logService.error(`Error: Extension ${extensionId} timed out waiting for menu response`); cts.cancel(); }); - return result as IssueReporterData | undefined; + return result as OldIssueReporterData | undefined; } async $closeReporter(): Promise { this.issueReporterWindow?.close(); } - async closeProcessExplorer(): Promise { - this.processExplorerWindow?.close(); - } - //#endregion private focusWindow(window: BrowserWindow): void { @@ -403,12 +203,6 @@ export class IssueMainService implements IIssueMainService { window.focus(); } - private safeSend(event: IpcMainEvent, channel: string, ...args: unknown[]): void { - if (!event.sender.isDestroyed()) { - event.sender.send(channel, ...args); - } - } - private createBrowserWindow(position: IWindowState, ipcObjectUrl: IIPCObjectUrl, options: IBrowserWindowOptions, windowKind: string): BrowserWindow { const window = new BrowserWindow({ fullscreen: false, @@ -509,15 +303,3 @@ export class IssueMainService implements IIssueMainService { return state; } } - -function isStrictWindowState(obj: unknown): obj is IStrictWindowState { - if (typeof obj !== 'object' || obj === null) { - return false; - } - return ( - 'x' in obj && - 'y' in obj && - 'width' in obj && - 'height' in obj - ); -} diff --git a/patched-vscode/src/vs/platform/issue/electron-main/processMainService.ts b/patched-vscode/src/vs/platform/issue/electron-main/processMainService.ts new file mode 100644 index 00000000..52e72f35 --- /dev/null +++ b/patched-vscode/src/vs/platform/issue/electron-main/processMainService.ts @@ -0,0 +1,383 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BrowserWindow, BrowserWindowConstructorOptions, contentTracing, Display, IpcMainEvent, screen } from 'electron'; +import { randomPath } from 'vs/base/common/extpath'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { FileAccess } from 'vs/base/common/network'; +import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; +import { listProcesses } from 'vs/base/node/ps'; +import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; +import { getNLSLanguage, getNLSMessages, localize } from 'vs/nls'; +import { IDiagnosticsService, isRemoteDiagnosticError, PerformanceInfo, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; +import { IDiagnosticsMainService } from 'vs/platform/diagnostics/electron-main/diagnosticsMainService'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; +import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; +import { IProcessMainService, ProcessExplorerData, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; +import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; +import { IStateService } from 'vs/platform/state/node/state'; +import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess'; +import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; +import { IWindowState } from 'vs/platform/window/electron-main/window'; +import { isESM } from 'vs/base/common/amd'; + +const processExplorerWindowState = 'issue.processExplorerWindowState'; + +interface IBrowserWindowOptions { + backgroundColor: string | undefined; + title: string; + zoomLevel: number; + alwaysOnTop: boolean; +} + +type IStrictWindowState = Required>; + +export class ProcessMainService implements IProcessMainService { + + declare readonly _serviceBrand: undefined; + + private static readonly DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; + + private processExplorerWindow: BrowserWindow | null = null; + private processExplorerParentWindow: BrowserWindow | null = null; + + constructor( + private userEnv: IProcessEnvironment, + @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, + @ILogService private readonly logService: ILogService, + @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, + @IDiagnosticsMainService private readonly diagnosticsMainService: IDiagnosticsMainService, + @IDialogMainService private readonly dialogMainService: IDialogMainService, + @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, + @IProtocolMainService private readonly protocolMainService: IProtocolMainService, + @IProductService private readonly productService: IProductService, + @IStateService private readonly stateService: IStateService, + @ICSSDevelopmentService private readonly cssDevelopmentService: ICSSDevelopmentService + ) { + this.registerListeners(); + } + + //#region Register Listeners + + private registerListeners(): void { + validatedIpcMain.on('vscode:listProcesses', async event => { + const processes = []; + + try { + processes.push({ name: localize('local', "Local"), rootProcess: await listProcesses(process.pid) }); + + const remoteDiagnostics = await this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: true }); + remoteDiagnostics.forEach(data => { + if (isRemoteDiagnosticError(data)) { + processes.push({ + name: data.hostName, + rootProcess: data + }); + } else { + if (data.processes) { + processes.push({ + name: data.hostName, + rootProcess: data.processes + }); + } + } + }); + } catch (e) { + this.logService.error(`Listing processes failed: ${e}`); + } + + this.safeSend(event, 'vscode:listProcessesResponse', processes); + }); + + validatedIpcMain.on('vscode:workbenchCommand', (_: unknown, commandInfo: { id: any; from: any; args: any }) => { + const { id, from, args } = commandInfo; + + let parentWindow: BrowserWindow | null; + switch (from) { + case 'processExplorer': + parentWindow = this.processExplorerParentWindow; + break; + default: + // The issue reporter does not use this anymore. + throw new Error(`Unexpected command source: ${from}`); + } + + parentWindow?.webContents.send('vscode:runAction', { id, from, args }); + }); + + validatedIpcMain.on('vscode:closeProcessExplorer', event => { + this.processExplorerWindow?.close(); + }); + + validatedIpcMain.on('vscode:pidToNameRequest', async event => { + const mainProcessInfo = await this.diagnosticsMainService.getMainDiagnostics(); + + const pidToNames: [number, string][] = []; + for (const window of mainProcessInfo.windows) { + pidToNames.push([window.pid, `window [${window.id}] (${window.title})`]); + } + + for (const { pid, name } of UtilityProcess.getAll()) { + pidToNames.push([pid, name]); + } + + this.safeSend(event, 'vscode:pidToNameResponse', pidToNames); + }); + } + + async openProcessExplorer(data: ProcessExplorerData): Promise { + if (!this.processExplorerWindow) { + this.processExplorerParentWindow = BrowserWindow.getFocusedWindow(); + if (this.processExplorerParentWindow) { + const processExplorerDisposables = new DisposableStore(); + + const processExplorerWindowConfigUrl = processExplorerDisposables.add(this.protocolMainService.createIPCObjectUrl()); + + const savedPosition = this.stateService.getItem(processExplorerWindowState, undefined); + const position = isStrictWindowState(savedPosition) ? savedPosition : this.getWindowPosition(this.processExplorerParentWindow, 800, 500); + + this.processExplorerWindow = this.createBrowserWindow(position, processExplorerWindowConfigUrl, { + backgroundColor: data.styles.backgroundColor, + title: localize('processExplorer', "Process Explorer"), + zoomLevel: data.zoomLevel, + alwaysOnTop: true + }, 'process-explorer'); + + // Store into config object URL + processExplorerWindowConfigUrl.update({ + appRoot: this.environmentMainService.appRoot, + windowId: this.processExplorerWindow.id, + userEnv: this.userEnv, + data, + product, + nls: { + messages: getNLSMessages(), + language: getNLSLanguage() + }, + cssModules: this.cssDevelopmentService.isEnabled ? await this.cssDevelopmentService.getCssModules() : undefined + }); + + this.processExplorerWindow.loadURL( + FileAccess.asBrowserUri(`vs/code/electron-sandbox/processExplorer/processExplorer${this.environmentMainService.isBuilt ? '' : '-dev'}.${isESM ? 'esm.' : ''}html`).toString(true) + ); + + this.processExplorerWindow.on('close', () => { + this.processExplorerWindow = null; + processExplorerDisposables.dispose(); + }); + + this.processExplorerParentWindow.on('close', () => { + if (this.processExplorerWindow) { + this.processExplorerWindow.close(); + this.processExplorerWindow = null; + + processExplorerDisposables.dispose(); + } + }); + + const storeState = () => { + if (!this.processExplorerWindow) { + return; + } + const size = this.processExplorerWindow.getSize(); + const position = this.processExplorerWindow.getPosition(); + if (!size || !position) { + return; + } + const state: IWindowState = { + width: size[0], + height: size[1], + x: position[0], + y: position[1] + }; + this.stateService.setItem(processExplorerWindowState, state); + }; + + this.processExplorerWindow.on('moved', storeState); + this.processExplorerWindow.on('resized', storeState); + } + } + + if (this.processExplorerWindow) { + this.focusWindow(this.processExplorerWindow); + } + } + + private focusWindow(window: BrowserWindow): void { + if (window.isMinimized()) { + window.restore(); + } + + window.focus(); + } + + private getWindowPosition(parentWindow: BrowserWindow, defaultWidth: number, defaultHeight: number): IStrictWindowState { + + // We want the new window to open on the same display that the parent is in + let displayToUse: Display | undefined; + const displays = screen.getAllDisplays(); + + // Single Display + if (displays.length === 1) { + displayToUse = displays[0]; + } + + // Multi Display + else { + + // on mac there is 1 menu per window so we need to use the monitor where the cursor currently is + if (isMacintosh) { + const cursorPoint = screen.getCursorScreenPoint(); + displayToUse = screen.getDisplayNearestPoint(cursorPoint); + } + + // if we have a last active window, use that display for the new window + if (!displayToUse && parentWindow) { + displayToUse = screen.getDisplayMatching(parentWindow.getBounds()); + } + + // fallback to primary display or first display + if (!displayToUse) { + displayToUse = screen.getPrimaryDisplay() || displays[0]; + } + } + + const displayBounds = displayToUse.bounds; + + const state: IStrictWindowState = { + width: defaultWidth, + height: defaultHeight, + x: displayBounds.x + (displayBounds.width / 2) - (defaultWidth / 2), + y: displayBounds.y + (displayBounds.height / 2) - (defaultHeight / 2) + }; + + if (displayBounds.width > 0 && displayBounds.height > 0 /* Linux X11 sessions sometimes report wrong display bounds */) { + if (state.x < displayBounds.x) { + state.x = displayBounds.x; // prevent window from falling out of the screen to the left + } + + if (state.y < displayBounds.y) { + state.y = displayBounds.y; // prevent window from falling out of the screen to the top + } + + if (state.x > (displayBounds.x + displayBounds.width)) { + state.x = displayBounds.x; // prevent window from falling out of the screen to the right + } + + if (state.y > (displayBounds.y + displayBounds.height)) { + state.y = displayBounds.y; // prevent window from falling out of the screen to the bottom + } + + if (state.width > displayBounds.width) { + state.width = displayBounds.width; // prevent window from exceeding display bounds width + } + + if (state.height > displayBounds.height) { + state.height = displayBounds.height; // prevent window from exceeding display bounds height + } + } + + return state; + } + + async stopTracing(): Promise { + if (!this.environmentMainService.args.trace) { + return; // requires tracing to be on + } + + const path = await contentTracing.stopRecording(`${randomPath(this.environmentMainService.userHome.fsPath, this.productService.applicationName)}.trace.txt`); + + // Inform user to report an issue + await this.dialogMainService.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created the trace file"), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize({ key: 'trace.ok', comment: ['&& denotes a mnemonic'] }, "&&OK")], + }, BrowserWindow.getFocusedWindow() ?? undefined); + + // Show item in explorer + this.nativeHostMainService.showItemInFolder(undefined, path); + } + + async getSystemStatus(): Promise { + const [info, remoteData] = await Promise.all([this.diagnosticsMainService.getMainDiagnostics(), this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]); + return this.diagnosticsService.getDiagnostics(info, remoteData); + } + + async $getSystemInfo(): Promise { + const [info, remoteData] = await Promise.all([this.diagnosticsMainService.getMainDiagnostics(), this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]); + const msg = await this.diagnosticsService.getSystemInfo(info, remoteData); + return msg; + } + + async $getPerformanceInfo(): Promise { + try { + const [info, remoteData] = await Promise.all([this.diagnosticsMainService.getMainDiagnostics(), this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]); + return await this.diagnosticsService.getPerformanceInfo(info, remoteData); + } catch (error) { + this.logService.warn('issueService#getPerformanceInfo ', error.message); + + throw error; + } + } + + private createBrowserWindow(position: IWindowState, ipcObjectUrl: IIPCObjectUrl, options: IBrowserWindowOptions, windowKind: string): BrowserWindow { + const window = new BrowserWindow({ + fullscreen: false, + skipTaskbar: false, + resizable: true, + width: position.width, + height: position.height, + minWidth: 300, + minHeight: 200, + x: position.x, + y: position.y, + title: options.title, + backgroundColor: options.backgroundColor || ProcessMainService.DEFAULT_BACKGROUND_COLOR, + webPreferences: { + preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload.js').fsPath, + additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`], + v8CacheOptions: this.environmentMainService.useCodeCache ? 'bypassHeatCheck' : 'none', + enableWebSQL: false, + spellcheck: false, + zoomFactor: zoomLevelToZoomFactor(options.zoomLevel), + sandbox: true + }, + alwaysOnTop: options.alwaysOnTop, + experimentalDarkMode: true + } as BrowserWindowConstructorOptions & { experimentalDarkMode: boolean }); + + window.setMenuBarVisibility(false); + + return window; + } + + private safeSend(event: IpcMainEvent, channel: string, ...args: unknown[]): void { + if (!event.sender.isDestroyed()) { + event.sender.send(channel, ...args); + } + } + + async closeProcessExplorer(): Promise { + this.processExplorerWindow?.close(); + } +} + +function isStrictWindowState(obj: unknown): obj is IStrictWindowState { + if (typeof obj !== 'object' || obj === null) { + return false; + } + return ( + 'x' in obj && + 'y' in obj && + 'width' in obj && + 'height' in obj + ); +} diff --git a/patched-vscode/src/vs/platform/jsonschemas/common/jsonContributionRegistry.ts b/patched-vscode/src/vs/platform/jsonschemas/common/jsonContributionRegistry.ts index 08322db2..039aaa6c 100644 --- a/patched-vscode/src/vs/platform/jsonschemas/common/jsonContributionRegistry.ts +++ b/patched-vscode/src/vs/platform/jsonschemas/common/jsonContributionRegistry.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { getCompressedContent, IJSONSchema } from 'vs/base/common/jsonSchema'; import * as platform from 'vs/platform/registry/common/platform'; export const Extensions = { @@ -35,6 +35,18 @@ export interface IJSONContributionRegistry { * Get all schemas */ getSchemaContributions(): ISchemaContributions; + + /** + * Gets the (compressed) content of the schema with the given schema ID (if any) + * @param uri The id of the schema + */ + getSchemaContent(uri: string): string | undefined; + + /** + * Returns true if there's a schema that matches the given schema ID + * @param uri The id of the schema + */ + hasSchemaContent(uri: string): boolean; } @@ -74,6 +86,15 @@ class JSONContributionRegistry implements IJSONContributionRegistry { }; } + public getSchemaContent(uri: string): string | undefined { + const schema = this.schemasById[uri]; + return schema ? getCompressedContent(schema) : undefined; + } + + public hasSchemaContent(uri: string): boolean { + return !!this.schemasById[uri]; + } + } const jsonContributionRegistry = new JSONContributionRegistry(); diff --git a/patched-vscode/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/patched-vscode/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 2f79c811..88aa86a0 100644 --- a/patched-vscode/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/patched-vscode/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.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 * as assert from 'assert'; +import assert from 'assert'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { createSimpleKeybinding, ResolvedKeybinding, KeyCodeChord, Keybinding } from 'vs/base/common/keybindings'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts b/patched-vscode/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts index dfc5df1e..b88a747e 100644 --- a/patched-vscode/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts +++ b/patched-vscode/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { OperatingSystem } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/patched-vscode/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 54bd2a67..3fccf387 100644 --- a/patched-vscode/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/patched-vscode/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { decodeKeybinding, createSimpleKeybinding, KeyCodeChord } from 'vs/base/common/keybindings'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/platform/languagePacks/node/languagePacks.ts b/patched-vscode/src/vs/platform/languagePacks/node/languagePacks.ts index 3a7df771..698b9b56 100644 --- a/patched-vscode/src/vs/platform/languagePacks/node/languagePacks.ts +++ b/patched-vscode/src/vs/platform/languagePacks/node/languagePacks.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { createHash } from 'crypto'; import { equals } from 'vs/base/common/arrays'; import { Queue } from 'vs/base/common/async'; @@ -180,7 +181,7 @@ class LanguagePacksCache extends Disposable { private withLanguagePacks(fn: (languagePacks: { [language: string]: ILanguagePack }) => T | null = () => null): Promise { return this.languagePacksFileLimiter.queue(() => { let result: T | null = null; - return Promises.readFile(this.languagePacksFilePath, 'utf8') + return fs.promises.readFile(this.languagePacksFilePath, 'utf8') .then(undefined, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err)) .then<{ [language: string]: ILanguagePack }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } }) .then(languagePacks => { result = fn(languagePacks); return languagePacks; }) diff --git a/patched-vscode/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/patched-vscode/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 7dfc46d0..4013b2b5 100644 --- a/patched-vscode/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/patched-vscode/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, BrowserWindow, Event as ElectronEvent } from 'electron'; +import electron from 'electron'; import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; import { Barrier, Promises, timeout } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -294,7 +294,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe this.fireOnWillShutdown(ShutdownReason.QUIT); } }; - app.addListener('before-quit', beforeQuitListener); + electron.app.addListener('before-quit', beforeQuitListener); // window-all-closed: an event that only fires when the last window // was closed. We override this event to be in charge if app.quit() @@ -305,14 +305,14 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // Windows/Linux: we quit when all windows have closed // Mac: we only quit when quit was requested if (this._quitRequested || !isMacintosh) { - app.quit(); + electron.app.quit(); } }; - app.addListener('window-all-closed', windowAllClosedListener); + electron.app.addListener('window-all-closed', windowAllClosedListener); // will-quit: an event that is fired after all windows have been // closed, but before actually quitting. - app.once('will-quit', e => { + electron.app.once('will-quit', e => { this.trace('Lifecycle#app.on(will-quit) - begin'); // Prevent the quit until the shutdown promise was resolved @@ -332,12 +332,12 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // will-quit listener is only installed "once". Also // remove any listener we have that is no longer needed - app.removeListener('before-quit', beforeQuitListener); - app.removeListener('window-all-closed', windowAllClosedListener); + electron.app.removeListener('before-quit', beforeQuitListener); + electron.app.removeListener('window-all-closed', windowAllClosedListener); this.trace('Lifecycle#app.on(will-quit) - calling app.quit()'); - app.quit(); + electron.app.quit(); }); }); } @@ -428,7 +428,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // Window Before Closing: Main -> Renderer const win = assertIsDefined(window.win); - windowListeners.add(Event.fromNodeEventEmitter(win, 'close')(e => { + windowListeners.add(Event.fromNodeEventEmitter(win, 'close')(e => { // The window already acknowledged to be closed const windowId = window.id; @@ -458,7 +458,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe window.close(); }); })); - windowListeners.add(Event.fromNodeEventEmitter(win, 'closed')(() => { + windowListeners.add(Event.fromNodeEventEmitter(win, 'closed')(() => { this.trace(`Lifecycle#window.on('closed') - window ID ${window.id}`); // update window count @@ -480,7 +480,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe const win = assertIsDefined(auxWindow.win); const windowListeners = new DisposableStore(); - windowListeners.add(Event.fromNodeEventEmitter(win, 'close')(e => { + windowListeners.add(Event.fromNodeEventEmitter(win, 'close')(e => { this.trace(`Lifecycle#auxWindow.on('close') - window ID ${auxWindow.id}`); if (this._quitRequested) { @@ -499,7 +499,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe e.preventDefault(); } })); - windowListeners.add(Event.fromNodeEventEmitter(win, 'closed')(() => { + windowListeners.add(Event.fromNodeEventEmitter(win, 'closed')(() => { this.trace(`Lifecycle#auxWindow.on('closed') - window ID ${auxWindow.id}`); windowListeners.dispose(); @@ -652,7 +652,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // Calling app.quit() will trigger the close handlers of each opened window // and only if no window vetoed the shutdown, we will get the will-quit event this.trace('Lifecycle#quit() - calling app.quit()'); - app.quit(); + electron.app.quit(); }); return this.pendingQuitPromise; @@ -690,16 +690,16 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe const quitListener = () => { if (!this.relaunchHandler?.handleRelaunch(options)) { this.trace('Lifecycle#relaunch() - calling app.relaunch()'); - app.relaunch({ args }); + electron.app.relaunch({ args }); } }; - app.once('quit', quitListener); + electron.app.once('quit', quitListener); // `app.relaunch()` does not quit automatically, so we quit first, // check for vetoes and then relaunch from the `app.on('quit')` event const veto = await this.quit(true /* will restart */); if (veto) { - app.removeListener('quit', quitListener); + electron.app.removeListener('quit', quitListener); } } @@ -727,7 +727,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // to a participant within the window. this is not wanted when we // are asked to kill the application. (async () => { - for (const window of BrowserWindow.getAllWindows()) { + for (const window of electron.BrowserWindow.getAllWindows()) { if (window && !window.isDestroyed()) { let whenWindowClosed: Promise; if (window.webContents && !window.webContents.isDestroyed()) { @@ -744,6 +744,6 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe ]); // Now exit either after 1s or all windows destroyed - app.exit(code); + electron.app.exit(code); } } diff --git a/patched-vscode/src/vs/platform/list/browser/listService.ts b/patched-vscode/src/vs/platform/list/browser/listService.ts index 6857531c..976b9a85 100644 --- a/patched-vscode/src/vs/platform/list/browser/listService.ts +++ b/patched-vscode/src/vs/platform/list/browser/listService.ts @@ -1490,7 +1490,7 @@ configurationRegistry.registerConfiguration({ type: 'number', minimum: 1, default: 7, - markdownDescription: localize('sticky scroll maximum items', "Controls the number of sticky elements displayed in the tree when `#workbench.tree.enableStickyScroll#` is enabled."), + markdownDescription: localize('sticky scroll maximum items', "Controls the number of sticky elements displayed in the tree when {0} is enabled.", '`#workbench.tree.enableStickyScroll#`'), }, [typeNavigationModeSettingKey]: { type: 'string', diff --git a/patched-vscode/src/vs/platform/markers/common/markerService.ts b/patched-vscode/src/vs/platform/markers/common/markerService.ts index 692f6ba5..91120aab 100644 --- a/patched-vscode/src/vs/platform/markers/common/markerService.ts +++ b/patched-vscode/src/vs/platform/markers/common/markerService.ts @@ -18,7 +18,6 @@ export const unsupportedSchemas = new Set([ Schemas.walkThrough, Schemas.walkThroughSnippet, Schemas.vscodeChatCodeBlock, - Schemas.vscodeCopilotBackingChatCodeBlock, ]); class DoubleResourceMap { diff --git a/patched-vscode/src/vs/platform/markers/test/common/markerService.test.ts b/patched-vscode/src/vs/platform/markers/test/common/markerService.test.ts index d8ebccf7..1b9916a8 100644 --- a/patched-vscode/src/vs/platform/markers/test/common/markerService.test.ts +++ b/patched-vscode/src/vs/platform/markers/test/common/markerService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; diff --git a/patched-vscode/src/vs/platform/menubar/electron-main/menubar.ts b/patched-vscode/src/vs/platform/menubar/electron-main/menubar.ts index 7e96549a..d7449e94 100644 --- a/patched-vscode/src/vs/platform/menubar/electron-main/menubar.ts +++ b/patched-vscode/src/vs/platform/menubar/electron-main/menubar.ts @@ -25,6 +25,7 @@ import { IUpdateService, StateType } from 'vs/platform/update/common/update'; import { INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable, hasNativeTitlebar } from 'vs/platform/window/common/window'; import { IWindowsCountChangedEvent, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; +import { Disposable } from 'vs/base/common/lifecycle'; const telemetryFrom = 'menu'; @@ -42,7 +43,7 @@ interface IMenuItemWithKeybinding { userSettingsLabel?: string; } -export class Menubar { +export class Menubar extends Disposable { private static readonly lastKnownMenubarStorageKey = 'lastKnownMenubarData'; @@ -78,6 +79,8 @@ export class Menubar { @IProductService private readonly productService: IProductService, @IAuxiliaryWindowsMainService private readonly auxiliaryWindowsMainService: IAuxiliaryWindowsMainService ) { + super(); + this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); this.menuGC = new RunOnceScheduler(() => { this.oldMenus = []; }, 10000); @@ -169,12 +172,12 @@ export class Menubar { private registerListeners(): void { // Keep flag when app quits - this.lifecycleMainService.onWillShutdown(() => this.willShutdown = true); + this._register(this.lifecycleMainService.onWillShutdown(() => this.willShutdown = true)); // Listen to some events from window service to update menu - this.windowsMainService.onDidChangeWindowsCount(e => this.onDidChangeWindowsCount(e)); - this.nativeHostMainService.onDidBlurMainWindow(() => this.onDidChangeWindowFocus()); - this.nativeHostMainService.onDidFocusMainWindow(() => this.onDidChangeWindowFocus()); + this._register(this.windowsMainService.onDidChangeWindowsCount(e => this.onDidChangeWindowsCount(e))); + this._register(this.nativeHostMainService.onDidBlurMainWindow(() => this.onDidChangeWindowFocus())); + this._register(this.nativeHostMainService.onDidFocusMainWindow(() => this.onDidChangeWindowFocus())); } private get currentEnableMenuBarMnemonics(): boolean { diff --git a/patched-vscode/src/vs/platform/menubar/electron-main/menubarMainService.ts b/patched-vscode/src/vs/platform/menubar/electron-main/menubarMainService.ts index c680afa2..731070e4 100644 --- a/patched-vscode/src/vs/platform/menubar/electron-main/menubarMainService.ts +++ b/patched-vscode/src/vs/platform/menubar/electron-main/menubarMainService.ts @@ -8,6 +8,7 @@ import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle import { ILogService } from 'vs/platform/log/common/log'; import { ICommonMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; import { Menubar } from 'vs/platform/menubar/electron-main/menubar'; +import { Disposable } from 'vs/base/common/lifecycle'; export const IMenubarMainService = createDecorator('menubarMainService'); @@ -15,24 +16,24 @@ export interface IMenubarMainService extends ICommonMenubarService { readonly _serviceBrand: undefined; } -export class MenubarMainService implements IMenubarMainService { +export class MenubarMainService extends Disposable implements IMenubarMainService { declare readonly _serviceBrand: undefined; - private menubar: Promise; + private readonly menubar = this.installMenuBarAfterWindowOpen(); constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService ) { - this.menubar = this.installMenuBarAfterWindowOpen(); + super(); } private async installMenuBarAfterWindowOpen(): Promise { await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen); - return this.instantiationService.createInstance(Menubar); + return this._register(this.instantiationService.createInstance(Menubar)); } async updateMenubar(windowId: number, menus: IMenubarData): Promise { diff --git a/patched-vscode/src/vs/platform/native/common/native.ts b/patched-vscode/src/vs/platform/native/common/native.ts index 4c035680..a27a17ad 100644 --- a/patched-vscode/src/vs/platform/native/common/native.ts +++ b/patched-vscode/src/vs/platform/native/common/native.ts @@ -6,11 +6,12 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'vs/base/parts/sandbox/common/electronTypes'; +import { MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'vs/base/parts/sandbox/common/electronTypes'; import { ISerializableCommandAction } from 'vs/platform/action/common/action'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; +import { AuthInfo, Credentials } from 'vs/platform/request/common/request'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window'; @@ -73,6 +74,7 @@ export interface ICommonNativeHostService { getWindows(options: { includeAuxiliaryWindows: false }): Promise>; getWindowCount(): Promise; getActiveWindowId(): Promise; + getActiveWindowPosition(): Promise; openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; @@ -126,7 +128,7 @@ export interface ICommonNativeHostService { showItemInFolder(path: string): Promise; setRepresentedFilename(path: string, options?: INativeHostOptions): Promise; setDocumentEdited(edited: boolean, options?: INativeHostOptions): Promise; - openExternal(url: string): Promise; + openExternal(url: string, defaultApplication?: string): Promise; moveItemToTrash(fullPath: string): Promise; isAdmin(): Promise; @@ -176,7 +178,7 @@ export interface ICommonNativeHostService { exit(code: number): Promise; // Development - openDevTools(options?: Partial & INativeHostOptions): Promise; + openDevTools(options?: INativeHostOptions): Promise; toggleDevTools(options?: INativeHostOptions): Promise; // Perf Introspection @@ -184,6 +186,8 @@ export interface ICommonNativeHostService { // Connectivity resolveProxy(url: string): Promise; + lookupAuthorization(authInfo: AuthInfo): Promise; + lookupKerberosAuthorization(url: string): Promise; loadCertificates(): Promise; findFreePort(startPort: number, giveUpAfter: number, timeout: number, stride?: number): Promise; diff --git a/patched-vscode/src/vs/code/electron-main/auth.ts b/patched-vscode/src/vs/platform/native/electron-main/auth.ts similarity index 54% rename from patched-vscode/src/vs/code/electron-main/auth.ts rename to patched-vscode/src/vs/platform/native/electron-main/auth.ts index bf2a99f6..52b1f51c 100644 --- a/patched-vscode/src/vs/code/electron-main/auth.ts +++ b/patched-vscode/src/vs/platform/native/electron-main/auth.ts @@ -3,14 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, AuthenticationResponseDetails, AuthInfo, Event as ElectronEvent, WebContents } from 'electron'; +import { app, AuthenticationResponseDetails, AuthInfo as ElectronAuthInfo, Event as ElectronEvent, WebContents } from 'electron'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEncryptionMainService } from 'vs/platform/encryption/common/encryptionService'; +import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { AuthInfo, Credentials } from 'vs/platform/request/common/request'; import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IApplicationStorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; @@ -20,55 +25,37 @@ interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDe } type LoginEvent = { - event: ElectronEvent; + event?: ElectronEvent; authInfo: AuthInfo; - req: ElectronAuthenticationResponseDetails; - - callback: (username?: string, password?: string) => void; + callback?: (username?: string, password?: string) => void; }; -type Credentials = { - username: string; - password: string; -}; +export const IProxyAuthService = createDecorator('proxyAuthService'); -enum ProxyAuthState { - - /** - * Initial state: we will try to use stored credentials - * first to reply to the auth challenge. - */ - Initial = 1, - - /** - * We used stored credentials and are still challenged, - * so we will show a login dialog next. - */ - StoredCredentialsUsed, - - /** - * Finally, if we showed a login dialog already, we will - * not show any more login dialogs until restart to reduce - * the UI noise. - */ - LoginDialogShown +export interface IProxyAuthService { + lookupAuthorization(authInfo: AuthInfo): Promise; } -export class ProxyAuthHandler extends Disposable { +export class ProxyAuthService extends Disposable implements IProxyAuthService { + + declare readonly _serviceBrand: undefined; private readonly PROXY_CREDENTIALS_SERVICE_KEY = 'proxy-credentials://'; - private pendingProxyResolve: Promise | undefined = undefined; + private pendingProxyResolves = new Map>(); + private currentDialog: Promise | undefined = undefined; - private state = ProxyAuthState.Initial; + private cancelledAuthInfoHashes = new Set(); - private sessionCredentials: Credentials | undefined = undefined; + private sessionCredentials = new Map(); constructor( @ILogService private readonly logService: ILogService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService, - @IApplicationStorageMainService private readonly applicationStorageMainService: IApplicationStorageMainService + @IApplicationStorageMainService private readonly applicationStorageMainService: IApplicationStorageMainService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, ) { super(); @@ -76,39 +63,45 @@ export class ProxyAuthHandler extends Disposable { } private registerListeners(): void { - const onLogin = Event.fromNodeEventEmitter(app, 'login', (event: ElectronEvent, webContents: WebContents, req: ElectronAuthenticationResponseDetails, authInfo: AuthInfo, callback) => ({ event, webContents, req, authInfo, callback })); + const onLogin = Event.fromNodeEventEmitter(app, 'login', (event: ElectronEvent, _webContents: WebContents, req: ElectronAuthenticationResponseDetails, authInfo: ElectronAuthInfo, callback) => ({ event, authInfo: { ...authInfo, attempt: req.firstAuthAttempt ? 1 : 2 }, callback } satisfies LoginEvent)); this._register(onLogin(this.onLogin, this)); } - private async onLogin({ event, authInfo, req, callback }: LoginEvent): Promise { + async lookupAuthorization(authInfo: AuthInfo): Promise { + return this.onLogin({ authInfo }); + } + + private async onLogin({ event, authInfo, callback }: LoginEvent): Promise { if (!authInfo.isProxy) { return; // only for proxy } - if (!this.pendingProxyResolve && this.state === ProxyAuthState.LoginDialogShown && req.firstAuthAttempt) { - this.logService.trace('auth#onLogin (proxy) - exit - proxy dialog already shown'); - - return; // only one dialog per session at max (except when firstAuthAttempt: false which indicates a login problem) - } - // Signal we handle this event on our own, otherwise // Electron will ignore our provided credentials. - event.preventDefault(); + event?.preventDefault(); + + // Compute a hash over the authentication info to be used + // with the credentials store to return the right credentials + // given the properties of the auth request + // (see https://github.com/microsoft/vscode/issues/109497) + const authInfoHash = String(hash({ scheme: authInfo.scheme, host: authInfo.host, port: authInfo.port })); let credentials: Credentials | undefined = undefined; - if (!this.pendingProxyResolve) { + let pendingProxyResolve = this.pendingProxyResolves.get(authInfoHash); + if (!pendingProxyResolve) { this.logService.trace('auth#onLogin (proxy) - no pending proxy handling found, starting new'); - this.pendingProxyResolve = this.resolveProxyCredentials(authInfo); + pendingProxyResolve = this.resolveProxyCredentials(authInfo, authInfoHash); + this.pendingProxyResolves.set(authInfoHash, pendingProxyResolve); try { - credentials = await this.pendingProxyResolve; + credentials = await pendingProxyResolve; } finally { - this.pendingProxyResolve = undefined; + this.pendingProxyResolves.delete(authInfoHash); } } else { this.logService.trace('auth#onLogin (proxy) - pending proxy handling found'); - credentials = await this.pendingProxyResolve; + credentials = await pendingProxyResolve; } // According to Electron docs, it is fine to call back without @@ -118,14 +111,15 @@ export class ProxyAuthHandler extends Disposable { // > If `callback` is called without a username or password, the authentication // > request will be cancelled and the authentication error will be returned to the // > page. - callback(credentials?.username, credentials?.password); + callback?.(credentials?.username, credentials?.password); + return credentials; } - private async resolveProxyCredentials(authInfo: AuthInfo): Promise { + private async resolveProxyCredentials(authInfo: AuthInfo, authInfoHash: string): Promise { this.logService.trace('auth#resolveProxyCredentials (proxy) - enter'); try { - const credentials = await this.doResolveProxyCredentials(authInfo); + const credentials = await this.doResolveProxyCredentials(authInfo, authInfoHash); if (credentials) { this.logService.trace('auth#resolveProxyCredentials (proxy) - got credentials'); @@ -140,14 +134,69 @@ export class ProxyAuthHandler extends Disposable { return undefined; } - private async doResolveProxyCredentials(authInfo: AuthInfo): Promise { + private async doResolveProxyCredentials(authInfo: AuthInfo, authInfoHash: string): Promise { this.logService.trace('auth#doResolveProxyCredentials - enter', authInfo); - // Compute a hash over the authentication info to be used - // with the credentials store to return the right credentials - // given the properties of the auth request - // (see https://github.com/microsoft/vscode/issues/109497) - const authInfoHash = String(hash({ scheme: authInfo.scheme, host: authInfo.host, port: authInfo.port })); + // For testing. + if (this.environmentMainService.extensionTestsLocationURI) { + const credentials = this.configurationService.getValue('integration-test.http.proxyAuth'); + if (credentials) { + const j = credentials.indexOf(':'); + if (j !== -1) { + return { + username: credentials.substring(0, j), + password: credentials.substring(j + 1) + }; + } else { + return { + username: credentials, + password: '' + }; + } + } + return undefined; + } + + // Reply with manually supplied credentials. Fail if they are wrong. + const newHttpProxy = (this.configurationService.getValue('http.proxy') || '').trim() + || (process.env['https_proxy'] || process.env['HTTPS_PROXY'] || process.env['http_proxy'] || process.env['HTTP_PROXY'] || '').trim() + || undefined; + + if (newHttpProxy?.indexOf('@') !== -1) { + const uri = URI.parse(newHttpProxy!); + const i = uri.authority.indexOf('@'); + if (i !== -1) { + if (authInfo.attempt > 1) { + this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - ignoring previously used config/envvar credentials'); + return undefined; // We tried already, let the user handle it. + } + this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found config/envvar credentials to use'); + const credentials = uri.authority.substring(0, i); + const j = credentials.indexOf(':'); + if (j !== -1) { + return { + username: credentials.substring(0, j), + password: credentials.substring(j + 1) + }; + } else { + return { + username: credentials, + password: '' + }; + } + } + } + + // Reply with session credentials unless we used them already. + // In that case we need to show a login dialog again because + // they seem invalid. + const sessionCredentials = authInfo.attempt === 1 && this.sessionCredentials.get(authInfoHash); + if (sessionCredentials) { + this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found session credentials to use'); + + const { username, password } = sessionCredentials; + return { username, password }; + } let storedUsername: string | undefined; let storedPassword: string | undefined; @@ -166,13 +215,32 @@ export class ProxyAuthHandler extends Disposable { // Reply with stored credentials unless we used them already. // In that case we need to show a login dialog again because // they seem invalid. - if (this.state !== ProxyAuthState.StoredCredentialsUsed && typeof storedUsername === 'string' && typeof storedPassword === 'string') { + if (authInfo.attempt === 1 && typeof storedUsername === 'string' && typeof storedPassword === 'string') { this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - found stored credentials to use'); - this.state = ProxyAuthState.StoredCredentialsUsed; + this.sessionCredentials.set(authInfoHash, { username: storedUsername, password: storedPassword }); return { username: storedUsername, password: storedPassword }; } + const previousDialog = this.currentDialog; + const currentDialog = this.currentDialog = (async () => { + await previousDialog; + const credentials = await this.showProxyCredentialsDialog(authInfo, authInfoHash, storedUsername, storedPassword); + if (this.currentDialog === currentDialog!) { + this.currentDialog = undefined; + } + return credentials; + })(); + return currentDialog; + } + + private async showProxyCredentialsDialog(authInfo: AuthInfo, authInfoHash: string, storedUsername: string | undefined, storedPassword: string | undefined): Promise { + if (this.cancelledAuthInfoHashes.has(authInfoHash)) { + this.logService.trace('auth#doResolveProxyCredentials (proxy) - exit - login dialog was cancelled before, not showing again'); + + return undefined; + } + // Find suitable window to show dialog: prefer to show it in the // active window because any other network request will wait on // the credentials and we want the user to present the dialog. @@ -186,14 +254,14 @@ export class ProxyAuthHandler extends Disposable { this.logService.trace(`auth#doResolveProxyCredentials (proxy) - asking window ${window.id} to handle proxy login`); // Open proxy dialog + const sessionCredentials = this.sessionCredentials.get(authInfoHash); const payload = { authInfo, - username: this.sessionCredentials?.username ?? storedUsername, // prefer to show already used username (if any) over stored - password: this.sessionCredentials?.password ?? storedPassword, // prefer to show already used password (if any) over stored + username: sessionCredentials?.username ?? storedUsername, // prefer to show already used username (if any) over stored + password: sessionCredentials?.password ?? storedPassword, // prefer to show already used password (if any) over stored replyChannel: `vscode:proxyAuthResponse:${generateUuid()}` }; window.sendWhenReady('vscode:openProxyAuthenticationDialog', CancellationToken.None, payload); - this.state = ProxyAuthState.LoginDialogShown; // Handle reply const loginDialogCredentials = await new Promise(resolve => { @@ -229,6 +297,7 @@ export class ProxyAuthHandler extends Disposable { // We did not get any credentials from the window (e.g. cancelled) else { + this.cancelledAuthInfoHashes.add(authInfoHash); resolve(undefined); } } @@ -240,7 +309,7 @@ export class ProxyAuthHandler extends Disposable { // Remember credentials for the session in case // the credentials are wrong and we show the dialog // again - this.sessionCredentials = loginDialogCredentials; + this.sessionCredentials.set(authInfoHash, loginDialogCredentials); return loginDialogCredentials; } diff --git a/patched-vscode/src/vs/platform/native/electron-main/nativeHostMainService.ts b/patched-vscode/src/vs/platform/native/electron-main/nativeHostMainService.ts index bb5d30f0..4ccaa6b1 100644 --- a/patched-vscode/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/patched-vscode/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { exec } from 'child_process'; -import { app, BrowserWindow, clipboard, Display, Menu, MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, powerMonitor, SaveDialogOptions, SaveDialogReturnValue, screen, shell, webContents } from 'electron'; +import { app, BrowserWindow, clipboard, Display, Menu, MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, powerMonitor, SaveDialogOptions, SaveDialogReturnValue, screen, shell, webContents } from 'electron'; import { arch, cpus, freemem, loadavg, platform, release, totalmem, type } from 'os'; import { promisify } from 'util'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { dirname, join, resolve } from 'vs/base/common/path'; +import { matchesSomeScheme, Schemas } from 'vs/base/common/network'; +import { dirname, join, posix, resolve, win32 } from 'vs/base/common/path'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -32,7 +33,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { ICodeWindow } from 'vs/platform/window/electron-main/window'; -import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window'; +import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable, useWindowControlsOverlay } from 'vs/platform/window/common/window'; import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; @@ -43,6 +44,9 @@ import { IV8Profile } from 'vs/platform/profiling/common/profiling'; import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { CancellationError } from 'vs/base/common/errors'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProxyAuthService } from 'vs/platform/native/electron-main/auth'; +import { AuthInfo, Credentials, IRequestService } from 'vs/platform/request/common/request'; export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -61,7 +65,10 @@ export class NativeHostMainService extends Disposable implements INativeHostMain @ILogService private readonly logService: ILogService, @IProductService private readonly productService: IProductService, @IThemeMainService private readonly themeMainService: IThemeMainService, - @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService + @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IRequestService private readonly requestService: IRequestService, + @IProxyAuthService private readonly proxyAuthService: IProxyAuthService ) { super(); } @@ -172,6 +179,14 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return undefined; } + async getActiveWindowPosition(): Promise { + const activeWindow = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); + if (activeWindow) { + return activeWindow.getBounds(); + } + return undefined; + } + openWindow(windowId: number | undefined, options?: IOpenEmptyWindowOptions): Promise; openWindow(windowId: number | undefined, toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; openWindow(windowId: number | undefined, arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { @@ -322,7 +337,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } // Different source, delete it first - await Promises.unlink(source); + await fs.promises.unlink(source); } catch (error) { if (error.code !== 'ENOENT') { throw error; // throw on any error but file not found @@ -330,7 +345,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } try { - await Promises.symlink(target, source); + await fs.promises.symlink(target, source); } catch (error) { if (error.code !== 'EACCES' && error.code !== 'ENOENT') { throw error; @@ -362,7 +377,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain const { source } = await this.getShellCommandLink(); try { - await Promises.unlink(source); + await fs.promises.unlink(source); } catch (error) { switch (error.code) { case 'EACCES': { @@ -485,14 +500,56 @@ export class NativeHostMainService extends Disposable implements INativeHostMain window?.setDocumentEdited(edited); } - async openExternal(windowId: number | undefined, url: string): Promise { + async openExternal(windowId: number | undefined, url: string, defaultApplication?: string): Promise { this.environmentMainService.unsetSnapExportedVariables(); - shell.openExternal(url); - this.environmentMainService.restoreSnapExportedVariables(); + try { + if (matchesSomeScheme(url, Schemas.http, Schemas.https)) { + this.openExternalBrowser(url, defaultApplication); + } else { + shell.openExternal(url); + } + } finally { + this.environmentMainService.restoreSnapExportedVariables(); + } return true; } + private async openExternalBrowser(url: string, defaultApplication?: string): Promise { + const configuredBrowser = defaultApplication ?? this.configurationService.getValue('workbench.externalBrowser'); + if (!configuredBrowser) { + return shell.openExternal(url); + } + + if (configuredBrowser.includes(posix.sep) || configuredBrowser.includes(win32.sep)) { + const browserPathExists = await Promises.exists(configuredBrowser); + if (!browserPathExists) { + this.logService.error(`Configured external browser path does not exist: ${configuredBrowser}`); + return shell.openExternal(url); + } + } + + try { + const { default: open } = await import('open'); + const res = await open(url, { + app: { + // Use `open.apps` helper to allow cross-platform browser + // aliases to be looked up properly. Fallback to the + // configured value if not found. + name: Object.hasOwn(open.apps, configuredBrowser) ? open.apps[(configuredBrowser as keyof typeof open['apps'])] : configuredBrowser + } + }); + + res.stderr?.once('data', (data: Buffer) => { + this.logService.error(`Error openening external URL '${url}' using browser '${configuredBrowser}': ${data.toString()}`); + return shell.openExternal(url); + }); + } catch (error) { + this.logService.error(`Unable to open external URL '${url}' using browser '${configuredBrowser}' due to ${error}.`); + return shell.openExternal(url); + } + } + moveItemToTrash(windowId: number | undefined, fullPath: string): Promise { return shell.trashItem(fullPath); } @@ -500,7 +557,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain async isAdmin(): Promise { let isAdmin: boolean; if (isWindows) { - isAdmin = (await import('native-is-elevated'))(); + isAdmin = (await import('native-is-elevated')).default(); } else { isAdmin = process.getuid?.() === 0; } @@ -765,15 +822,28 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Connectivity async resolveProxy(windowId: number | undefined, url: string): Promise { + if (this.environmentMainService.extensionTestsLocationURI) { + const testProxy = this.configurationService.getValue('integration-test.http.proxy'); + if (testProxy) { + return testProxy; + } + } const window = this.codeWindowById(windowId); const session = window?.win?.webContents?.session; return session?.resolveProxy(url); } + async lookupAuthorization(_windowId: number | undefined, authInfo: AuthInfo): Promise { + return this.proxyAuthService.lookupAuthorization(authInfo); + } + + async lookupKerberosAuthorization(_windowId: number | undefined, url: string): Promise { + return this.requestService.lookupKerberosAuthorization(url); + } + async loadCertificates(_windowId: number | undefined): Promise { - const proxyAgent = await import('@vscode/proxy-agent'); - return proxyAgent.loadSystemCertificates({ log: this.logService }); + return this.requestService.loadCertificates(); } findFreePort(windowId: number | undefined, startPort: number, giveUpAfter: number, timeout: number, stride = 1): Promise { @@ -785,14 +855,28 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Development - async openDevTools(windowId: number | undefined, options?: Partial & INativeHostOptions): Promise { + async openDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); - window?.win?.webContents.openDevTools(options?.mode ? { mode: options.mode, activate: options.activate } : undefined); + + let mode: 'bottom' | undefined = undefined; + if (isLinux && useWindowControlsOverlay(this.configurationService)) { + mode = 'bottom'; // TODO@bpasero WCO and devtools collide with default option 'right' + } + window?.win?.webContents.openDevTools(mode ? { mode } : undefined); } async toggleDevTools(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); - window?.win?.webContents.toggleDevTools(); + const webContents = window?.win?.webContents; + if (!webContents) { + return; + } + + if (isLinux && useWindowControlsOverlay(this.configurationService) && !webContents.isDevToolsOpened()) { + webContents.openDevTools({ mode: 'bottom' }); // TODO@bpasero WCO and devtools collide with default option 'right' + } else { + webContents.toggleDevTools(); + } } //#endregion diff --git a/patched-vscode/src/vs/platform/observable/common/platformObservableUtils.ts b/patched-vscode/src/vs/platform/observable/common/platformObservableUtils.ts index 096993be..2e886ef6 100644 --- a/patched-vscode/src/vs/platform/observable/common/platformObservableUtils.ts +++ b/patched-vscode/src/vs/platform/observable/common/platformObservableUtils.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { autorunOpts, IObservable, IReader, observableFromEvent } from 'vs/base/common/observable'; +import { autorunOpts, IObservable, IReader } from 'vs/base/common/observable'; +import { observableFromEventOpts } from 'vs/base/common/observableInternal/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyValue, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyValue, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; /** Creates an observable update when a configuration key updates. */ export function observableConfigValue(key: string, defaultValue: T, configurationService: IConfigurationService): IObservable { - return observableFromEvent( + return observableFromEventOpts({ debugName: () => `Configuration Key "${key}"`, }, (handleChange) => configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(key)) { handleChange(e); diff --git a/patched-vscode/src/vs/platform/observable/common/wrapInReloadableClass.ts b/patched-vscode/src/vs/platform/observable/common/wrapInReloadableClass.ts new file mode 100644 index 00000000..cfecf902 --- /dev/null +++ b/patched-vscode/src/vs/platform/observable/common/wrapInReloadableClass.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { isHotReloadEnabled } from 'vs/base/common/hotReload'; +import { readHotReloadableExport } from 'vs/base/common/hotReloadHelpers'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { autorunWithStore } from 'vs/base/common/observable'; +import { BrandedService, GetLeadingNonServiceArgs, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +/** + * Wrap a class in a reloadable wrapper. + * When the wrapper is created, the original class is created. + * When the original class changes, the instance is re-created. +*/ +export function wrapInReloadableClass0(getClass: () => Result): Result> { + return !isHotReloadEnabled() ? getClass() : createWrapper(getClass, BaseClass0); +} + +type Result = new (...args: TArgs) => IDisposable; + +class BaseClass { + constructor( + public readonly instantiationService: IInstantiationService, + ) { } + + public init(...params: any[]): void { } +} + +function createWrapper(getClass: () => any, B: new (...args: T) => BaseClass) { + return (class ReloadableWrapper extends B { + private _autorun: IDisposable | undefined = undefined; + + override init(...params: any[]) { + this._autorun = autorunWithStore((reader, store) => { + const clazz = readHotReloadableExport(getClass(), reader); + store.add(this.instantiationService.createInstance(clazz as any, ...params) as IDisposable); + }); + } + + dispose(): void { + this._autorun?.dispose(); + } + }) as any; +} + +class BaseClass0 extends BaseClass { + constructor(@IInstantiationService i: IInstantiationService) { super(i); this.init(); } +} + +/** + * Wrap a class in a reloadable wrapper. + * When the wrapper is created, the original class is created. + * When the original class changes, the instance is re-created. +*/ +export function wrapInReloadableClass1(getClass: () => Result): Result> { + return !isHotReloadEnabled() ? getClass() as any : createWrapper(getClass, BaseClass1); +} + +class BaseClass1 extends BaseClass { + constructor(param1: any, @IInstantiationService i: IInstantiationService,) { super(i); this.init(param1); } +} diff --git a/patched-vscode/src/vs/platform/opener/browser/link.ts b/patched-vscode/src/vs/platform/opener/browser/link.ts index 710292ef..eb93b66a 100644 --- a/patched-vscode/src/vs/platform/opener/browser/link.ts +++ b/patched-vscode/src/vs/platform/opener/browser/link.ts @@ -14,7 +14,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import 'vs/css!./link'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; export interface ILinkDescriptor { @@ -33,7 +33,7 @@ export interface ILinkOptions { export class Link extends Disposable { private el: HTMLAnchorElement; - private hover?: IUpdatableHover; + private hover?: IManagedHover; private hoverDelegate: IHoverDelegate; private _enabled: boolean = true; @@ -131,7 +131,7 @@ export class Link extends Disposable { if (this.hoverDelegate.showNativeHover) { this.el.title = title ?? ''; } else if (!this.hover && title) { - this.hover = this._register(this._hoverService.setupUpdatableHover(this.hoverDelegate, this.el, title)); + this.hover = this._register(this._hoverService.setupManagedHover(this.hoverDelegate, this.el, title)); } else if (this.hover) { this.hover.update(title); } diff --git a/patched-vscode/src/vs/platform/opener/test/common/opener.test.ts b/patched-vscode/src/vs/platform/opener/test/common/opener.test.ts index 35b6e027..93ee50f9 100644 --- a/patched-vscode/src/vs/platform/opener/test/common/opener.test.ts +++ b/patched-vscode/src/vs/platform/opener/test/common/opener.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { extractSelection, withSelection } from 'vs/platform/opener/common/opener'; diff --git a/patched-vscode/src/vs/platform/product/common/product.ts b/patched-vscode/src/vs/platform/product/common/product.ts index 7f5f731e..58278d97 100644 --- a/patched-vscode/src/vs/platform/product/common/product.ts +++ b/patched-vscode/src/vs/platform/product/common/product.ts @@ -53,23 +53,21 @@ else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) { else { // Built time configuration (do NOT modify) - product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as IProductConfiguration; + product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as any; // Running out of sources if (Object.keys(product).length === 0) { Object.assign(product, { - version: '1.90.0-dev', - nameShort: 'CodeEditor', - nameLong: 'Code Editor', + version: '1.91.0-dev', + nameShort: 'Code - OSS Dev', + nameLong: 'Code - OSS Dev', applicationName: 'code-oss', dataFolderName: '.vscode-oss', - commit: "hellocommit", - date: "hellodate", urlProtocol: 'code-oss', reportIssueUrl: 'https://github.com/microsoft/vscode/issues/new', licenseName: 'MIT', licenseUrl: 'https://github.com/microsoft/vscode/blob/main/LICENSE.txt', - serverLicenseUrl: 'https://github.com/microsoft/vscode/blob/main/LICENSE.txt', + serverLicenseUrl: 'https://github.com/microsoft/vscode/blob/main/LICENSE.txt' }); } } diff --git a/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.esm.ts b/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.esm.ts new file mode 100644 index 00000000..4329929c --- /dev/null +++ b/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { create } from './profileAnalysisWorker'; +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; + +bootstrapSimpleWorker(create); diff --git a/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts b/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts index 14d6a473..4d04422e 100644 --- a/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts +++ b/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts @@ -6,12 +6,16 @@ import { basename } from 'vs/base/common/path'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI } from 'vs/base/common/uri'; -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; import { IV8Profile, Utils } from 'vs/platform/profiling/common/profiling'; import { IProfileModel, BottomUpSample, buildModel, BottomUpNode, processNode, CdpCallFrame } from 'vs/platform/profiling/common/profilingModel'; import { BottomUpAnalysis, IProfileAnalysisWorker, ProfilingOutput } from 'vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService'; -export function create(): IRequestHandler { +/** + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle + */ +export function create(workerServer: IWorkerServer): IRequestHandler { return new ProfileAnalysisWorker(); } @@ -19,7 +23,7 @@ class ProfileAnalysisWorker implements IRequestHandler, IProfileAnalysisWorker { _requestHandlerBrand: any; - analyseBottomUp(profile: IV8Profile): BottomUpAnalysis { + $analyseBottomUp(profile: IV8Profile): BottomUpAnalysis { if (!Utils.isValidProfile(profile)) { return { kind: ProfilingOutput.Irrelevant, samples: [] }; } @@ -37,7 +41,7 @@ class ProfileAnalysisWorker implements IRequestHandler, IProfileAnalysisWorker { return { kind: ProfilingOutput.Interesting, samples }; } - analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][] { + $analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][] { // build search tree const searchTree = TernarySearchTree.forUris(); diff --git a/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts b/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts index ad6a25cd..433200a8 100644 --- a/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts +++ b/patched-vscode/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ -import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; import { URI } from 'vs/base/common/uri'; -import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { Proxied } from 'vs/base/common/worker/simpleWorker'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -15,7 +15,6 @@ import { BottomUpSample } from 'vs/platform/profiling/common/profilingModel'; import { reportSample } from 'vs/platform/profiling/common/profilingTelemetrySpec'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - export const enum ProfilingOutput { Failure, Irrelevant, @@ -41,8 +40,6 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { declare _serviceBrand: undefined; - private readonly _workerFactory = new DefaultWorkerFactory('CpuProfileAnalysis'); - constructor( @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, @@ -50,14 +47,13 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { private async _withWorker(callback: (worker: Proxied) => Promise): Promise { - const worker = new SimpleWorkerClient, {}>( - this._workerFactory, + const worker = createWebWorker( 'vs/platform/profiling/electron-sandbox/profileAnalysisWorker', - { /* host */ } + 'CpuProfileAnalysisWorker' ); try { - const r = await callback(await worker.getProxyObject()); + const r = await callback(worker.proxy); return r; } finally { worker.dispose(); @@ -66,7 +62,7 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { async analyseBottomUp(profile: IV8Profile, callFrameClassifier: IScriptUrlClassifier, perfBaseline: number, sendAsErrorTelemtry: boolean): Promise { return this._withWorker(async worker => { - const result = await worker.analyseBottomUp(profile); + const result = await worker.$analyseBottomUp(profile); if (result.kind === ProfilingOutput.Interesting) { for (const sample of result.samples) { reportSample({ @@ -82,7 +78,7 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { async analyseByLocation(profile: IV8Profile, locations: [location: URI, id: string][]): Promise<[category: string, aggregated: number][]> { return this._withWorker(async worker => { - const result = await worker.analyseByUrlCategory(profile, locations); + const result = await worker.$analyseByUrlCategory(profile, locations); return result; }); } @@ -103,15 +99,8 @@ export interface CategoryAnalysis { } export interface IProfileAnalysisWorker { - analyseBottomUp(profile: IV8Profile): BottomUpAnalysis; - analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][]; + $analyseBottomUp(profile: IV8Profile): BottomUpAnalysis; + $analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][]; } -// TODO@jrieken move into worker logic -type Proxied = { [K in keyof T]: T[K] extends (...args: infer A) => infer R - ? (...args: A) => Promise> - : never -}; - - registerSingleton(IProfileAnalysisWorkerService, ProfileAnalysisWorkerService, InstantiationType.Delayed); diff --git a/patched-vscode/src/vs/platform/progress/common/progress.ts b/patched-vscode/src/vs/platform/progress/common/progress.ts index e1eb0a2f..2a1c7349 100644 --- a/patched-vscode/src/vs/platform/progress/common/progress.ts +++ b/patched-vscode/src/vs/platform/progress/common/progress.ts @@ -65,7 +65,7 @@ export interface IProgressNotificationOptions extends IProgressOptions { readonly secondaryActions?: readonly IAction[]; readonly delay?: number; readonly priority?: NotificationPriority; - readonly type?: 'syncing' | 'loading'; + readonly type?: 'loading' | 'syncing'; } export interface IProgressDialogOptions extends IProgressOptions { @@ -77,7 +77,7 @@ export interface IProgressDialogOptions extends IProgressOptions { export interface IProgressWindowOptions extends IProgressOptions { readonly location: ProgressLocation.Window; readonly command?: string; - readonly type?: 'syncing' | 'loading'; + readonly type?: 'loading' | 'syncing'; } export interface IProgressCompositeOptions extends IProgressOptions { diff --git a/patched-vscode/src/vs/platform/progress/test/common/progress.test.ts b/patched-vscode/src/vs/platform/progress/test/common/progress.test.ts index 85bce306..24c2ddb7 100644 --- a/patched-vscode/src/vs/platform/progress/test/common/progress.test.ts +++ b/patched-vscode/src/vs/platform/progress/test/common/progress.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { AsyncProgress } from 'vs/platform/progress/common/progress'; diff --git a/patched-vscode/src/vs/platform/protocol/electron-main/protocolMainService.ts b/patched-vscode/src/vs/platform/protocol/electron-main/protocolMainService.ts index 8b6fd7b5..4dcb1bca 100644 --- a/patched-vscode/src/vs/platform/protocol/electron-main/protocolMainService.ts +++ b/patched-vscode/src/vs/platform/protocol/electron-main/protocolMainService.ts @@ -96,7 +96,8 @@ export class ProtocolMainService extends Disposable implements IProtocolMainServ let headers: Record | undefined; if (this.environmentService.crossOriginIsolated) { - if (basename(path) === 'workbench.html' || basename(path) === 'workbench-dev.html') { + const pathBasename = basename(path); + if (pathBasename === 'workbench.html' || pathBasename === 'workbench-dev.html' || pathBasename === 'workbench.esm.html' || pathBasename === 'workbench-dev.esm.html') { headers = COI.CoopAndCoep; } else { headers = COI.getHeadersFromQuery(request.url); diff --git a/patched-vscode/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/patched-vscode/src/vs/platform/quickinput/browser/helpQuickAccess.ts index 0346fc0b..0a931587 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/helpQuickAccess.ts +++ b/patched-vscode/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -25,7 +25,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { @IKeybindingService private readonly keybindingService: IKeybindingService ) { } - provide(picker: IQuickPick): IDisposable { + provide(picker: IQuickPick): IDisposable { const disposables = new DisposableStore(); // Open a picker with the selected value if picked diff --git a/patched-vscode/src/vs/platform/quickinput/browser/media/quickInput.css b/patched-vscode/src/vs/platform/quickinput/browser/media/quickInput.css index 3afa06e5..9108ee9a 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/media/quickInput.css +++ b/patched-vscode/src/vs/platform/quickinput/browser/media/quickInput.css @@ -16,7 +16,8 @@ .quick-input-titlebar { display: flex; align-items: center; - border-radius: inherit; + border-top-right-radius: 5px; + border-top-left-radius: 5px; } .quick-input-left-action-bar { @@ -25,6 +26,10 @@ flex: 1; } +.quick-input-inline-action-bar { + margin: 2px 0 0 5px; +} + .quick-input-title { padding: 3px 0px; text-align: center; @@ -172,7 +177,6 @@ box-sizing: border-box; overflow: hidden; display: flex; - height: 100%; padding: 0 6px; } @@ -295,7 +299,7 @@ .quick-input-list .quick-input-list-entry-action-bar .action-label.codicon { margin-right: 4px; - padding: 0px 2px 2px 2px; + padding: 2px; } .quick-input-list .quick-input-list-entry-action-bar { diff --git a/patched-vscode/src/vs/platform/quickinput/browser/pickerQuickAccess.ts b/patched-vscode/src/vs/platform/quickinput/browser/pickerQuickAccess.ts index 0613c557..eded3510 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/pickerQuickAccess.ts +++ b/patched-vscode/src/vs/platform/quickinput/browser/pickerQuickAccess.ts @@ -133,7 +133,7 @@ export abstract class PickerQuickAccessProvider, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { + provide(picker: IQuickPick, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { const disposables = new DisposableStore(); // Apply options if any diff --git a/patched-vscode/src/vs/platform/quickinput/browser/quickAccess.ts b/patched-vscode/src/vs/platform/quickinput/browser/quickAccess.ts index 7cccc352..b66289c8 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/quickAccess.ts +++ b/patched-vscode/src/vs/platform/quickinput/browser/quickAccess.ts @@ -20,7 +20,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon private readonly lastAcceptedPickerValues = new Map(); private visibleQuickAccess: { - readonly picker: IQuickPick; + readonly picker: IQuickPick; readonly descriptor: IQuickAccessProviderDescriptor | undefined; readonly value: string; } | undefined = undefined; @@ -99,7 +99,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon // Create a picker for the provider to use with the initial value // and adjust the filtering to exclude the prefix from filtering const disposables = new DisposableStore(); - const picker = disposables.add(this.quickInputService.createQuickPick()); + const picker = disposables.add(this.quickInputService.createQuickPick({ useSeparators: true })); picker.value = value; this.adjustValueSelection(picker, descriptor, options); picker.placeholder = options?.placeholder ?? descriptor?.placeholder; @@ -163,7 +163,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon } } - private adjustValueSelection(picker: IQuickPick, descriptor?: IQuickAccessProviderDescriptor, options?: IQuickAccessOptions): void { + private adjustValueSelection(picker: IQuickPick, descriptor?: IQuickAccessProviderDescriptor, options?: IQuickAccessOptions): void { let valueSelection: [number, number]; // Preserve: just always put the cursor at the end @@ -180,7 +180,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon } private registerPickerListeners( - picker: IQuickPick, + picker: IQuickPick, provider: IQuickAccessProvider | undefined, descriptor: IQuickAccessProviderDescriptor | undefined, value: string, diff --git a/patched-vscode/src/vs/platform/quickinput/browser/quickInput.ts b/patched-vscode/src/vs/platform/quickinput/browser/quickInput.ts index 4c6dfbe6..afc73c9e 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/quickInput.ts +++ b/patched-vscode/src/vs/platform/quickinput/browser/quickInput.ts @@ -25,13 +25,12 @@ import Severity from 'vs/base/common/severity'; import { ThemeIcon } from 'vs/base/common/themables'; import 'vs/css!./media/quickInput'; import { localize } from 'vs/nls'; -import { IInputBox, IKeyMods, IQuickInput, IQuickInputButton, IQuickInputHideEvent, IQuickInputToggle, IQuickNavigateConfiguration, IQuickPick, IQuickPickDidAcceptEvent, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickSeparatorButtonEvent, IQuickPickWillAcceptEvent, IQuickWidget, ItemActivation, NO_KEY_MODS, QuickInputHideReason, QuickInputType } from 'vs/platform/quickinput/common/quickInput'; +import { IInputBox, IKeyMods, IQuickInput, IQuickInputButton, IQuickInputHideEvent, IQuickInputToggle, IQuickNavigateConfiguration, IQuickPick, IQuickPickDidAcceptEvent, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickSeparatorButtonEvent, IQuickPickWillAcceptEvent, IQuickWidget, ItemActivation, NO_KEY_MODS, QuickInputButtonLocation, QuickInputHideReason, QuickInputType, QuickPickFocus } from 'vs/platform/quickinput/common/quickInput'; import { QuickInputBox } from './quickInputBox'; import { quickInputButtonToAction, renderQuickInputDescription } from './quickInputUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHoverService, WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; import { QuickInputTree } from 'vs/platform/quickinput/browser/quickInputTree'; -import { QuickPickFocus } from '../common/quickInput'; import type { IHoverOptions } from 'vs/base/browser/ui/hover/hover'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -100,6 +99,7 @@ export interface QuickInputUI { description2: HTMLElement; widget: HTMLElement; rightActionBar: ActionBar; + inlineActionBar: ActionBar; checkAll: HTMLInputElement; inputContainer: HTMLElement; filterContainer: HTMLElement; @@ -157,7 +157,9 @@ abstract class QuickInput extends Disposable implements IQuickInput { private _contextKey: string | undefined; private _busy = false; private _ignoreFocusOut = false; - private _buttons: IQuickInputButton[] = []; + private _leftButtons: IQuickInputButton[] = []; + private _rightButtons: IQuickInputButton[] = []; + private _inlineButtons: IQuickInputButton[] = []; private buttonsUpdated = false; private _toggles: IQuickInputToggle[] = []; private togglesUpdated = false; @@ -273,12 +275,24 @@ abstract class QuickInput extends Disposable implements IQuickInput { } } + protected get titleButtons() { + return this._leftButtons.length + ? [...this._leftButtons, this._rightButtons] + : this._rightButtons; + } + get buttons() { - return this._buttons; + return [ + ...this._leftButtons, + ...this._rightButtons, + ...this._inlineButtons + ]; } set buttons(buttons: IQuickInputButton[]) { - this._buttons = buttons; + this._leftButtons = buttons.filter(b => b === backButton); + this._rightButtons = buttons.filter(b => b !== backButton && b.location !== QuickInputButtonLocation.Inline); + this._inlineButtons = buttons.filter(b => b.location === QuickInputButtonLocation.Inline); this.buttonsUpdated = true; this.update(); } @@ -407,8 +421,7 @@ abstract class QuickInput extends Disposable implements IQuickInput { if (this.buttonsUpdated) { this.buttonsUpdated = false; this.ui.leftActionBar.clear(); - const leftButtons = this.buttons - .filter(button => button === backButton) + const leftButtons = this._leftButtons .map((button, index) => quickInputButtonToAction( button, `id-${index}`, @@ -416,14 +429,21 @@ abstract class QuickInput extends Disposable implements IQuickInput { )); this.ui.leftActionBar.push(leftButtons, { icon: true, label: false }); this.ui.rightActionBar.clear(); - const rightButtons = this.buttons - .filter(button => button !== backButton) + const rightButtons = this._rightButtons .map((button, index) => quickInputButtonToAction( button, `id-${index}`, async () => this.onDidTriggerButtonEmitter.fire(button) )); this.ui.rightActionBar.push(rightButtons, { icon: true, label: false }); + this.ui.inlineActionBar.clear(); + const inlineButtons = this._inlineButtons + .map((button, index) => quickInputButtonToAction( + button, + `id-${index}`, + async () => this.onDidTriggerButtonEmitter.fire(button) + )); + this.ui.inlineActionBar.push(inlineButtons, { icon: true, label: false }); } if (this.togglesUpdated) { this.togglesUpdated = false; @@ -507,7 +527,7 @@ abstract class QuickInput extends Disposable implements IQuickInput { } } -export class QuickPick extends QuickInput implements IQuickPick { +export class QuickPick extends QuickInput implements IQuickPick { private static readonly DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); @@ -518,7 +538,7 @@ export class QuickPick extends QuickInput implements I private readonly onWillAcceptEmitter = this._register(new Emitter()); private readonly onDidAcceptEmitter = this._register(new Emitter()); private readonly onDidCustomEmitter = this._register(new Emitter()); - private _items: Array = []; + private _items: O extends { useSeparators: true } ? Array : Array = []; private itemsUpdated = false; private _canSelectMany = false; private _canAcceptInBackground = false; @@ -625,7 +645,7 @@ export class QuickPick extends QuickInput implements I this.ui.list.scrollTop = scrollTop; } - set items(items: Array) { + set items(items: O extends { useSeparators: true } ? Array : Array) { this._items = items; this.itemsUpdated = true; this.update(); @@ -895,7 +915,7 @@ export class QuickPick extends QuickInput implements I } })); this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => { - if (!this.canSelectMany) { + if (!this.canSelectMany || !this.visible) { return; } if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) { @@ -986,7 +1006,7 @@ export class QuickPick extends QuickInput implements I const scrollTopBefore = this.keepScrollPosition ? this.scrollTop : 0; const hasDescription = !!this.description; const visibilities: Visibilities = { - title: !!this.title || !!this.step || !!this.buttons.length, + title: !!this.title || !!this.step || !!this.titleButtons.length, description: hasDescription, checkAll: this.canSelectMany && !this._hideCheckAll, checkBox: this.canSelectMany, @@ -1036,9 +1056,6 @@ export class QuickPick extends QuickInput implements I // We want focus to exist in the list if there are items so that space can be used to toggle this.ui.list.shouldLoop = !this.canSelectMany; this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); - this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); - this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); - this.ui.count.setCount(this.ui.list.getCheckedCount()); switch (this._itemActivation) { case ItemActivation.NONE: this._itemActivation = ItemActivation.FIRST; // only valid once, then unset @@ -1216,7 +1233,7 @@ export class InputBox extends QuickInput implements IInputBox { this.ui.container.classList.remove('hidden-input'); const visibilities: Visibilities = { - title: !!this.title || !!this.step || !!this.buttons.length, + title: !!this.title || !!this.step || !!this.titleButtons.length, description: !!this.description || !!this.step, inputBox: true, message: true, @@ -1250,7 +1267,7 @@ export class QuickWidget extends QuickInput implements IQuickWidget { } const visibilities: Visibilities = { - title: !!this.title || !!this.step || !!this.buttons.length, + title: !!this.title || !!this.step || !!this.titleButtons.length, description: !!this.description || !!this.step }; diff --git a/patched-vscode/src/vs/platform/quickinput/browser/quickInputController.ts b/patched-vscode/src/vs/platform/quickinput/browser/quickInputController.ts index 626b74e8..8941fb5b 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/patched-vscode/src/vs/platform/quickinput/browser/quickInputController.ts @@ -16,14 +16,13 @@ import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import { isString } from 'vs/base/common/types'; import { localize } from 'vs/nls'; -import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInput, IQuickInputButton, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, IQuickWidget, QuickInputHideReason, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInput, IQuickInputButton, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, IQuickWidget, QuickInputHideReason, QuickPickInput, QuickPickFocus } from 'vs/platform/quickinput/common/quickInput'; import { QuickInputBox } from 'vs/platform/quickinput/browser/quickInputBox'; import { QuickInputUI, Writeable, IQuickInputStyles, IQuickInputOptions, QuickPick, backButton, InputBox, Visibilities, QuickWidget, InQuickInputContextKey, QuickInputTypeContextKey, EndOfQuickInputBoxContextKey } from 'vs/platform/quickinput/browser/quickInput'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { mainWindow } from 'vs/base/browser/window'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { QuickInputTree } from 'vs/platform/quickinput/browser/quickInputTree'; -import { QuickPickFocus } from '../common/quickInput'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import 'vs/platform/quickinput/browser/quickInputActions'; @@ -157,6 +156,9 @@ export class QuickInputController extends Disposable { countContainer.setAttribute('aria-live', 'polite'); const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }, this.styles.countBadge); + const inlineActionBar = this._register(new ActionBar(headerContainer, { hoverDelegate: this.options.hoverDelegate })); + inlineActionBar.domNode.classList.add('quick-input-inline-action-bar'); + const okContainer = dom.append(headerContainer, $('.quick-input-action')); const ok = this._register(new Button(okContainer, this.styles.button)); ok.label = localize('ok', "OK"); @@ -275,7 +277,7 @@ export class QuickInputController extends Disposable { } else { selectors.push('input[type=text]'); } - if (this.getUI().list.isDisplayed()) { + if (this.getUI().list.displayed) { selectors.push('.monaco-list'); } // focus links if there are any @@ -321,6 +323,7 @@ export class QuickInputController extends Disposable { description2, widget, rightActionBar, + inlineActionBar, checkAll, inputContainer, filterContainer, @@ -359,7 +362,7 @@ export class QuickInputController extends Disposable { } } - pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { + pick>(picks: Promise[]> | QuickPickInput[], options: IPickOptions = {}, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { type R = (O extends { canPickMany: true } ? T[] : T) | undefined; return new Promise((doResolve, reject) => { let resolve = (result: R) => { @@ -371,7 +374,7 @@ export class QuickInputController extends Disposable { resolve(undefined); return; } - const input = this.createQuickPick(); + const input = this.createQuickPick({ useSeparators: true }); let activeItem: T | undefined; const disposables = [ input, @@ -435,6 +438,9 @@ export class QuickInputController extends Disposable { }), ]; input.title = options.title; + if (options.value) { + input.value = options.value; + } input.canSelectMany = !!options.canPickMany; input.placeholder = options.placeHolder; input.ignoreFocusOut = !!options.ignoreFocusLost; @@ -542,9 +548,11 @@ export class QuickInputController extends Disposable { backButton = backButton; - createQuickPick(): IQuickPick { + createQuickPick(options: { useSeparators: true }): IQuickPick; + createQuickPick(options?: { useSeparators: boolean }): IQuickPick; + createQuickPick(options: { useSeparators: boolean } = { useSeparators: false }): IQuickPick { const ui = this.getUI(true); - return new QuickPick(ui); + return new QuickPick(ui); } createInputBox(): IInputBox { @@ -571,6 +579,7 @@ export class QuickInputController extends Disposable { ui.description2.textContent = ''; dom.reset(ui.widget); ui.rightActionBar.clear(); + ui.inlineActionBar.clear(); ui.checkAll.checked = false; // ui.inputBox.value = ''; Avoid triggering an event. ui.inputBox.placeholder = ''; @@ -615,7 +624,7 @@ export class QuickInputController extends Disposable { ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none'; ui.message.style.display = visibilities.message ? '' : 'none'; ui.progressBar.getContainer().style.display = visibilities.progressBar ? '' : 'none'; - ui.list.display(!!visibilities.list); + ui.list.displayed = !!visibilities.list; ui.container.classList.toggle('show-checkboxes', !!visibilities.checkBox); ui.container.classList.toggle('hidden-input', !visibilities.inputBox && !visibilities.description); this.updateLayout(); // TODO @@ -684,7 +693,7 @@ export class QuickInputController extends Disposable { } navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { - if (this.isVisible() && this.getUI().list.isDisplayed()) { + if (this.isVisible() && this.getUI().list.displayed) { this.getUI().list.focus(next ? QuickPickFocus.Next : QuickPickFocus.Previous); if (quickNavigate && this.controller instanceof QuickPick) { this.controller.quickNavigate = quickNavigate; diff --git a/patched-vscode/src/vs/platform/quickinput/browser/quickInputService.ts b/patched-vscode/src/vs/platform/quickinput/browser/quickInputService.ts index 4727a3f9..3c5b30a9 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/patched-vscode/src/vs/platform/quickinput/browser/quickInputService.ts @@ -149,7 +149,7 @@ export class QuickInputService extends Themable implements IQuickInputService { }); } - pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { + pick>(picks: Promise[]> | QuickPickInput[], options?: O, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { return this.controller.pick(picks, options, token); } @@ -157,8 +157,10 @@ export class QuickInputService extends Themable implements IQuickInputService { return this.controller.input(options, token); } - createQuickPick(): IQuickPick { - return this.controller.createQuickPick(); + createQuickPick(options: { useSeparators: true }): IQuickPick; + createQuickPick(options?: { useSeparators: boolean }): IQuickPick; + createQuickPick(options: { useSeparators: boolean } = { useSeparators: false }): IQuickPick { + return this.controller.createQuickPick(options); } createInputBox(): IInputBox { diff --git a/patched-vscode/src/vs/platform/quickinput/browser/quickInputTree.ts b/patched-vscode/src/vs/platform/quickinput/browser/quickInputTree.ts index 6fa28c37..74163bd7 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/quickInputTree.ts +++ b/patched-vscode/src/vs/platform/quickinput/browser/quickInputTree.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { Emitter, Event, IValueWithChangeEvent } from 'vs/base/common/event'; +import { Emitter, Event, EventBufferer, IValueWithChangeEvent } from 'vs/base/common/event'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IObjectTreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { IObjectTreeElement, ITreeNode, ITreeRenderer, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickSeparatorButtonEvent, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickSeparatorButtonEvent, QuickPickItem, QuickPickFocus } from 'vs/platform/quickinput/common/quickInput'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IMatch } from 'vs/base/common/filters'; import { IListAccessibilityProvider, IListStyles } from 'vs/base/browser/ui/list/listWidget'; @@ -36,9 +36,10 @@ import { ltrim } from 'vs/base/common/strings'; import { RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; import { ThrottledDelayer } from 'vs/base/common/async'; import { isCancellationError } from 'vs/base/common/errors'; -import type { IHoverWidget, IUpdatableHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; -import { QuickPickFocus } from '../common/quickInput'; +import type { IHoverWidget, IManagedHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { observableValue, observableValueOpts, transaction } from 'vs/base/common/observable'; +import { equals } from 'vs/base/common/arrays'; const $ = dom.$; @@ -434,7 +435,7 @@ class QuickPickItemElementRenderer extends BaseQuickInputListRenderer, index: number, data: IQuickInputItemTemplateData): void { const element = node.element; data.element = element; @@ -566,7 +573,7 @@ class QuickPickSeparatorElementRenderer extends BaseQuickInputListRenderer(); /** * Event that is fired when the tree receives a keydown. @@ -668,17 +677,17 @@ export class QuickInputTree extends Disposable { */ readonly onLeave: Event = this._onLeave.event; - private readonly _onChangedAllVisibleChecked = new Emitter(); - onChangedAllVisibleChecked: Event = this._onChangedAllVisibleChecked.event; + private readonly _visibleCountObservable = observableValue('VisibleCount', 0); + onChangedVisibleCount: Event = Event.fromObservable(this._visibleCountObservable, this._store); - private readonly _onChangedCheckedCount = new Emitter(); - onChangedCheckedCount: Event = this._onChangedCheckedCount.event; + private readonly _allVisibleCheckedObservable = observableValue('AllVisibleChecked', false); + onChangedAllVisibleChecked: Event = Event.fromObservable(this._allVisibleCheckedObservable, this._store); - private readonly _onChangedVisibleCount = new Emitter(); - onChangedVisibleCount: Event = this._onChangedVisibleCount.event; + private readonly _checkedCountObservable = observableValue('CheckedCount', 0); + onChangedCheckedCount: Event = Event.fromObservable(this._checkedCountObservable, this._store); - private readonly _onChangedCheckedElements = new Emitter(); - onChangedCheckedElements: Event = this._onChangedCheckedElements.event; + private readonly _checkedElementsObservable = observableValueOpts({ equalsFn: equals }, new Array()); + onChangedCheckedElements: Event = Event.fromObservable(this._checkedElementsObservable, this._store); private readonly _onButtonTriggered = new Emitter>(); onButtonTriggered = this._onButtonTriggered.event; @@ -686,21 +695,24 @@ export class QuickInputTree extends Disposable { private readonly _onSeparatorButtonTriggered = new Emitter(); onSeparatorButtonTriggered = this._onSeparatorButtonTriggered.event; + private readonly _elementChecked = new Emitter<{ element: IQuickPickElement; checked: boolean }>(); + private readonly _elementCheckedEventBufferer = new EventBufferer(); + + //#endregion + + private _hasCheckboxes = false; + private readonly _container: HTMLElement; private readonly _tree: WorkbenchObjectTree; private readonly _separatorRenderer: QuickPickSeparatorElementRenderer; private readonly _itemRenderer: QuickPickItemElementRenderer; - private readonly _elementChecked = new Emitter<{ element: IQuickPickElement; checked: boolean }>(); private _inputElements = new Array(); private _elementTree = new Array(); private _itemElements = new Array(); // Elements that apply to the current set of elements private readonly _elementDisposable = this._register(new DisposableStore()); private _lastHover: IHoverWidget | undefined; - // This is used to prevent setting the checked state of a single element from firing the checked events - // so that we can batch them together. This can probably be improved by handling events differently, - // but this works for now. An observable would probably be ideal for this. - private _shouldFireCheckedEvents = true; + private _lastQueryString: string | undefined; constructor( private parent: HTMLElement, @@ -721,6 +733,24 @@ export class QuickInputTree extends Disposable { new QuickInputItemDelegate(), [this._itemRenderer, this._separatorRenderer], { + filter: { + filter(element) { + return element.hidden + ? TreeVisibility.Hidden + : element instanceof QuickPickSeparatorElement + ? TreeVisibility.Recurse + : TreeVisibility.Visible; + }, + }, + sorter: { + compare: (element, otherElement) => { + if (!this.sortByLabel || !this._lastQueryString) { + return 0; + } + const normalizedSearchValue = this._lastQueryString.toLowerCase(); + return compareEntries(element, otherElement, normalizedSearchValue); + }, + }, accessibilityProvider: new QuickInputAccessibilityProvider(), setRowLineHeight: false, multipleSelectionSupport: false, @@ -743,7 +773,8 @@ export class QuickInputTree extends Disposable { get onDidChangeFocus() { return Event.map( this._tree.onDidChangeFocus, - e => e.elements.filter((e): e is QuickPickItemElement => e instanceof QuickPickItemElement).map(e => e.item) + e => e.elements.filter((e): e is QuickPickItemElement => e instanceof QuickPickItemElement).map(e => e.item), + this._store ); } @@ -754,10 +785,19 @@ export class QuickInputTree extends Disposable { e => ({ items: e.elements.filter((e): e is QuickPickItemElement => e instanceof QuickPickItemElement).map(e => e.item), event: e.browserEvent - }) + }), + this._store ); } + get displayed() { + return this._container.style.display !== 'none'; + } + + set displayed(value: boolean) { + this._container.style.display = value ? '' : 'none'; + } + get scrollTop() { return this._tree.scrollTop; } @@ -842,6 +882,7 @@ export class QuickInputTree extends Disposable { this._registerOnKeyDown(); this._registerOnContainerClick(); this._registerOnMouseMiddleClick(); + this._registerOnTreeModelChanged(); this._registerOnElementChecked(); this._registerOnContextMenu(); this._registerHoverListeners(); @@ -879,8 +920,19 @@ export class QuickInputTree extends Disposable { })); } + private _registerOnTreeModelChanged() { + this._register(this._tree.onDidChangeModel(() => { + const visibleCount = this._itemElements.filter(e => !e.hidden).length; + this._visibleCountObservable.set(visibleCount, undefined); + if (this._hasCheckboxes) { + this._updateCheckedObservables(); + } + })); + } + private _registerOnElementChecked() { - this._register(this._elementChecked.event(_ => this._fireCheckedEvents())); + // Only fire the last event when buffered + this._register(this._elementCheckedEventBufferer.wrapEvent(this._elementChecked.event, (_, e) => e)(_ => this._updateCheckedObservables())); } private _registerOnContextMenu() { @@ -1015,37 +1067,22 @@ export class QuickInputTree extends Disposable { //#region public methods - getAllVisibleChecked() { - return this._allVisibleChecked(this._itemElements, false); - } - - getCheckedCount() { - return this._itemElements.filter(element => element.checked).length; - } - - getVisibleCount() { - return this._itemElements.filter(e => !e.hidden).length; - } - setAllVisibleChecked(checked: boolean) { - try { - this._shouldFireCheckedEvents = false; + this._elementCheckedEventBufferer.bufferEvents(() => { this._itemElements.forEach(element => { if (!element.hidden && !element.checkboxDisabled) { - // Would fire an event if we didn't have the flag set + // Would fire an event if we didn't beffer the events element.checked = checked; } }); - } finally { - this._shouldFireCheckedEvents = true; - this._fireCheckedEvents(); - } + }); } setElements(inputElements: QuickPickItem[]): void { this._elementDisposable.clear(); + this._lastQueryString = undefined; this._inputElements = inputElements; - const hasCheckbox = this.parent.classList.contains('show-checkboxes'); + this._hasCheckboxes = this.parent.classList.contains('show-checkboxes'); let currentSeparatorElement: QuickPickSeparatorElement | undefined; this._itemElements = new Array(); this._elementTree = inputElements.reduce((result, item, index) => { @@ -1057,7 +1094,7 @@ export class QuickInputTree extends Disposable { } currentSeparatorElement = new QuickPickSeparatorElement( index, - (event: IQuickPickSeparatorButtonEvent) => this.fireSeparatorButtonTriggered(event), + e => this._onSeparatorButtonTriggered.fire(e), item ); element = currentSeparatorElement; @@ -1071,8 +1108,8 @@ export class QuickInputTree extends Disposable { } const qpi = new QuickPickItemElement( index, - hasCheckbox, - (event: IQuickPickItemButtonEvent) => this.fireButtonTriggered(event), + this._hasCheckboxes, + e => this._onButtonTriggered.fire(e), this._elementChecked, item, separator, @@ -1090,32 +1127,7 @@ export class QuickInputTree extends Disposable { return result; }, new Array()); - const elements = new Array>(); - let visibleCount = 0; - for (const element of this._elementTree) { - if (element instanceof QuickPickSeparatorElement) { - elements.push({ - element, - collapsible: false, - collapsed: false, - children: element.children.map(e => ({ - element: e, - collapsible: false, - collapsed: false, - })), - }); - visibleCount += element.children.length + 1; // +1 for the separator itself; - } else { - elements.push({ - element, - collapsible: false, - collapsed: false, - }); - visibleCount++; - } - } - this._tree.setChildren(null, elements); - this._onChangedVisibleCount.fire(visibleCount); + this._setElementsToTree(this._elementTree); // Accessibility hack, unfortunately on next tick // https://github.com/microsoft/vscode/issues/211976 @@ -1125,27 +1137,17 @@ export class QuickInputTree extends Disposable { const parent = focusedElement?.parentNode; if (focusedElement && parent) { const nextSibling = focusedElement.nextSibling; - parent.removeChild(focusedElement); + focusedElement.remove(); parent.insertBefore(focusedElement, nextSibling); } }, 0); } } - getElementsCount(): number { - return this._inputElements.length; - } - - getFocusedElements() { - return this._tree.getFocus() - .filter((e): e is IQuickPickElement => !!e) - .map(e => e.item) - .filter((e): e is IQuickPickItem => !!e); - } - setFocusedElements(items: IQuickPickItem[]) { const elements = items.map(item => this._itemElements.find(e => e.item === item)) - .filter((e): e is QuickPickItemElement => !!e); + .filter((e): e is QuickPickItemElement => !!e) + .filter(e => !e.hidden); this._tree.setFocus(elements); if (items.length > 0) { const focused = this._tree.getFocus()[0]; @@ -1159,12 +1161,6 @@ export class QuickInputTree extends Disposable { return this._tree.getHTMLElement().getAttribute('aria-activedescendant'); } - getSelectedElements() { - return this._tree.getSelection() - .filter((e): e is IQuickPickElement => !!e && !!(e as QuickPickItemElement).item) - .map(e => e.item); - } - setSelectedElements(items: IQuickPickItem[]) { const elements = items.map(item => this._itemElements.find(e => e.item === item)) .filter((e): e is QuickPickItemElement => !!e); @@ -1177,20 +1173,16 @@ export class QuickInputTree extends Disposable { } setCheckedElements(items: IQuickPickItem[]) { - try { - this._shouldFireCheckedEvents = false; + this._elementCheckedEventBufferer.bufferEvents(() => { const checked = new Set(); for (const item of items) { checked.add(item); } for (const element of this._itemElements) { - // Would fire an event if we didn't have the flag set + // Would fire an event if we didn't beffer the events element.checked = checked.has(element.item); } - } finally { - this._shouldFireCheckedEvents = true; - this._fireCheckedEvents(); - } + }); } focus(what: QuickPickFocus): void { @@ -1207,13 +1199,24 @@ export class QuickInputTree extends Disposable { this._tree.scrollTop = 0; this._tree.focusFirst(undefined, (e) => e.element instanceof QuickPickItemElement); break; - case QuickPickFocus.Second: + case QuickPickFocus.Second: { this._tree.scrollTop = 0; - this._tree.setFocus([this._itemElements[1]]); + let isSecondItem = false; + this._tree.focusFirst(undefined, (e) => { + if (!(e.element instanceof QuickPickItemElement)) { + return false; + } + if (isSecondItem) { + return true; + } + isSecondItem = !isSecondItem; + return false; + }); break; + } case QuickPickFocus.Last: this._tree.scrollTop = this._tree.scrollHeight; - this._tree.setFocus([this._itemElements[this._itemElements.length - 1]]); + this._tree.focusLast(undefined, (e) => e.element instanceof QuickPickItemElement); break; case QuickPickFocus.Next: { const prevFocus = this._tree.getFocus(); @@ -1315,7 +1318,7 @@ export class QuickInputTree extends Disposable { // If we didn't move, then we should just move to the end // of the list. this._tree.scrollTop = this._tree.scrollHeight; - this._tree.setFocus([this._itemElements[this._itemElements.length - 1]]); + this._tree.focusLast(undefined, (e) => e.element instanceof QuickPickItemElement); } break; } @@ -1385,6 +1388,7 @@ export class QuickInputTree extends Disposable { } filter(query: string): boolean { + this._lastQueryString = query; if (!(this._sortByLabel || this._matchOnLabel || this._matchOnDescription || this._matchOnDetail)) { this._tree.layout(); return false; @@ -1410,7 +1414,7 @@ export class QuickInputTree extends Disposable { // Filter by value (since we support icons in labels, use $(..) aware fuzzy matching) else { let currentSeparator: IQuickPickSeparator | undefined; - this._elementTree.forEach(element => { + this._itemElements.forEach(element => { let labelHighlights: IMatch[] | undefined; if (this.matchOnLabelMode === 'fuzzy') { labelHighlights = this.matchOnLabel ? matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel)) ?? undefined : undefined; @@ -1441,8 +1445,10 @@ export class QuickInputTree extends Disposable { // we can show the separator unless the list gets sorted by match if (!this.sortByLabel) { - const previous = element.index && this._inputElements[element.index - 1]; - currentSeparator = previous && previous.type === 'separator' ? previous : currentSeparator; + const previous = element.index && this._inputElements[element.index - 1] || undefined; + if (previous?.type === 'separator' && !previous.buttons) { + currentSeparator = previous; + } if (currentSeparator && !element.hidden) { element.separator = currentSeparator; currentSeparator = undefined; @@ -1451,65 +1457,18 @@ export class QuickInputTree extends Disposable { }); } - const shownElements = this._elementTree.filter(element => !element.hidden); - - // Sort by value - if (this.sortByLabel && query) { - const normalizedSearchValue = query.toLowerCase(); - shownElements.sort((a, b) => { - return compareEntries(a, b, normalizedSearchValue); - }); - } - - let currentSeparator: QuickPickSeparatorElement | undefined; - const finalElements = shownElements.reduce((result, element, index) => { - if (element instanceof QuickPickItemElement) { - if (currentSeparator) { - currentSeparator.children.push(element); - } else { - result.push(element); - } - } else if (element instanceof QuickPickSeparatorElement) { - element.children = []; - currentSeparator = element; - result.push(element); - } - return result; - }, new Array()); - - const elements = new Array>(); - for (const element of finalElements) { - if (element instanceof QuickPickSeparatorElement) { - elements.push({ - element, - collapsible: false, - collapsed: false, - children: element.children.map(e => ({ - element: e, - collapsible: false, - collapsed: false, - })), - }); - } else { - elements.push({ - element, - collapsible: false, - collapsed: false, - }); - } - } - this._tree.setChildren(null, elements); + this._setElementsToTree(this._sortByLabel && query + // We don't render any separators if we're sorting so just render the elements + ? this._itemElements + // Render the full tree + : this._elementTree + ); this._tree.layout(); - - this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked()); - this._onChangedVisibleCount.fire(shownElements.length); - return true; } toggleCheckbox() { - try { - this._shouldFireCheckedEvents = false; + this._elementCheckedEventBufferer.bufferEvents(() => { const elements = this._tree.getFocus().filter((e): e is QuickPickItemElement => e instanceof QuickPickItemElement); const allChecked = this._allVisibleChecked(elements); for (const element of elements) { @@ -1518,18 +1477,7 @@ export class QuickInputTree extends Disposable { element.checked = !allChecked; } } - } finally { - this._shouldFireCheckedEvents = true; - this._fireCheckedEvents(); - } - } - - display(display: boolean) { - this._container.style.display = display ? '' : 'none'; - } - - isDisplayed() { - return this._container.style.display !== 'none'; + }); } style(styles: IListStyles) { @@ -1566,6 +1514,31 @@ export class QuickInputTree extends Disposable { //#region private methods + private _setElementsToTree(elements: IQuickPickElement[]) { + const treeElements = new Array>(); + for (const element of elements) { + if (element instanceof QuickPickSeparatorElement) { + treeElements.push({ + element, + collapsible: false, + collapsed: false, + children: element.children.map(e => ({ + element: e, + collapsible: false, + collapsed: false, + })), + }); + } else { + treeElements.push({ + element, + collapsible: false, + collapsed: false, + }); + } + } + this._tree.setChildren(null, treeElements); + } + private _allVisibleChecked(elements: QuickPickItemElement[], whenNoneVisible = true) { for (let i = 0, n = elements.length; i < n; i++) { const element = elements[i]; @@ -1580,21 +1553,13 @@ export class QuickInputTree extends Disposable { return whenNoneVisible; } - private _fireCheckedEvents() { - if (!this._shouldFireCheckedEvents) { - return; - } - this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked()); - this._onChangedCheckedCount.fire(this.getCheckedCount()); - this._onChangedCheckedElements.fire(this.getCheckedElements()); - } - - private fireButtonTriggered(event: IQuickPickItemButtonEvent) { - this._onButtonTriggered.fire(event); - } - - private fireSeparatorButtonTriggered(event: IQuickPickSeparatorButtonEvent) { - this._onSeparatorButtonTriggered.fire(event); + private _updateCheckedObservables() { + transaction((tx) => { + this._allVisibleCheckedObservable.set(this._allVisibleChecked(this._itemElements, false), tx); + const checkedCount = this._itemElements.filter(element => element.checked).length; + this._checkedCountObservable.set(checkedCount, tx); + this._checkedElementsObservable.set(this.getCheckedElements(), tx); + }); } /** diff --git a/patched-vscode/src/vs/platform/quickinput/browser/quickPickPin.ts b/patched-vscode/src/vs/platform/quickinput/browser/quickPickPin.ts index 51c8b25d..7ab79c54 100644 --- a/patched-vscode/src/vs/platform/quickinput/browser/quickPickPin.ts +++ b/patched-vscode/src/vs/platform/quickinput/browser/quickPickPin.ts @@ -8,6 +8,7 @@ import { localize } from 'vs/nls'; import { IQuickPick, IQuickPickItem, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ThemeIcon } from 'vs/base/common/themables'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; const pinButtonClass = ThemeIcon.asClassName(Codicon.pin); const pinnedButtonClass = ThemeIcon.asClassName(Codicon.pinned); @@ -18,30 +19,32 @@ const buttonClasses = [pinButtonClass, pinnedButtonClass]; * be removed if @param filterDupliates has been provided. Pin and pinned button events trigger updates to the underlying storage. * Shows the quickpick once formatted. */ -export async function showWithPinnedItems(storageService: IStorageService, storageKey: string, quickPick: IQuickPick, filterDuplicates?: boolean): Promise { +export function showWithPinnedItems(storageService: IStorageService, storageKey: string, quickPick: IQuickPick, filterDuplicates?: boolean): IDisposable { const itemsWithoutPinned = quickPick.items; let itemsWithPinned = _formatPinnedItems(storageKey, quickPick, storageService, undefined, filterDuplicates); - quickPick.onDidTriggerItemButton(async buttonEvent => { + const disposables = new DisposableStore(); + disposables.add(quickPick.onDidTriggerItemButton(async buttonEvent => { const expectedButton = buttonEvent.button.iconClass && buttonClasses.includes(buttonEvent.button.iconClass); if (expectedButton) { quickPick.items = itemsWithoutPinned; itemsWithPinned = _formatPinnedItems(storageKey, quickPick, storageService, buttonEvent.item, filterDuplicates); quickPick.items = quickPick.value ? itemsWithoutPinned : itemsWithPinned; } - }); - quickPick.onDidChangeValue(async value => { + })); + disposables.add(quickPick.onDidChangeValue(async value => { if (quickPick.items === itemsWithPinned && value) { quickPick.items = itemsWithoutPinned; } else if (quickPick.items === itemsWithoutPinned && !value) { quickPick.items = itemsWithPinned; } - }); + })); quickPick.items = quickPick.value ? itemsWithoutPinned : itemsWithPinned; quickPick.show(); + return disposables; } -function _formatPinnedItems(storageKey: string, quickPick: IQuickPick, storageService: IStorageService, changedItem?: IQuickPickItem, filterDuplicates?: boolean): QuickPickItem[] { +function _formatPinnedItems(storageKey: string, quickPick: IQuickPick, storageService: IStorageService, changedItem?: IQuickPickItem, filterDuplicates?: boolean): QuickPickItem[] { const formattedItems: QuickPickItem[] = []; let pinnedItems; if (changedItem) { diff --git a/patched-vscode/src/vs/platform/quickinput/common/quickAccess.ts b/patched-vscode/src/vs/platform/quickinput/common/quickAccess.ts index 4fd3faf3..98757132 100644 --- a/patched-vscode/src/vs/platform/quickinput/common/quickAccess.ts +++ b/patched-vscode/src/vs/platform/quickinput/common/quickAccess.ts @@ -30,7 +30,6 @@ export interface IQuickAccessProviderRunOptions { export interface AnythingQuickAccessProviderRunOptions extends IQuickAccessProviderRunOptions { readonly includeHelp?: boolean; readonly filter?: (item: unknown) => boolean; - readonly includeSymbols?: boolean; /** * @deprecated - temporary for Dynamic Chat Variables (see usage) until it has built-in UX for file picking * Useful for adding items to the top of the list that might contain actions. @@ -130,7 +129,7 @@ export interface IQuickAccessProvider { * @return a disposable that will automatically be disposed when the picker * closes or is replaced by another picker. */ - provide(picker: IQuickPick, token: CancellationToken, options?: IQuickAccessProviderRunOptions): IDisposable; + provide(picker: IQuickPick, token: CancellationToken, options?: IQuickAccessProviderRunOptions): IDisposable; } export interface IQuickAccessProviderHelp { diff --git a/patched-vscode/src/vs/platform/quickinput/common/quickInput.ts b/patched-vscode/src/vs/platform/quickinput/common/quickInput.ts index f893e58f..a3b26fd5 100644 --- a/patched-vscode/src/vs/platform/quickinput/common/quickInput.ts +++ b/patched-vscode/src/vs/platform/quickinput/common/quickInput.ts @@ -81,6 +81,11 @@ export interface IPickOptions { */ title?: string; + /** + * the value to prefill in the input box + */ + value?: string; + /** * an optional string to show as placeholder in the input box to guide the user what she picks on */ @@ -418,7 +423,7 @@ export enum QuickPickFocus { /** * Represents a quick pick control that allows the user to select an item from a list of options. */ -export interface IQuickPick extends IQuickInput { +export interface IQuickPick extends IQuickInput { /** * The type of the quick input. @@ -505,7 +510,7 @@ export interface IQuickPick extends IQuickInput { /** * The items to be displayed in the quick pick. */ - items: ReadonlyArray; + items: O extends { useSeparators: true } ? ReadonlyArray : ReadonlyArray; /** * Whether multiple items can be selected. If so, checkboxes will be rendered. @@ -705,6 +710,18 @@ export interface IInputBox extends IQuickInput { severity: Severity; } +export enum QuickInputButtonLocation { + /** + * In the title bar. + */ + Title = 1, + + /** + * To the right of the input box. + */ + Inline = 2 +} + /** * Represents a button in the quick input UI. */ @@ -728,6 +745,11 @@ export interface IQuickInputButton { * By default, buttons are only visible when hovering over them with the mouse. */ alwaysVisible?: boolean; + /** + * Where the button should be rendered. The default is {@link QuickInputButtonLocation.Title}. + * @note This property is ignored if the button was added to a QuickPickItem. + */ + location?: QuickInputButtonLocation; } /** @@ -854,7 +876,8 @@ export interface IQuickInputService { /** * Provides raw access to the quick pick controller. */ - createQuickPick(): IQuickPick; + createQuickPick(options: { useSeparators: true }): IQuickPick; + createQuickPick(options?: { useSeparators: boolean }): IQuickPick; /** * Provides raw access to the input box controller. diff --git a/patched-vscode/src/vs/platform/quickinput/test/browser/quickinput.test.ts b/patched-vscode/src/vs/platform/quickinput/test/browser/quickinput.test.ts index 8e8fc694..328c43d7 100644 --- a/patched-vscode/src/vs/platform/quickinput/test/browser/quickinput.test.ts +++ b/patched-vscode/src/vs/platform/quickinput/test/browser/quickinput.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { unthemedInboxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { unthemedButtonStyles } from 'vs/base/browser/ui/button/button'; import { unthemedListStyles } from 'vs/base/browser/ui/list/listWidget'; @@ -57,7 +57,7 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 setup(() => { const fixture = document.createElement('div'); mainWindow.document.body.appendChild(fixture); - store.add(toDisposable(() => mainWindow.document.body.removeChild(fixture))); + store.add(toDisposable(() => fixture.remove())); const instantiationService = new TestInstantiationService(); diff --git a/patched-vscode/src/vs/platform/registry/test/common/platform.test.ts b/patched-vscode/src/vs/platform/registry/test/common/platform.test.ts index 3fe9188f..8ec965d5 100644 --- a/patched-vscode/src/vs/platform/registry/test/common/platform.test.ts +++ b/patched-vscode/src/vs/platform/registry/test/common/platform.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { isFunction } from 'vs/base/common/types'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/patched-vscode/src/vs/platform/remote/browser/browserSocketFactory.ts b/patched-vscode/src/vs/platform/remote/browser/browserSocketFactory.ts index f44c9205..e8d40cb8 100644 --- a/patched-vscode/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/patched-vscode/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -281,7 +281,6 @@ export class BrowserSocketFactory implements ISocketFactory { return new Promise((resolve, reject) => { const webSocketSchema = (/^https:/.test(mainWindow.location.href) ? 'wss' : 'ws'); - path = (mainWindow.location.pathname + "/" + path).replace(/\/\/+/g, "/") const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel); const errorListener = socket.onError(reject); socket.onOpen(() => { diff --git a/patched-vscode/src/vs/platform/remote/common/remoteExtensionsScanner.ts b/patched-vscode/src/vs/platform/remote/common/remoteExtensionsScanner.ts index 792c0352..a26de9d1 100644 --- a/patched-vscode/src/vs/platform/remote/common/remoteExtensionsScanner.ts +++ b/patched-vscode/src/vs/platform/remote/common/remoteExtensionsScanner.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -16,5 +15,4 @@ export interface IRemoteExtensionsScannerService { whenExtensionsReady(): Promise; scanExtensions(): Promise; - scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise; } diff --git a/patched-vscode/src/vs/platform/remote/node/wsl.ts b/patched-vscode/src/vs/platform/remote/node/wsl.ts index 3ba33f74..637ccff4 100644 --- a/patched-vscode/src/vs/platform/remote/node/wsl.ts +++ b/patched-vscode/src/vs/platform/remote/node/wsl.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import * as os from 'os'; import * as cp from 'child_process'; -import { Promises } from 'vs/base/node/pfs'; import * as path from 'path'; let hasWSLFeaturePromise: Promise | undefined; @@ -26,14 +26,18 @@ async function testWSLFeatureInstalled(): Promise { const wslExePath = getWSLExecutablePath(); if (wslExePath) { return new Promise(s => { - cp.execFile(wslExePath, ['--status'], err => s(!err)); + try { + cp.execFile(wslExePath, ['--status'], err => s(!err)); + } catch (e) { + s(false); + } }); } } else { const dllPath = getLxssManagerDllPath(); if (dllPath) { try { - if ((await Promises.stat(dllPath)).isFile()) { + if ((await fs.promises.stat(dllPath)).isFile()) { return true; } } catch (e) { diff --git a/patched-vscode/src/vs/platform/remote/test/common/remoteHosts.test.ts b/patched-vscode/src/vs/platform/remote/test/common/remoteHosts.test.ts index cfbc105f..ed564551 100644 --- a/patched-vscode/src/vs/platform/remote/test/common/remoteHosts.test.ts +++ b/patched-vscode/src/vs/platform/remote/test/common/remoteHosts.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { parseAuthorityWithOptionalPort, parseAuthorityWithPort } from 'vs/platform/remote/common/remoteHosts'; diff --git a/patched-vscode/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts b/patched-vscode/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts index a5bacbb8..67790a80 100644 --- a/patched-vscode/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts +++ b/patched-vscode/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; diff --git a/patched-vscode/src/vs/platform/request/browser/requestService.ts b/patched-vscode/src/vs/platform/request/browser/requestService.ts index f0fc3d68..78c34491 100644 --- a/patched-vscode/src/vs/platform/request/browser/requestService.ts +++ b/patched-vscode/src/vs/platform/request/browser/requestService.ts @@ -8,7 +8,7 @@ import { request } from 'vs/base/parts/request/browser/request'; import { IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILoggerService } from 'vs/platform/log/common/log'; -import { AbstractRequestService, IRequestService } from 'vs/platform/request/common/request'; +import { AbstractRequestService, AuthInfo, Credentials, IRequestService } from 'vs/platform/request/common/request'; /** * This service exposes the `request` API, while using the global @@ -36,6 +36,14 @@ export class RequestService extends AbstractRequestService implements IRequestSe return undefined; // not implemented in the web } + async lookupAuthorization(authInfo: AuthInfo): Promise { + return undefined; // not implemented in the web + } + + async lookupKerberosAuthorization(url: string): Promise { + return undefined; // not implemented in the web + } + async loadCertificates(): Promise { return []; // not implemented in the web } diff --git a/patched-vscode/src/vs/platform/request/common/request.ts b/patched-vscode/src/vs/platform/request/common/request.ts index 289ad674..fb732b7f 100644 --- a/patched-vscode/src/vs/platform/request/common/request.ts +++ b/patched-vscode/src/vs/platform/request/common/request.ts @@ -16,12 +16,28 @@ import { Registry } from 'vs/platform/registry/common/platform'; export const IRequestService = createDecorator('requestService'); +export interface AuthInfo { + isProxy: boolean; + scheme: string; + host: string; + port: number; + realm: string; + attempt: number; +} + +export interface Credentials { + username: string; + password: string; +} + export interface IRequestService { readonly _serviceBrand: undefined; request(options: IRequestOptions, token: CancellationToken): Promise; resolveProxy(url: string): Promise; + lookupAuthorization(authInfo: AuthInfo): Promise; + lookupKerberosAuthorization(url: string): Promise; loadCertificates(): Promise; } @@ -80,6 +96,8 @@ export abstract class AbstractRequestService extends Disposable implements IRequ abstract request(options: IRequestOptions, token: CancellationToken): Promise; abstract resolveProxy(url: string): Promise; + abstract lookupAuthorization(authInfo: AuthInfo): Promise; + abstract lookupKerberosAuthorization(url: string): Promise; abstract loadCertificates(): Promise; } @@ -155,6 +173,12 @@ function registerProxyConfigurations(scope: ConfigurationScope): void { markdownDescription: localize('proxyKerberosServicePrincipal', "Overrides the principal service name for Kerberos authentication with the HTTP proxy. A default based on the proxy hostname is used when this is not set."), restricted: true }, + 'http.noProxy': { + type: 'array', + items: { type: 'string' }, + markdownDescription: localize('noProxy', "Specifies domain names for which proxy settings should be ignored for HTTP/HTTPS requests."), + restricted: true + }, 'http.proxyAuthorization': { type: ['null', 'string'], default: null, diff --git a/patched-vscode/src/vs/platform/request/common/requestIpc.ts b/patched-vscode/src/vs/platform/request/common/requestIpc.ts index 421106eb..f6854f5e 100644 --- a/patched-vscode/src/vs/platform/request/common/requestIpc.ts +++ b/patched-vscode/src/vs/platform/request/common/requestIpc.ts @@ -8,7 +8,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; -import { IRequestService } from 'vs/platform/request/common/request'; +import { AuthInfo, Credentials, IRequestService } from 'vs/platform/request/common/request'; type RequestResponse = [ { @@ -34,6 +34,8 @@ export class RequestChannel implements IServerChannel { return [{ statusCode: res.statusCode, headers: res.headers }, buffer]; }); case 'resolveProxy': return this.service.resolveProxy(args[0]); + case 'lookupAuthorization': return this.service.lookupAuthorization(args[0]); + case 'lookupKerberosAuthorization': return this.service.lookupKerberosAuthorization(args[0]); case 'loadCertificates': return this.service.loadCertificates(); } throw new Error('Invalid call'); @@ -55,6 +57,14 @@ export class RequestChannelClient implements IRequestService { return this.channel.call('resolveProxy', [url]); } + async lookupAuthorization(authInfo: AuthInfo): Promise { + return this.channel.call<{ username: string; password: string } | undefined>('lookupAuthorization', [authInfo]); + } + + async lookupKerberosAuthorization(url: string): Promise { + return this.channel.call('lookupKerberosAuthorization', [url]); + } + async loadCertificates(): Promise { return this.channel.call('loadCertificates'); } diff --git a/patched-vscode/src/vs/platform/request/node/proxy.ts b/patched-vscode/src/vs/platform/request/node/proxy.ts index db448e06..570854b2 100644 --- a/patched-vscode/src/vs/platform/request/node/proxy.ts +++ b/patched-vscode/src/vs/platform/request/node/proxy.ts @@ -44,7 +44,21 @@ export async function getProxyAgent(rawRequestURL: string, env: typeof process.e rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true, }; - return requestURL.protocol === 'http:' - ? new (await import('http-proxy-agent')).HttpProxyAgent(proxyURL, opts) - : new (await import('https-proxy-agent')).HttpsProxyAgent(proxyURL, opts); + if (requestURL.protocol === 'http:') { + // ESM-comment-begin + const mod = await import('http-proxy-agent'); + // ESM-comment-end + // ESM-uncomment-begin + // const { default: mod } = await import('http-proxy-agent'); + // ESM-uncomment-end + return new mod.HttpProxyAgent(proxyURL, opts); + } else { + // ESM-comment-begin + const mod = await import('https-proxy-agent'); + // ESM-comment-end + // ESM-uncomment-begin + // const { default: mod } = await import('https-proxy-agent'); + // ESM-uncomment-end + return new mod.HttpsProxyAgent(proxyURL, opts); + } } diff --git a/patched-vscode/src/vs/platform/request/node/requestService.ts b/patched-vscode/src/vs/platform/request/node/requestService.ts index 23f8f0d4..f3c57ebb 100644 --- a/patched-vscode/src/vs/platform/request/node/requestService.ts +++ b/patched-vscode/src/vs/platform/request/node/requestService.ts @@ -17,7 +17,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; -import { AbstractRequestService, IRequestService } from 'vs/platform/request/common/request'; +import { AbstractRequestService, AuthInfo, Credentials, IRequestService } from 'vs/platform/request/common/request'; import { Agent, getProxyAgent } from 'vs/platform/request/node/proxy'; import { createGunzip } from 'zlib'; @@ -110,6 +110,26 @@ export class RequestService extends AbstractRequestService implements IRequestSe return undefined; // currently not implemented in node } + async lookupAuthorization(authInfo: AuthInfo): Promise { + return undefined; // currently not implemented in node + } + + async lookupKerberosAuthorization(urlStr: string): Promise { + try { + const kerberos = await import('kerberos'); + const url = new URL(urlStr); + const spn = this.configurationService.getValue('http.proxyKerberosServicePrincipal') + || (process.platform === 'win32' ? `HTTP/${url.hostname}` : `HTTP@${url.hostname}`); + this.logService.debug('RequestService#lookupKerberosAuthorization Kerberos authentication lookup', `proxyURL:${url}`, `spn:${spn}`); + const client = await kerberos.initializeClient(spn); + const response = await client.step(''); + return 'Negotiate ' + response; + } catch (err) { + this.logService.debug('RequestService#lookupKerberosAuthorization Kerberos authentication failed', err); + return undefined; + } + } + async loadCertificates(): Promise { const proxyAgent = await import('@vscode/proxy-agent'); return proxyAgent.loadSystemCertificates({ log: this.logService }); @@ -165,7 +185,7 @@ export async function nodeRequest(options: NodeRequestOptions, token: Cancellati stream = res.pipe(createGunzip()); } - resolve({ res, stream: streamToBufferReadableStream(stream) } as IRequestContext); + resolve({ res, stream: streamToBufferReadableStream(stream) } satisfies IRequestContext); } }); diff --git a/patched-vscode/src/vs/platform/secrets/test/common/secrets.test.ts b/patched-vscode/src/vs/platform/secrets/test/common/secrets.test.ts index b3a048af..50def3a9 100644 --- a/patched-vscode/src/vs/platform/secrets/test/common/secrets.test.ts +++ b/patched-vscode/src/vs/platform/secrets/test/common/secrets.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEncryptionService, KnownStorageProvider } from 'vs/platform/encryption/common/encryptionService'; diff --git a/patched-vscode/src/vs/platform/sign/browser/signService.ts b/patched-vscode/src/vs/platform/sign/browser/signService.ts index a9d699bf..b6b08ebf 100644 --- a/patched-vscode/src/vs/platform/sign/browser/signService.ts +++ b/patched-vscode/src/vs/platform/sign/browser/signService.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { importAMDNodeModule, resolveAmdNodeModulePath } from 'vs/amdX'; import { WindowIntervalTimer } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; +import { isESM } from 'vs/base/common/amd'; import { memoize } from 'vs/base/common/decorators'; import { FileAccess } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -62,7 +64,7 @@ export class SignService extends AbstractSignService implements ISignService { let [wasm] = await Promise.all([ this.getWasmBytes(), new Promise((resolve, reject) => { - require(['vsda'], resolve, reject); + importAMDNodeModule('vsda', 'rust/web/vsda.js').then(() => resolve(), reject); // todo@connor4312: there seems to be a bug(?) in vscode-loader with // require() not resolving in web once the script loads, so check manually @@ -74,7 +76,6 @@ export class SignService extends AbstractSignService implements ISignService { }).finally(() => checkInterval.dispose()), ]); - const keyBytes = new TextEncoder().encode(this.productService.serverLicense?.join('\n') || ''); for (let i = 0; i + STEP_SIZE < keyBytes.length; i += STEP_SIZE) { const key = await crypto.subtle.importKey('raw', keyBytes.slice(i + IV_SIZE, i + IV_SIZE + KEY_SIZE), { name: 'AES-CBC' }, false, ['decrypt']); @@ -87,7 +88,10 @@ export class SignService extends AbstractSignService implements ISignService { } private async getWasmBytes(): Promise { - const response = await fetch(FileAccess.asBrowserUri('vsda/../vsda_bg.wasm').toString(true)); + const url = isESM + ? resolveAmdNodeModulePath('vsda', 'rust/web/vsda_bg.wasm') + : FileAccess.asBrowserUri('vsda/../vsda_bg.wasm').toString(true); + const response = await fetch(url); if (!response.ok) { throw new Error('error loading vsda'); } diff --git a/patched-vscode/src/vs/platform/sign/node/signService.ts b/patched-vscode/src/vs/platform/sign/node/signService.ts index d07ba9cf..d1c7b132 100644 --- a/patched-vscode/src/vs/platform/sign/node/signService.ts +++ b/patched-vscode/src/vs/platform/sign/node/signService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { importAMDNodeModule } from 'vs/amdX'; import { AbstractSignService, IVsdaValidator } from 'vs/platform/sign/common/abstractSignService'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -28,7 +29,16 @@ export class SignService extends AbstractSignService implements ISignService { return this.vsda().then(vsda => new vsda.signer().sign(arg)); } - private vsda(): Promise { - return new Promise((resolve, reject) => require(['vsda'], resolve, reject)); + private async vsda(): Promise { + // ESM-uncomment-begin + // if (typeof importAMDNodeModule === 'function') { /* fixes unused import, remove me */} + // const mod = 'vsda'; + // const { default: vsda } = await import(mod); + // return vsda; + // ESM-uncomment-end + + // ESM-comment-begin + return importAMDNodeModule('vsda', 'index.js'); + // ESM-comment-end } } diff --git a/patched-vscode/src/vs/platform/state/test/node/state.test.ts b/patched-vscode/src/vs/platform/state/test/node/state.test.ts index 493d78d0..4674f20a 100644 --- a/patched-vscode/src/vs/platform/state/test/node/state.test.ts +++ b/patched-vscode/src/vs/platform/state/test/node/state.test.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import { readFileSync } from 'fs'; +import assert from 'assert'; +import { readFileSync, promises } from 'fs'; import { tmpdir } from 'os'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; @@ -37,7 +37,7 @@ flakySuite('StateService', () => { diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(logService)); disposables.add(fileService.registerProvider(Schemas.file, diskFileSystemProvider)); - return Promises.mkdir(testDir, { recursive: true }); + return promises.mkdir(testDir, { recursive: true }); }); teardown(() => { diff --git a/patched-vscode/src/vs/platform/storage/electron-main/storageMain.ts b/patched-vscode/src/vs/platform/storage/electron-main/storageMain.ts index c110f280..8d6d1b53 100644 --- a/patched-vscode/src/vs/platform/storage/electron-main/storageMain.ts +++ b/patched-vscode/src/vs/platform/storage/electron-main/storageMain.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { top } from 'vs/base/common/arrays'; import { DeferredPromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -403,7 +404,7 @@ export class WorkspaceStorageMain extends BaseStorageMain { } // Ensure storage folder exists - await Promises.mkdir(workspaceStorageFolderPath, { recursive: true }); + await fs.promises.mkdir(workspaceStorageFolderPath, { recursive: true }); // Write metadata into folder (but do not await) this.ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath); diff --git a/patched-vscode/src/vs/platform/telemetry/common/1dsAppender.ts b/patched-vscode/src/vs/platform/telemetry/common/1dsAppender.ts index 44ab1c65..6585e563 100644 --- a/patched-vscode/src/vs/platform/telemetry/common/1dsAppender.ts +++ b/patched-vscode/src/vs/platform/telemetry/common/1dsAppender.ts @@ -8,6 +8,7 @@ import type { IChannelConfiguration, IXHROverride, PostChannel } from '@microsof import { importAMDNodeModule } from 'vs/amdX'; import { onUnexpectedError } from 'vs/base/common/errors'; import { mixin } from 'vs/base/common/objects'; +import { isWeb } from 'vs/base/common/platform'; import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; // Interface type which is a subset of @microsoft/1ds-core-js AppInsightsCore. @@ -18,12 +19,22 @@ export interface IAppInsightsCore { unload(isAsync: boolean, unloadComplete: (unloadState: ITelemetryUnloadState) => void): void; } -const endpointUrl = 'https://0.0.0.0/OneCollector/1.0'; -const endpointHealthUrl = 'https://0.0.0.0/ping'; +const endpointUrl = 'https://mobile.events.data.microsoft.com/OneCollector/1.0'; +const endpointHealthUrl = 'https://mobile.events.data.microsoft.com/ping'; async function getClient(instrumentationKey: string, addInternalFlag?: boolean, xhrOverride?: IXHROverride): Promise { + // ESM-comment-begin + if (isWeb) { /* fix the import warning */ } const oneDs = await importAMDNodeModule('@microsoft/1ds-core-js', 'dist/ms.core.js'); const postPlugin = await importAMDNodeModule('@microsoft/1ds-post-js', 'dist/ms.post.js'); + // ESM-comment-end + // ESM-uncomment-begin + // // eslint-disable-next-line local/code-amd-node-module + // const oneDs = isWeb ? await importAMDNodeModule('@microsoft/1ds-core-js', 'bundle/ms.core.min.js') : await import('@microsoft/1ds-core-js'); + // // eslint-disable-next-line local/code-amd-node-module + // const postPlugin = isWeb ? await importAMDNodeModule('@microsoft/1ds-post-js', 'bundle/ms.post.min.js'): await import('@microsoft/1ds-post-js'); + // ESM-uncomment-end + const appInsightsCore = new oneDs.AppInsightsCore(); const collectorChannelPlugin: PostChannel = new postPlugin.PostChannel(); // Configure the app insights core to send to collector++ and disable logging of debug info diff --git a/patched-vscode/src/vs/platform/telemetry/common/telemetryService.ts b/patched-vscode/src/vs/platform/telemetry/common/telemetryService.ts index e2e7fb2c..245bb41b 100644 --- a/patched-vscode/src/vs/platform/telemetry/common/telemetryService.ts +++ b/patched-vscode/src/vs/platform/telemetry/common/telemetryService.ts @@ -208,7 +208,7 @@ Registry.as(Extensions.Configuration).registerConfigurat 'properties': { [TELEMETRY_SETTING_ID]: { 'type': 'string', - 'enum': [TelemetryConfiguration.OFF], + 'enum': [TelemetryConfiguration.ON, TelemetryConfiguration.ERROR, TelemetryConfiguration.CRASH, TelemetryConfiguration.OFF], 'enumDescriptions': [ localize('telemetry.telemetryLevel.default', "Sends usage data, errors, and crash reports."), localize('telemetry.telemetryLevel.error', "Sends general error telemetry and crash reports."), @@ -216,7 +216,7 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('telemetry.telemetryLevel.off', "Disables all product telemetry.") ], 'markdownDescription': getTelemetryLevelSettingDescription(), - 'default': TelemetryConfiguration.OFF, + 'default': TelemetryConfiguration.ON, 'restricted': true, 'scope': ConfigurationScope.APPLICATION, 'tags': ['usesOnlineServices', 'telemetry'] diff --git a/patched-vscode/src/vs/platform/telemetry/node/telemetry.ts b/patched-vscode/src/vs/platform/telemetry/node/telemetry.ts index 72f87ddd..d1770958 100644 --- a/patched-vscode/src/vs/platform/telemetry/node/telemetry.ts +++ b/patched-vscode/src/vs/platform/telemetry/node/telemetry.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { join } from 'vs/base/common/path'; import { Promises } from 'vs/base/node/pfs'; @@ -21,7 +22,7 @@ export async function buildTelemetryMessage(appRoot: string, extensionsPath?: st const files = await Promises.readdir(extensionsPath); for (const file of files) { try { - const fileStat = await Promises.stat(join(extensionsPath, file)); + const fileStat = await fs.promises.stat(join(extensionsPath, file)); if (fileStat.isDirectory()) { dirs.push(file); } @@ -39,15 +40,15 @@ export async function buildTelemetryMessage(appRoot: string, extensionsPath?: st } for (const folder of telemetryJsonFolders) { - const contents = (await Promises.readFile(join(extensionsPath, folder, 'telemetry.json'))).toString(); + const contents = (await fs.promises.readFile(join(extensionsPath, folder, 'telemetry.json'))).toString(); mergeTelemetry(contents, folder); } } - let contents = (await Promises.readFile(join(appRoot, 'telemetry-core.json'))).toString(); + let contents = (await fs.promises.readFile(join(appRoot, 'telemetry-core.json'))).toString(); mergeTelemetry(contents, 'vscode-core'); - contents = (await Promises.readFile(join(appRoot, 'telemetry-extensions.json'))).toString(); + contents = (await fs.promises.readFile(join(appRoot, 'telemetry-extensions.json'))).toString(); mergeTelemetry(contents, 'vscode-extensions'); return JSON.stringify(mergedTelemetry, null, 4); diff --git a/patched-vscode/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts b/patched-vscode/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts index 2ee6f9bc..33a22c39 100644 --- a/patched-vscode/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts +++ b/patched-vscode/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import type { ITelemetryItem, ITelemetryUnloadState } from '@microsoft/1ds-core-js'; -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender'; import { IAppInsightsCore } from 'vs/platform/telemetry/common/1dsAppender'; diff --git a/patched-vscode/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/patched-vscode/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index 8524e9a6..bc75667e 100644 --- a/patched-vscode/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/patched-vscode/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -2,9 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; -import * as sinonTest from 'sinon-test'; +import sinonTest from 'sinon-test'; import { mainWindow } from 'vs/base/browser/window'; import * as Errors from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; diff --git a/patched-vscode/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts b/patched-vscode/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts index 8de29c1e..2fe9b7a6 100644 --- a/patched-vscode/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts +++ b/patched-vscode/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.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 * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; diff --git a/patched-vscode/src/vs/platform/terminal/common/capabilities/capabilities.ts b/patched-vscode/src/vs/platform/terminal/common/capabilities/capabilities.ts index 426de4a8..5cd3d9be 100644 --- a/patched-vscode/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/patched-vscode/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import type { IPromptInputModel } from 'vs/platform/terminal/common/capabilities/commandDetection/promptInputModel'; +import type { IPromptInputModel, ISerializedPromptInputModel } from 'vs/platform/terminal/common/capabilities/commandDetection/promptInputModel'; import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetection/terminalCommand'; import { ITerminalOutputMatch, ITerminalOutputMatcher } from 'vs/platform/terminal/common/terminal'; import { ReplayEntry } from 'vs/platform/terminal/common/terminalProcess'; @@ -169,11 +169,6 @@ export interface ICommandDetectionCapability { readonly executingCommandObject: ITerminalCommand | undefined; /** The current cwd at the cursor's position. */ readonly cwd: string | undefined; - /** - * Whether a command is currently being input. If the a command is current not being input or - * the state cannot reliably be detected the fallback of undefined will be used. - */ - readonly hasInput: boolean | undefined; readonly currentCommand: ICurrentPartialCommand | undefined; readonly onCommandStarted: Event; readonly onCommandFinished: Event; @@ -306,6 +301,7 @@ export interface IMarkProperties { export interface ISerializedCommandDetectionCapability { isWindowsPty: boolean; commands: ISerializedTerminalCommand[]; + promptInputModel: ISerializedPromptInputModel | undefined; } export interface IPtyHostProcessReplayEvent { events: ReplayEntry[]; diff --git a/patched-vscode/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts b/patched-vscode/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts index 0f1c53ef..9e7c183b 100644 --- a/patched-vscode/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts +++ b/patched-vscode/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts @@ -23,7 +23,7 @@ const enum PromptInputState { * A model of the prompt input state using shell integration and analyzing the terminal buffer. This * may not be 100% accurate but provides a best guess. */ -export interface IPromptInputModel { +export interface IPromptInputModel extends IPromptInputModelState { readonly onDidStartInput: Event; readonly onDidChangeInput: Event; readonly onDidFinishInput: Event; @@ -32,10 +32,6 @@ export interface IPromptInputModel { */ readonly onDidInterrupt: Event; - readonly value: string; - readonly cursorIndex: number; - readonly ghostTextIndex: number; - /** * Gets the prompt input as a user-friendly string where `|` is the cursor position and `[` and * `]` wrap any ghost text. @@ -44,11 +40,37 @@ export interface IPromptInputModel { } export interface IPromptInputModelState { + /** + * The full prompt input include ghost text. + */ readonly value: string; + /** + * The prompt input up to the cursor index, this will always exclude the ghost text. + */ + readonly prefix: string; + /** + * The prompt input from the cursor to the end, this _does not_ include ghost text. + */ + readonly suffix: string; + /** + * The index of the cursor in {@link value}. + */ readonly cursorIndex: number; + /** + * The index of the start of ghost text in {@link value}. This is -1 when there is no ghost + * text. + */ readonly ghostTextIndex: number; } +export interface ISerializedPromptInputModel { + readonly modelState: IPromptInputModelState; + readonly commandStartX: number; + readonly lastPromptLine: string | undefined; + readonly continuationPrompt: string | undefined; + readonly lastUserInput: string; +} + export class PromptInputModel extends Disposable implements IPromptInputModel { private _state: PromptInputState = PromptInputState.Unknown; @@ -61,6 +83,8 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { private _value: string = ''; get value() { return this._value; } + get prefix() { return this._value.substring(0, this._cursorIndex); } + get suffix() { return this._value.substring(this._cursorIndex, this._ghostTextIndex === -1 ? undefined : this._ghostTextIndex); } private _cursorIndex: number = 0; get cursorIndex() { return this._cursorIndex; } @@ -142,6 +166,26 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { return result; } + serialize(): ISerializedPromptInputModel { + return { + modelState: this._createStateObject(), + commandStartX: this._commandStartX, + lastPromptLine: this._lastPromptLine, + continuationPrompt: this._continuationPrompt, + lastUserInput: this._lastUserInput + }; + } + + deserialize(serialized: ISerializedPromptInputModel): void { + this._value = serialized.modelState.value; + this._cursorIndex = serialized.modelState.cursorIndex; + this._ghostTextIndex = serialized.modelState.ghostTextIndex; + this._commandStartX = serialized.commandStartX; + this._lastPromptLine = serialized.lastPromptLine; + this._continuationPrompt = serialized.continuationPrompt; + this._lastUserInput = serialized.lastUserInput; + } + private _handleCommandStart(command: { marker: IMarker }) { if (this._state === PromptInputState.Input) { return; @@ -220,8 +264,13 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { const absoluteCursorY = buffer.baseY + buffer.cursorY; let value = commandLine; - let cursorIndex = absoluteCursorY === commandStartY ? this._getRelativeCursorIndex(this._commandStartX, buffer, line) : commandLine.trimEnd().length + 1; let ghostTextIndex = -1; + let cursorIndex: number; + if (absoluteCursorY === commandStartY) { + cursorIndex = this._getRelativeCursorIndex(this._commandStartX, buffer, line); + } else { + cursorIndex = commandLine.trimEnd().length; + } // Detect ghost text by looking for italic or dim text in or after the cursor and // non-italic/dim text in the cell closest non-whitespace cell before the cursor @@ -235,15 +284,25 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { line = buffer.getLine(y); const lineText = line?.translateToString(true); if (lineText && line) { + // Check if the line wrapped without a new line (continuation) + if (line.isWrapped) { + value += lineText; + const relativeCursorIndex = this._getRelativeCursorIndex(0, buffer, line); + if (absoluteCursorY === y) { + cursorIndex += relativeCursorIndex; + } else { + cursorIndex += lineText.length; + } + } // Verify continuation prompt if we have it, if this line doesn't have it then the - // user likely just pressed enter - if (this._continuationPrompt === undefined || this._lineContainsContinuationPrompt(lineText)) { + // user likely just pressed enter. + else if (this._continuationPrompt === undefined || this._lineContainsContinuationPrompt(lineText)) { const trimmedLineText = this._trimContinuationPrompt(lineText); value += `\n${trimmedLineText}`; if (absoluteCursorY === y) { const continuationCellWidth = this._getContinuationPromptCellWidth(line, lineText); const relativeCursorIndex = this._getRelativeCursorIndex(continuationCellWidth, buffer, line); - cursorIndex += relativeCursorIndex; + cursorIndex += relativeCursorIndex + 1; } else { cursorIndex += trimmedLineText.length + 1; } @@ -419,6 +478,8 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { private _createStateObject(): IPromptInputModelState { return Object.freeze({ value: this._value, + prefix: this.prefix, + suffix: this.suffix, cursorIndex: this._cursorIndex, ghostTextIndex: this._ghostTextIndex }); diff --git a/patched-vscode/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/patched-vscode/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 8df3d3b8..b48cf630 100644 --- a/patched-vscode/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/patched-vscode/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -56,23 +56,6 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } get cwd(): string | undefined { return this._cwd; } get promptTerminator(): string | undefined { return this._promptTerminator; } - private get _isInputting(): boolean { - return !!(this._currentCommand.commandStartMarker && !this._currentCommand.commandExecutedMarker); - } - - get hasInput(): boolean | undefined { - if (!this._isInputting || !this._currentCommand?.commandStartMarker) { - return undefined; - } - if (this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY === this._currentCommand.commandStartMarker?.line) { - const line = this._terminal.buffer.active.getLine(this._terminal.buffer.active.cursorY)?.translateToString(true, this._currentCommand.commandStartX); - if (line === undefined) { - return undefined; - } - return line.length > 0; - } - return true; - } private readonly _onCommandStarted = this._register(new Emitter()); readonly onCommandStarted = this._onCommandStarted.event; @@ -166,6 +149,9 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe @debounce(500) private _handleCursorMove() { + if (this._store.isDisposed) { + return; + } // Early versions of conpty do not have real support for an alt buffer, in addition certain // commands such as tsc watch will write to the top of the normal buffer. The following // checks when the cursor has moved while the normal buffer is empty and if it is above the @@ -425,7 +411,8 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } return { isWindowsPty: this._ptyHeuristics.value instanceof WindowsPtyHeuristics, - commands + commands, + promptInputModel: this._promptInputModel.serialize(), }; } @@ -460,6 +447,9 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); this._onCommandFinished.fire(newCommand); } + if (serialized.promptInputModel) { + this._promptInputModel.deserialize(serialized.promptInputModel); + } } } diff --git a/patched-vscode/src/vs/platform/terminal/common/terminal.ts b/patched-vscode/src/vs/platform/terminal/common/terminal.ts index f8e502eb..62a129b8 100644 --- a/patched-vscode/src/vs/platform/terminal/common/terminal.ts +++ b/patched-vscode/src/vs/platform/terminal/common/terminal.ts @@ -90,6 +90,7 @@ export const enum TerminalSettingId { EnvWindows = 'terminal.integrated.env.windows', EnvironmentChangesIndicator = 'terminal.integrated.environmentChangesIndicator', EnvironmentChangesRelaunch = 'terminal.integrated.environmentChangesRelaunch', + ExperimentalWindowsUseConptyDll = 'terminal.integrated.experimental.windowsUseConptyDll', ShowExitAlert = 'terminal.integrated.showExitAlert', SplitCwd = 'terminal.integrated.splitCwd', WindowsEnableConpty = 'terminal.integrated.windowsEnableConpty', @@ -127,23 +128,27 @@ export const enum TerminalSettingId { } export const enum PosixShellType { - PowerShell = 'pwsh', Bash = 'bash', Fish = 'fish', Sh = 'sh', Csh = 'csh', Ksh = 'ksh', Zsh = 'zsh', - Python = 'python' + } export const enum WindowsShellType { CommandPrompt = 'cmd', - PowerShell = 'pwsh', Wsl = 'wsl', GitBash = 'gitbash', - Python = 'python' } -export type TerminalShellType = PosixShellType | WindowsShellType; + +export const enum GeneralShellType { + PowerShell = 'pwsh', + Python = 'python', + Julia = 'julia', + NuShell = 'nu' +} +export type TerminalShellType = PosixShellType | WindowsShellType | GeneralShellType; export interface IRawTerminalInstanceLayoutInfo { relativeSize: number; @@ -661,6 +666,7 @@ export interface ITerminalProcessOptions { nonce: string; }; windowsEnableConpty: boolean; + windowsUseConptyDll: boolean; environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined; workspaceFolder: IWorkspaceFolder | undefined; } diff --git a/patched-vscode/src/vs/platform/terminal/common/terminalRecorder.ts b/patched-vscode/src/vs/platform/terminal/common/terminalRecorder.ts index 79a828cc..417527a9 100644 --- a/patched-vscode/src/vs/platform/terminal/common/terminalRecorder.ts +++ b/patched-vscode/src/vs/platform/terminal/common/terminalRecorder.ts @@ -91,7 +91,8 @@ export class TerminalRecorder { // No command restoration is needed when relaunching terminals commands: { isWindowsPty: false, - commands: [] + commands: [], + promptInputModel: undefined, } }; } diff --git a/patched-vscode/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/patched-vscode/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index cacc170e..d335e2c2 100644 --- a/patched-vscode/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/patched-vscode/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -577,7 +577,8 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati if (!this._terminal || !this.capabilities.has(TerminalCapability.CommandDetection)) { return { isWindowsPty: false, - commands: [] + commands: [], + promptInputModel: undefined, }; } const result = this._createOrGetCommandDetection(this._terminal).serialize(); diff --git a/patched-vscode/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts b/patched-vscode/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts index 8c74c72b..ebd03316 100644 --- a/patched-vscode/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts +++ b/patched-vscode/src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts @@ -38,7 +38,7 @@ export class ElectronPtyHostStarter extends Disposable implements IPtyHostStarte ) { super(); - this._lifecycleMainService.onWillShutdown(() => this._onWillShutdown.fire()); + this._register(this._lifecycleMainService.onWillShutdown(() => this._onWillShutdown.fire())); // Listen for new windows to establish connection directly to pty host validatedIpcMain.on('vscode:createPtyHostMessageChannel', (e, nonce) => this._onWindowConnection(e, nonce)); this._register(toDisposable(() => { diff --git a/patched-vscode/src/vs/platform/terminal/node/ptyHostService.ts b/patched-vscode/src/vs/platform/terminal/node/ptyHostService.ts index f91b87ba..402dcc7b 100644 --- a/patched-vscode/src/vs/platform/terminal/node/ptyHostService.ts +++ b/patched-vscode/src/vs/platform/terminal/node/ptyHostService.ts @@ -108,14 +108,16 @@ export class PtyHostService extends Disposable implements IPtyHostService { this._register(toDisposable(() => this._disposePtyHost())); this._resolveVariablesRequestStore = this._register(new RequestStore(undefined, this._logService)); - this._resolveVariablesRequestStore.onCreateRequest(this._onPtyHostRequestResolveVariables.fire, this._onPtyHostRequestResolveVariables); + this._register(this._resolveVariablesRequestStore.onCreateRequest(this._onPtyHostRequestResolveVariables.fire, this._onPtyHostRequestResolveVariables)); // Start the pty host when a window requests a connection, if the starter has that capability. if (this._ptyHostStarter.onRequestConnection) { - Event.once(this._ptyHostStarter.onRequestConnection)(() => this._ensurePtyHost()); + this._register(Event.once(this._ptyHostStarter.onRequestConnection)(() => this._ensurePtyHost())); } - this._ptyHostStarter.onWillShutdown?.(() => this._wasQuitRequested = true); + if (this._ptyHostStarter.onWillShutdown) { + this._register(this._ptyHostStarter.onWillShutdown(() => this._wasQuitRequested = true)); + } } private get _ignoreProcessNames(): string[] { diff --git a/patched-vscode/src/vs/platform/terminal/node/ptyService.ts b/patched-vscode/src/vs/platform/terminal/node/ptyService.ts index ec8182b3..f7295637 100644 --- a/patched-vscode/src/vs/platform/terminal/node/ptyService.ts +++ b/patched-vscode/src/vs/platform/terminal/node/ptyService.ts @@ -15,7 +15,6 @@ import { RequestStore } from 'vs/platform/terminal/common/requestStore'; import { IProcessDataEvent, IProcessReadyEvent, IPtyService, IRawTerminalInstanceLayoutInfo, IReconnectConstants, IShellLaunchConfig, ITerminalInstanceLayoutInfoById, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalTabLayoutInfoById, TerminalIcon, IProcessProperty, TitleEventSource, ProcessPropertyType, IProcessPropertyMap, IFixedTerminalDimensions, IPersistentTerminalProcessLaunchConfig, ICrossVersionSerializedTerminalState, ISerializedTerminalState, ITerminalProcessOptions, IPtyHostLatencyMeasurement } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; -import { Terminal as XtermTerminal } from '@xterm/headless'; import type { ISerializeOptions, SerializeAddon as XtermSerializeAddon } from '@xterm/addon-serialize'; import type { Unicode11Addon as XtermUnicode11Addon } from '@xterm/addon-unicode11'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto } from 'vs/platform/terminal/common/terminalProcess'; @@ -32,6 +31,14 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { join } from 'path'; import { memoize } from 'vs/base/common/decorators'; import * as performance from 'vs/base/common/performance'; +// ESM-comment-begin +import { Terminal as XtermTerminal } from '@xterm/headless'; +// ESM-comment-end +// ESM-uncomment-begin +// import pkg from '@xterm/headless'; +// type XtermTerminal = pkg.Terminal; +// const { Terminal: XtermTerminal } = pkg; +// ESM-uncomment-end export function traceRpc(_target: any, key: string, descriptor: any) { if (typeof descriptor.value !== 'function') { diff --git a/patched-vscode/src/vs/platform/terminal/node/terminalEnvironment.ts b/patched-vscode/src/vs/platform/terminal/node/terminalEnvironment.ts index 813fbe5c..7349ac1e 100644 --- a/patched-vscode/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/patched-vscode/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -157,6 +157,7 @@ export function getShellIntegrationInjection( } newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, ''); + envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0'; if (options.shellIntegration.suggestEnabled) { envMixin['VSCODE_SUGGEST'] = '1'; } @@ -174,6 +175,7 @@ export function getShellIntegrationInjection( } newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot); + envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0'; return { newArgs, envMixin }; } logService.warn(`Shell integration cannot be enabled for executable "${shellLaunchConfig.executable}" and args`, shellLaunchConfig.args); @@ -195,6 +197,7 @@ export function getShellIntegrationInjection( } newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot); + envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0'; return { newArgs, envMixin }; } case 'fish': { @@ -220,6 +223,7 @@ export function getShellIntegrationInjection( } newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, ''); + envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0'; return { newArgs, envMixin }; } case 'zsh': { diff --git a/patched-vscode/src/vs/platform/terminal/node/terminalProcess.ts b/patched-vscode/src/vs/platform/terminal/node/terminalProcess.ts index a799870b..acd296df 100644 --- a/patched-vscode/src/vs/platform/terminal/node/terminalProcess.ts +++ b/patched-vscode/src/vs/platform/terminal/node/terminalProcess.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { exec } from 'child_process'; import { timeout } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -10,11 +11,10 @@ import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import * as path from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { Promises } from 'vs/base/node/pfs'; import { localize } from 'vs/nls'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; -import { FlowControlConstants, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, IProcessProperty, IProcessPropertyMap as IProcessPropertyMap, ProcessPropertyType, TerminalShellType, IProcessReadyEvent, ITerminalProcessOptions, PosixShellType, IProcessReadyWindowsPty } from 'vs/platform/terminal/common/terminal'; +import { FlowControlConstants, IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError, IProcessProperty, IProcessPropertyMap as IProcessPropertyMap, ProcessPropertyType, TerminalShellType, IProcessReadyEvent, ITerminalProcessOptions, PosixShellType, IProcessReadyWindowsPty, GeneralShellType } from 'vs/platform/terminal/common/terminal'; import { ChildProcessMonitor } from 'vs/platform/terminal/node/childProcessMonitor'; import { findExecutable, getShellIntegrationInjection, getWindowsBuildNumber, IShellIntegrationConfigInjection } from 'vs/platform/terminal/node/terminalEnvironment'; import { WindowsShellHelper } from 'vs/platform/terminal/node/windowsShellHelper'; @@ -72,11 +72,16 @@ const posixShellTypeMap = new Map([ ['fish', PosixShellType.Fish], ['ksh', PosixShellType.Ksh], ['sh', PosixShellType.Sh], - ['pwsh', PosixShellType.PowerShell], - ['python', PosixShellType.Python], ['zsh', PosixShellType.Zsh] ]); +const generalShellTypeMap = new Map([ + ['pwsh', GeneralShellType.PowerShell], + ['python', GeneralShellType.Python], + ['julia', GeneralShellType.Julia], + ['nu', GeneralShellType.NuShell], + +]); export class TerminalProcess extends Disposable implements ITerminalChildProcess { readonly id = 0; readonly shouldPersist = false; @@ -114,7 +119,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess get exitMessage(): string | undefined { return this._exitMessage; } get currentTitle(): string { return this._windowsShellHelper?.shellTitle || this._currentTitle; } - get shellType(): TerminalShellType | undefined { return isWindows ? this._windowsShellHelper?.shellType : posixShellTypeMap.get(this._currentTitle); } + get shellType(): TerminalShellType | undefined { return isWindows ? this._windowsShellHelper?.shellType : posixShellTypeMap.get(this._currentTitle) || generalShellTypeMap.get(this._currentTitle); } get hasChildProcesses(): boolean { return this._childProcessMonitor?.hasChildProcesses || false; } private readonly _onProcessData = this._register(new Emitter()); @@ -153,6 +158,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._properties[ProcessPropertyType.InitialCwd] = this._initialCwd; this._properties[ProcessPropertyType.Cwd] = this._initialCwd; const useConpty = this._options.windowsEnableConpty && process.platform === 'win32' && getWindowsBuildNumber() >= 18309; + const useConptyDll = useConpty && this._options.windowsUseConptyDll; this._ptyOptions = { name, cwd, @@ -161,6 +167,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess cols, rows, useConpty, + useConptyDll, // This option will force conpty to not redraw the whole viewport on launch conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText }; @@ -211,9 +218,9 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } if (injection.filesToCopy) { for (const f of injection.filesToCopy) { - await Promises.mkdir(path.dirname(f.dest), { recursive: true }); try { - await Promises.copyFile(f.source, f.dest); + await fs.promises.mkdir(path.dirname(f.dest), { recursive: true }); + await fs.promises.copyFile(f.source, f.dest); } catch { // Swallow error, this should only happen when multiple users are on the same // machine. Since the shell integration scripts rarely change, plus the other user @@ -241,7 +248,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess private async _validateCwd(): Promise { try { - const result = await Promises.stat(this._initialCwd); + const result = await fs.promises.stat(this._initialCwd); if (!result.isDirectory()) { return { message: localize('launchFail.cwdNotDirectory', "Starting directory (cwd) \"{0}\" is not a directory", this._initialCwd.toString()) }; } @@ -268,7 +275,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } try { - const result = await Promises.stat(executable); + const result = await fs.promises.stat(executable); if (!result.isFile() && !result.isSymbolicLink()) { return { message: localize('launchFail.executableIsNotFileOrSymlink', "Path to shell executable \"{0}\" is not a file or a symlink", slc.executable) }; } @@ -401,15 +408,19 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess if (this._store.isDisposed) { return; } - this._currentTitle = ptyProcess.process; + // HACK: The node-pty API can return undefined somehow https://github.com/microsoft/vscode/issues/222323 + this._currentTitle = (ptyProcess.process ?? ''); this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: this._currentTitle }); // If fig is installed it may change the title of the process const sanitizedTitle = this.currentTitle.replace(/ \(figterm\)$/g, ''); if (sanitizedTitle.toLowerCase().startsWith('python')) { - this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: PosixShellType.Python }); + this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: GeneralShellType.Python }); + } else if (sanitizedTitle.toLowerCase().startsWith('julia')) { + this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: GeneralShellType.Julia }); } else { - this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: posixShellTypeMap.get(sanitizedTitle) }); + const shellTypeValue = posixShellTypeMap.get(sanitizedTitle) || generalShellTypeMap.get(sanitizedTitle); + this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: shellTypeValue }); } } @@ -608,7 +619,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } this._logService.trace('node-pty.IPty#pid'); try { - return await Promises.readlink(`/proc/${this._ptyProcess.pid}/cwd`); + return await fs.promises.readlink(`/proc/${this._ptyProcess.pid}/cwd`); } catch (error) { return this._initialCwd; } diff --git a/patched-vscode/src/vs/platform/terminal/node/terminalProfiles.ts b/patched-vscode/src/vs/platform/terminal/node/terminalProfiles.ts index e289fbc3..97a85375 100644 --- a/patched-vscode/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/patched-vscode/src/vs/platform/terminal/node/terminalProfiles.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import * as cp from 'child_process'; import { Codicon } from 'vs/base/common/codicons'; import { basename, delimiter, normalize } from 'vs/base/common/path'; @@ -38,7 +39,7 @@ export function detectAvailableProfiles( ): Promise { fsProvider = fsProvider || { existsFile: pfs.SymlinkSupport.existsFile, - readFile: pfs.Promises.readFile + readFile: fs.promises.readFile }; if (isWindows) { return detectAvailableWindowsProfiles( diff --git a/patched-vscode/src/vs/platform/terminal/node/windowsShellHelper.ts b/patched-vscode/src/vs/platform/terminal/node/windowsShellHelper.ts index e9fcb6f8..8768ee75 100644 --- a/patched-vscode/src/vs/platform/terminal/node/windowsShellHelper.ts +++ b/patched-vscode/src/vs/platform/terminal/node/windowsShellHelper.ts @@ -8,7 +8,7 @@ import { debounce } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { isWindows, platform } from 'vs/base/common/platform'; -import { TerminalShellType, WindowsShellType } from 'vs/platform/terminal/common/terminal'; +import { GeneralShellType, TerminalShellType, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import type * as WindowsProcessTreeType from '@vscode/windows-process-tree'; export interface IWindowsShellHelper extends IDisposable { @@ -139,10 +139,14 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe return WindowsShellType.CommandPrompt; case 'powershell.exe': case 'pwsh.exe': - return WindowsShellType.PowerShell; + return GeneralShellType.PowerShell; case 'bash.exe': case 'git-cmd.exe': return WindowsShellType.GitBash; + case 'julia.exe:': + return GeneralShellType.Julia; + case 'nu.exe': + return GeneralShellType.NuShell; case 'wsl.exe': case 'ubuntu.exe': case 'ubuntu1804.exe': @@ -153,7 +157,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe return WindowsShellType.Wsl; default: if (executable.match(/python(\d(\.\d{0,2})?)?\.exe/)) { - return WindowsShellType.Python; + return GeneralShellType.Python; } return undefined; } diff --git a/patched-vscode/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts b/patched-vscode/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts index 4566eb4b..667442dd 100644 --- a/patched-vscode/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts +++ b/patched-vscode/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts @@ -2,10 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* eslint-disable local/code-import-patterns */ -// eslint-disable-next-line local/code-import-patterns, local/code-amd-node-module -import { Terminal } from '@xterm/headless'; - +import type { Terminal } from '@xterm/xterm'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { PromptInputModel, type IPromptInputModelState } from 'vs/platform/terminal/common/capabilities/commandDetection/promptInputModel'; @@ -13,6 +12,7 @@ import { Emitter } from 'vs/base/common/event'; import type { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { notDeepStrictEqual, strictEqual } from 'assert'; import { timeout } from 'vs/base/common/async'; +import { importAMDNodeModule } from 'vs/amdX'; suite('PromptInputModel', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); @@ -58,8 +58,9 @@ suite('PromptInputModel', () => { strictEqual(promptInputModel.cursorIndex, cursorIndex, `value=${promptInputModel.value}`); } - setup(() => { - xterm = store.add(new Terminal({ allowProposedApi: true })); + setup(async () => { + const TerminalCtor = (await importAMDNodeModule('@xterm/xterm', 'lib/xterm.js')).Terminal; + xterm = store.add(new TerminalCtor({ allowProposedApi: true })); onCommandStart = store.add(new Emitter()); onCommandExecuted = store.add(new Emitter()); promptInputModel = store.add(new PromptInputModel(xterm, onCommandStart.event, onCommandExecuted.event, new NullLogService)); @@ -469,6 +470,26 @@ suite('PromptInputModel', () => { }); }); + suite('wrapped line (non-continuation)', () => { + test('basic wrapped line', async () => { + xterm.resize(5, 10); + + await writePromise('$ '); + fireCommandStart(); + await assertPromptInput('|'); + + await writePromise('ech'); + await assertPromptInput(`ech|`); + + await writePromise('o '); + await assertPromptInput(`echo |`); + + await writePromise('"a"'); + // HACK: Trailing whitespace is due to flaky detection in wrapped lines (but it doesn't matter much) + await assertPromptInput(`echo "a"| `); + }); + }); + // To "record a session" for these tests: // - Enable debug logging // - Open and clear Terminal output channel diff --git a/patched-vscode/src/vs/platform/terminal/test/common/terminalRecorder.test.ts b/patched-vscode/src/vs/platform/terminal/test/common/terminalRecorder.test.ts index b1c523a2..66b317c4 100644 --- a/patched-vscode/src/vs/platform/terminal/test/common/terminalRecorder.test.ts +++ b/patched-vscode/src/vs/platform/terminal/test/common/terminalRecorder.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ReplayEntry } from 'vs/platform/terminal/common/terminalProcess'; import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder'; diff --git a/patched-vscode/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts b/patched-vscode/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts index 2f68a7be..06f8aeed 100644 --- a/patched-vscode/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts +++ b/patched-vscode/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts @@ -12,9 +12,9 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { ITerminalProcessOptions } from 'vs/platform/terminal/common/terminal'; import { getShellIntegrationInjection, getWindowsBuildNumber, IShellIntegrationConfigInjection } from 'vs/platform/terminal/node/terminalEnvironment'; -const enabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, environmentVariableCollections: undefined, workspaceFolder: undefined }; -const disabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: false, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, environmentVariableCollections: undefined, workspaceFolder: undefined }; -const winptyProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: false, environmentVariableCollections: undefined, workspaceFolder: undefined }; +const enabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined }; +const disabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: false, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined }; +const winptyProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: false, windowsUseConptyDll: false, environmentVariableCollections: undefined, workspaceFolder: undefined }; const pwshExe = process.platform === 'win32' ? 'pwsh.exe' : 'pwsh'; const repoRoot = process.platform === 'win32' ? process.cwd()[0].toLowerCase() + process.cwd().substring(1) : process.cwd(); const logService = new NullLogService(); @@ -186,7 +186,8 @@ suite('platform - terminalEnvironment', () => { `${repoRoot}/out/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh` ], envMixin: { - VSCODE_INJECTION: '1' + VSCODE_INJECTION: '1', + VSCODE_STABLE: '0' } }); deepStrictEqual(getShellIntegrationInjection({ executable: 'bash', args: [] }, enabledProcessOptions, defaultEnvironment, logService, productService), enabledExpectedResult); @@ -201,7 +202,8 @@ suite('platform - terminalEnvironment', () => { ], envMixin: { VSCODE_INJECTION: '1', - VSCODE_SHELL_LOGIN: '1' + VSCODE_SHELL_LOGIN: '1', + VSCODE_STABLE: '0' } }); test('when array', () => { diff --git a/patched-vscode/src/vs/platform/theme/browser/defaultStyles.ts b/patched-vscode/src/vs/platform/theme/browser/defaultStyles.ts index 871178fa..68d93cbc 100644 --- a/patched-vscode/src/vs/platform/theme/browser/defaultStyles.ts +++ b/patched-vscode/src/vs/platform/theme/browser/defaultStyles.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IButtonStyles } from 'vs/base/browser/ui/button/button'; import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; -import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropOverBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listDropBetweenBackground } from 'vs/platform/theme/common/colorRegistry'; +import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropOverBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listDropBetweenBackground, radioActiveBackground, radioActiveForeground, radioInactiveBackground, radioInactiveForeground, radioInactiveBorder, radioInactiveHoverBackground, radioActiveBorder } from 'vs/platform/theme/common/colorRegistry'; import { IProgressBarStyles } from 'vs/base/browser/ui/progressbar/progressbar'; import { ICheckboxStyles, IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { IDialogStyles } from 'vs/base/browser/ui/dialog/dialog'; @@ -16,6 +16,7 @@ import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ISelectBoxStyles } from 'vs/base/browser/ui/selectBox/selectBox'; import { Color } from 'vs/base/common/color'; import { IMenuStyles } from 'vs/base/browser/ui/menu/menu'; +import { IRadioStyles } from 'vs/base/browser/ui/radio/radio'; export type IStyleOverride = { [P in keyof T]?: ColorIdentifier | undefined; @@ -41,6 +42,7 @@ export const defaultKeybindingLabelStyles: IKeybindingLabelStyles = { export function getKeybindingLabelStyles(override: IStyleOverride): IKeybindingLabelStyles { return overrideStyles(override, defaultKeybindingLabelStyles); } + export const defaultButtonStyles: IButtonStyles = { buttonForeground: asCssVariable(buttonForeground), buttonSeparator: asCssVariable(buttonSeparator), @@ -70,6 +72,16 @@ export const defaultToggleStyles: IToggleStyles = { inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) }; +export const defaultRadioStyles: IRadioStyles = { + activeForeground: asCssVariable(radioActiveForeground), + activeBackground: asCssVariable(radioActiveBackground), + activeBorder: asCssVariable(radioActiveBorder), + inactiveForeground: asCssVariable(radioInactiveForeground), + inactiveBackground: asCssVariable(radioInactiveBackground), + inactiveBorder: asCssVariable(radioInactiveBorder), + inactiveHoverBackground: asCssVariable(radioInactiveHoverBackground), +}; + export function getToggleStyles(override: IStyleOverride): IToggleStyles { return overrideStyles(override, defaultToggleStyles); } @@ -176,7 +188,7 @@ export const defaultListStyles: IListStyles = { treeInactiveIndentGuidesStroke: asCssVariable(treeInactiveIndentGuidesStroke), treeStickyScrollBackground: undefined, treeStickyScrollBorder: undefined, - treeStickyScrollShadow: undefined, + treeStickyScrollShadow: asCssVariable(scrollbarShadow), tableColumnsBorder: asCssVariable(tableColumnsBorder), tableOddRowsBackgroundColor: asCssVariable(tableOddRowsBackgroundColor), }; diff --git a/patched-vscode/src/vs/platform/theme/common/colorUtils.ts b/patched-vscode/src/vs/platform/theme/common/colorUtils.ts index 2388e7cb..a237166f 100644 --- a/patched-vscode/src/vs/platform/theme/common/colorUtils.ts +++ b/patched-vscode/src/vs/platform/theme/common/colorUtils.ts @@ -7,10 +7,11 @@ import { assertNever } from 'vs/base/common/assert'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; -import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import * as platform from 'vs/platform/registry/common/platform'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; +import * as nls from 'vs/nls'; // ------ API types @@ -19,7 +20,7 @@ export type ColorIdentifier = string; export interface ColorContribution { readonly id: ColorIdentifier; readonly description: string; - readonly defaults: ColorDefaults | null; + readonly defaults: ColorDefaults | ColorValue | null; readonly needsTransparency: boolean; readonly deprecationMessage: string | undefined; } @@ -68,6 +69,9 @@ export interface ColorDefaults { hcLight: ColorValue | null; } +export function isColorDefaults(value: unknown): value is ColorDefaults { + return value !== null && typeof value === 'object' && 'light' in value && 'dark' in value; +} /** * A Color Value is either a color literal, a reference to an other color or a derived color @@ -79,6 +83,8 @@ export const Extensions = { ColorContribution: 'base.contributions.colors' }; +export const DEFAULT_COLOR_CONFIG_VALUE = 'default'; + export interface IColorRegistry { readonly onDidChangeSchema: Event; @@ -117,33 +123,57 @@ export interface IColorRegistry { */ getColorReferenceSchema(): IJSONSchema; + /** + * Notify when the color theme or settings change. + */ + notifyThemeUpdate(theme: IColorTheme): void; + } +type IJSONSchemaForColors = IJSONSchema & { properties: { [name: string]: { oneOf: [IJSONSchemaWithSnippets, IJSONSchema] } } }; +type IJSONSchemaWithSnippets = IJSONSchema & { defaultSnippets: IJSONSchemaSnippet[] }; + class ColorRegistry implements IColorRegistry { private readonly _onDidChangeSchema = new Emitter(); readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; private colorsById: { [key: string]: ColorContribution }; - private colorSchema: IJSONSchema & { properties: IJSONSchemaMap } = { type: 'object', properties: {} }; + private colorSchema: IJSONSchemaForColors = { type: 'object', properties: {} }; private colorReferenceSchema: IJSONSchema & { enum: string[]; enumDescriptions: string[] } = { type: 'string', enum: [], enumDescriptions: [] }; constructor() { this.colorsById = {}; } - public registerColor(id: string, defaults: ColorDefaults | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { + public notifyThemeUpdate(colorThemeData: IColorTheme) { + for (const key of Object.keys(this.colorsById)) { + const color = colorThemeData.getColor(key); + if (color) { + this.colorSchema.properties[key].oneOf[0].defaultSnippets[0].body = `\${1:${color.toString()}}`; + } + } + this._onDidChangeSchema.fire(); + } + + public registerColor(id: string, defaults: ColorDefaults | ColorValue | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { const colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage }; this.colorsById[id] = colorContribution; - const propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; + const propertySchema: IJSONSchemaWithSnippets = { type: 'string', format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } if (needsTransparency) { propertySchema.pattern = '^#(?:(?[0-9a-fA-f]{3}[0-9a-eA-E])|(?:[0-9a-fA-F]{6}(?:(?![fF]{2})(?:[0-9a-fA-F]{2}))))?$'; - propertySchema.patternErrorMessage = 'This color must be transparent or it will obscure content'; + propertySchema.patternErrorMessage = nls.localize('transparecyRequired', 'This color must be transparent or it will obscure content'); } - this.colorSchema.properties[id] = propertySchema; + this.colorSchema.properties[id] = { + description, + oneOf: [ + propertySchema, + { type: 'string', const: DEFAULT_COLOR_CONFIG_VALUE, description: nls.localize('useDefault', 'Use the default color.') } + ] + }; this.colorReferenceSchema.enum.push(id); this.colorReferenceSchema.enumDescriptions.push(description); @@ -169,8 +199,8 @@ class ColorRegistry implements IColorRegistry { public resolveDefaultColor(id: ColorIdentifier, theme: IColorTheme): Color | undefined { const colorDesc = this.colorsById[id]; - if (colorDesc && colorDesc.defaults) { - const colorValue = colorDesc.defaults[theme.type]; + if (colorDesc?.defaults) { + const colorValue = isColorDefaults(colorDesc.defaults) ? colorDesc.defaults[theme.type] : colorDesc.defaults; return resolveColorValue(colorValue, theme); } return undefined; @@ -203,7 +233,7 @@ const colorRegistry = new ColorRegistry(); platform.Registry.add(Extensions.ColorContribution, colorRegistry); -export function registerColor(id: string, defaults: ColorDefaults | null, description: string, needsTransparency?: boolean, deprecationMessage?: string): ColorIdentifier { +export function registerColor(id: string, defaults: ColorDefaults | ColorValue | null, description: string, needsTransparency?: boolean, deprecationMessage?: string): ColorIdentifier { return colorRegistry.registerColor(id, defaults, description, needsTransparency, deprecationMessage); } @@ -319,6 +349,7 @@ const schemaRegistry = platform.Registry.as(JSONExten schemaRegistry.registerSchema(workbenchColorsSchemaId, colorRegistry.getColorSchema()); const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(workbenchColorsSchemaId), 200); + colorRegistry.onDidChangeSchema(() => { if (!delayer.isScheduled()) { delayer.schedule(); diff --git a/patched-vscode/src/vs/platform/theme/common/colors/baseColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/baseColors.ts index 1d19b3ad..baf6b86f 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/baseColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/baseColors.ts @@ -43,7 +43,7 @@ export const activeContrastBorder = registerColor('contrastActiveBorder', nls.localize('activeContrastBorder', "An extra border around active elements to separate them from others for greater contrast.")); export const selectionBackground = registerColor('selection.background', - { light: null, dark: null, hcDark: null, hcLight: null }, + null, nls.localize('selectionBackground', "The background color of text selections in the workbench (e.g. for input fields or text areas). Note that this does not apply to selections within the editor.")); diff --git a/patched-vscode/src/vs/platform/theme/common/colors/chartsColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/chartsColors.ts index eb63b602..a35e296d 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/chartsColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/chartsColors.ts @@ -12,27 +12,27 @@ import { minimapFindMatch } from 'vs/platform/theme/common/colors/minimapColors' export const chartsForeground = registerColor('charts.foreground', - { dark: foreground, light: foreground, hcDark: foreground, hcLight: foreground }, + foreground, nls.localize('chartsForeground', "The foreground color used in charts.")); export const chartsLines = registerColor('charts.lines', - { dark: transparent(foreground, .5), light: transparent(foreground, .5), hcDark: transparent(foreground, .5), hcLight: transparent(foreground, .5) }, + transparent(foreground, .5), nls.localize('chartsLines', "The color used for horizontal lines in charts.")); export const chartsRed = registerColor('charts.red', - { dark: editorErrorForeground, light: editorErrorForeground, hcDark: editorErrorForeground, hcLight: editorErrorForeground }, + editorErrorForeground, nls.localize('chartsRed', "The red color used in chart visualizations.")); export const chartsBlue = registerColor('charts.blue', - { dark: editorInfoForeground, light: editorInfoForeground, hcDark: editorInfoForeground, hcLight: editorInfoForeground }, + editorInfoForeground, nls.localize('chartsBlue', "The blue color used in chart visualizations.")); export const chartsYellow = registerColor('charts.yellow', - { dark: editorWarningForeground, light: editorWarningForeground, hcDark: editorWarningForeground, hcLight: editorWarningForeground }, + editorWarningForeground, nls.localize('chartsYellow', "The yellow color used in chart visualizations.")); export const chartsOrange = registerColor('charts.orange', - { dark: minimapFindMatch, light: minimapFindMatch, hcDark: minimapFindMatch, hcLight: minimapFindMatch }, + minimapFindMatch, nls.localize('chartsOrange', "The orange color used in chart visualizations.")); export const chartsGreen = registerColor('charts.green', diff --git a/patched-vscode/src/vs/platform/theme/common/colors/editorColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/editorColors.ts index a57b85e2..cebf9ba8 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/editorColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/editorColors.ts @@ -26,7 +26,7 @@ export const editorForeground = registerColor('editor.foreground', export const editorStickyScrollBackground = registerColor('editorStickyScroll.background', - { light: editorBackground, dark: editorBackground, hcDark: editorBackground, hcLight: editorBackground }, + editorBackground, nls.localize('editorStickyScrollBackground', "Background color of sticky scroll in the editor")); export const editorStickyScrollHoverBackground = registerColor('editorStickyScrollHover.background', @@ -38,7 +38,7 @@ export const editorStickyScrollBorder = registerColor('editorStickyScroll.border nls.localize('editorStickyScrollBorder', "Border color of sticky scroll in the editor")); export const editorStickyScrollShadow = registerColor('editorStickyScroll.shadow', - { dark: scrollbarShadow, light: scrollbarShadow, hcDark: scrollbarShadow, hcLight: scrollbarShadow }, + scrollbarShadow, nls.localize('editorStickyScrollShadow', " Shadow color of sticky scroll in the editor")); @@ -47,7 +47,7 @@ export const editorWidgetBackground = registerColor('editorWidget.background', nls.localize('editorWidgetBackground', 'Background color of editor widgets, such as find/replace.')); export const editorWidgetForeground = registerColor('editorWidget.foreground', - { dark: foreground, light: foreground, hcDark: foreground, hcLight: foreground }, + foreground, nls.localize('editorWidgetForeground', 'Foreground color of editor widgets, such as find/replace.')); export const editorWidgetBorder = registerColor('editorWidget.border', @@ -55,12 +55,12 @@ export const editorWidgetBorder = registerColor('editorWidget.border', nls.localize('editorWidgetBorder', 'Border color of editor widgets. The color is only used if the widget chooses to have a border and if the color is not overridden by a widget.')); export const editorWidgetResizeBorder = registerColor('editorWidget.resizeBorder', - { light: null, dark: null, hcDark: null, hcLight: null }, + null, nls.localize('editorWidgetResizeBorder', "Border color of the resize bar of editor widgets. The color is only used if the widget chooses to have a resize border and if the color is not overridden by a widget.")); export const editorErrorBackground = registerColor('editorError.background', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('editorError.background', 'Background color of error text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorErrorForeground = registerColor('editorError.foreground', @@ -73,7 +73,7 @@ export const editorErrorBorder = registerColor('editorError.border', export const editorWarningBackground = registerColor('editorWarning.background', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('editorWarning.background', 'Background color of warning text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorWarningForeground = registerColor('editorWarning.foreground', @@ -86,7 +86,7 @@ export const editorWarningBorder = registerColor('editorWarning.border', export const editorInfoBackground = registerColor('editorInfo.background', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('editorInfo.background', 'Background color of info text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorInfoForeground = registerColor('editorInfo.foreground', @@ -142,7 +142,7 @@ export const editorFindMatch = registerColor('editor.findMatchBackground', nls.localize('editorFindMatch', "Color of the current search match.")); export const editorFindMatchForeground = registerColor('editor.findMatchForeground', - { light: null, dark: null, hcDark: null, hcLight: null }, + null, nls.localize('editorFindMatchForeground', "Text color of the current search match.")); export const editorFindMatchHighlight = registerColor('editor.findMatchHighlightBackground', @@ -150,7 +150,7 @@ export const editorFindMatchHighlight = registerColor('editor.findMatchHighlight nls.localize('findMatchHighlight', "Color of the other search matches. The color must not be opaque so as not to hide underlying decorations."), true); export const editorFindMatchHighlightForeground = registerColor('editor.findMatchHighlightForeground', - { light: null, dark: null, hcDark: null, hcLight: null }, + null, nls.localize('findMatchHighlightForeground', "Foreground color of the other search matches."), true); export const editorFindRangeHighlight = registerColor('editor.findRangeHighlightBackground', @@ -177,15 +177,15 @@ export const editorHoverHighlight = registerColor('editor.hoverHighlightBackgrou nls.localize('hoverHighlight', 'Highlight below the word for which a hover is shown. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorHoverBackground = registerColor('editorHoverWidget.background', - { light: editorWidgetBackground, dark: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, + editorWidgetBackground, nls.localize('hoverBackground', 'Background color of the editor hover.')); export const editorHoverForeground = registerColor('editorHoverWidget.foreground', - { light: editorWidgetForeground, dark: editorWidgetForeground, hcDark: editorWidgetForeground, hcLight: editorWidgetForeground }, + editorWidgetForeground, nls.localize('hoverForeground', 'Foreground color of the editor hover.')); export const editorHoverBorder = registerColor('editorHoverWidget.border', - { light: editorWidgetBorder, dark: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, + editorWidgetBorder, nls.localize('hoverBorder', 'Border color of the editor hover.')); export const editorHoverStatusBarBackground = registerColor('editorHoverWidget.statusBarBackground', @@ -204,19 +204,19 @@ export const editorInlayHintBackground = registerColor('editorInlayHint.backgrou nls.localize('editorInlayHintBackground', 'Background color of inline hints')); export const editorInlayHintTypeForeground = registerColor('editorInlayHint.typeForeground', - { dark: editorInlayHintForeground, light: editorInlayHintForeground, hcDark: editorInlayHintForeground, hcLight: editorInlayHintForeground }, + editorInlayHintForeground, nls.localize('editorInlayHintForegroundTypes', 'Foreground color of inline hints for types')); export const editorInlayHintTypeBackground = registerColor('editorInlayHint.typeBackground', - { dark: editorInlayHintBackground, light: editorInlayHintBackground, hcDark: editorInlayHintBackground, hcLight: editorInlayHintBackground }, + editorInlayHintBackground, nls.localize('editorInlayHintBackgroundTypes', 'Background color of inline hints for types')); export const editorInlayHintParameterForeground = registerColor('editorInlayHint.parameterForeground', - { dark: editorInlayHintForeground, light: editorInlayHintForeground, hcDark: editorInlayHintForeground, hcLight: editorInlayHintForeground }, + editorInlayHintForeground, nls.localize('editorInlayHintForegroundParameter', 'Foreground color of inline hints for parameters')); export const editorInlayHintParameterBackground = registerColor('editorInlayHint.parameterBackground', - { dark: editorInlayHintBackground, light: editorInlayHintBackground, hcDark: editorInlayHintBackground, hcLight: editorInlayHintBackground }, + editorInlayHintBackground, nls.localize('editorInlayHintBackgroundParameter', 'Background color of inline hints for parameters')); @@ -231,7 +231,7 @@ export const editorLightBulbAutoFixForeground = registerColor('editorLightBulbAu nls.localize('editorLightBulbAutoFixForeground', "The color used for the lightbulb auto fix actions icon.")); export const editorLightBulbAiForeground = registerColor('editorLightBulbAi.foreground', - { dark: editorLightBulbForeground, light: editorLightBulbForeground, hcDark: editorLightBulbForeground, hcLight: editorLightBulbForeground }, + editorLightBulbForeground, nls.localize('editorLightBulbAiForeground', "The color used for the lightbulb AI icon.")); @@ -242,11 +242,11 @@ export const snippetTabstopHighlightBackground = registerColor('editor.snippetTa nls.localize('snippetTabstopHighlightBackground', "Highlight background color of a snippet tabstop.")); export const snippetTabstopHighlightBorder = registerColor('editor.snippetTabstopHighlightBorder', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('snippetTabstopHighlightBorder', "Highlight border color of a snippet tabstop.")); export const snippetFinalTabstopHighlightBackground = registerColor('editor.snippetFinalTabstopHighlightBackground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('snippetFinalTabstopHighlightBackground', "Highlight background color of the final tabstop of a snippet.")); export const snippetFinalTabstopHighlightBorder = registerColor('editor.snippetFinalTabstopHighlightBorder', @@ -278,20 +278,20 @@ export const diffRemovedLine = registerColor('diffEditor.removedLineBackground', export const diffInsertedLineGutter = registerColor('diffEditorGutter.insertedLineBackground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('diffEditorInsertedLineGutter', 'Background color for the margin where lines got inserted.')); export const diffRemovedLineGutter = registerColor('diffEditorGutter.removedLineBackground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('diffEditorRemovedLineGutter', 'Background color for the margin where lines got removed.')); export const diffOverviewRulerInserted = registerColor('diffEditorOverview.insertedForeground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('diffEditorOverviewInserted', 'Diff overview ruler foreground for inserted content.')); export const diffOverviewRulerRemoved = registerColor('diffEditorOverview.removedForeground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('diffEditorOverviewRemoved', 'Diff overview ruler foreground for removed content.')); @@ -314,11 +314,11 @@ export const diffDiagonalFill = registerColor('diffEditor.diagonalFill', export const diffUnchangedRegionBackground = registerColor('diffEditor.unchangedRegionBackground', - { dark: 'sideBar.background', light: 'sideBar.background', hcDark: 'sideBar.background', hcLight: 'sideBar.background' }, + 'sideBar.background', nls.localize('diffEditor.unchangedRegionBackground', "The background color of unchanged blocks in the diff editor.")); export const diffUnchangedRegionForeground = registerColor('diffEditor.unchangedRegionForeground', - { dark: 'foreground', light: 'foreground', hcDark: 'foreground', hcLight: 'foreground' }, + 'foreground', nls.localize('diffEditor.unchangedRegionForeground', "The foreground color of unchanged blocks in the diff editor.")); export const diffUnchangedTextBackground = registerColor('diffEditor.unchangedCodeBackground', @@ -355,11 +355,11 @@ export const toolbarActiveBackground = registerColor('toolbar.activeBackground', // ----- breadcumbs export const breadcrumbsForeground = registerColor('breadcrumb.foreground', - { light: transparent(foreground, 0.8), dark: transparent(foreground, 0.8), hcDark: transparent(foreground, 0.8), hcLight: transparent(foreground, 0.8) }, + transparent(foreground, 0.8), nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items.")); export const breadcrumbsBackground = registerColor('breadcrumb.background', - { light: editorBackground, dark: editorBackground, hcDark: editorBackground, hcLight: editorBackground }, + editorBackground, nls.localize('breadcrumbsBackground', "Background color of breadcrumb items.")); export const breadcrumbsFocusForeground = registerColor('breadcrumb.focusForeground', @@ -371,7 +371,7 @@ export const breadcrumbsActiveSelectionForeground = registerColor('breadcrumb.ac nls.localize('breadcrumbsSelectedForeground', "Color of selected breadcrumb items.")); export const breadcrumbsPickerBackground = registerColor('breadcrumbPicker.background', - { light: editorWidgetBackground, dark: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, + editorWidgetBackground, nls.localize('breadcrumbsSelectedBackground', "Background color of breadcrumb item picker.")); @@ -389,7 +389,7 @@ export const mergeCurrentHeaderBackground = registerColor('merge.currentHeaderBa nls.localize('mergeCurrentHeaderBackground', 'Current header background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true); export const mergeCurrentContentBackground = registerColor('merge.currentContentBackground', - { dark: transparent(mergeCurrentHeaderBackground, contentTransparency), light: transparent(mergeCurrentHeaderBackground, contentTransparency), hcDark: transparent(mergeCurrentHeaderBackground, contentTransparency), hcLight: transparent(mergeCurrentHeaderBackground, contentTransparency) }, + transparent(mergeCurrentHeaderBackground, contentTransparency), nls.localize('mergeCurrentContentBackground', 'Current content background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true); export const mergeIncomingHeaderBackground = registerColor('merge.incomingHeaderBackground', @@ -397,7 +397,7 @@ export const mergeIncomingHeaderBackground = registerColor('merge.incomingHeader nls.localize('mergeIncomingHeaderBackground', 'Incoming header background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true); export const mergeIncomingContentBackground = registerColor('merge.incomingContentBackground', - { dark: transparent(mergeIncomingHeaderBackground, contentTransparency), light: transparent(mergeIncomingHeaderBackground, contentTransparency), hcDark: transparent(mergeIncomingHeaderBackground, contentTransparency), hcLight: transparent(mergeIncomingHeaderBackground, contentTransparency) }, + transparent(mergeIncomingHeaderBackground, contentTransparency), nls.localize('mergeIncomingContentBackground', 'Incoming content background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true); export const mergeCommonHeaderBackground = registerColor('merge.commonHeaderBackground', @@ -405,7 +405,7 @@ export const mergeCommonHeaderBackground = registerColor('merge.commonHeaderBack nls.localize('mergeCommonHeaderBackground', 'Common ancestor header background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true); export const mergeCommonContentBackground = registerColor('merge.commonContentBackground', - { dark: transparent(mergeCommonHeaderBackground, contentTransparency), light: transparent(mergeCommonHeaderBackground, contentTransparency), hcDark: transparent(mergeCommonHeaderBackground, contentTransparency), hcLight: transparent(mergeCommonHeaderBackground, contentTransparency) }, + transparent(mergeCommonHeaderBackground, contentTransparency), nls.localize('mergeCommonContentBackground', 'Common ancestor content background in inline merge-conflicts. The color must not be opaque so as not to hide underlying decorations.'), true); export const mergeBorder = registerColor('merge.border', @@ -426,24 +426,24 @@ export const overviewRulerCommonContentForeground = registerColor('editorOvervie nls.localize('overviewRulerCommonContentForeground', 'Common ancestor overview ruler foreground for inline merge-conflicts.')); export const overviewRulerFindMatchForeground = registerColor('editorOverviewRuler.findMatchForeground', - { dark: '#d186167e', light: '#d186167e', hcDark: '#AB5A00', hcLight: '' }, + { dark: '#d186167e', light: '#d186167e', hcDark: '#AB5A00', hcLight: '#AB5A00' }, nls.localize('overviewRulerFindMatchForeground', 'Overview ruler marker color for find matches. The color must not be opaque so as not to hide underlying decorations.'), true); export const overviewRulerSelectionHighlightForeground = registerColor('editorOverviewRuler.selectionHighlightForeground', - { dark: '#A0A0A0CC', light: '#A0A0A0CC', hcDark: '#A0A0A0CC', hcLight: '#A0A0A0CC' }, + '#A0A0A0CC', nls.localize('overviewRulerSelectionHighlightForeground', 'Overview ruler marker color for selection highlights. The color must not be opaque so as not to hide underlying decorations.'), true); // ----- problems export const problemsErrorIconForeground = registerColor('problemsErrorIcon.foreground', - { dark: editorErrorForeground, light: editorErrorForeground, hcDark: editorErrorForeground, hcLight: editorErrorForeground }, + editorErrorForeground, nls.localize('problemsErrorIconForeground', "The color used for the problems error icon.")); export const problemsWarningIconForeground = registerColor('problemsWarningIcon.foreground', - { dark: editorWarningForeground, light: editorWarningForeground, hcDark: editorWarningForeground, hcLight: editorWarningForeground }, + editorWarningForeground, nls.localize('problemsWarningIconForeground', "The color used for the problems warning icon.")); export const problemsInfoIconForeground = registerColor('problemsInfoIcon.foreground', - { dark: editorInfoForeground, light: editorInfoForeground, hcDark: editorInfoForeground, hcLight: editorInfoForeground }, + editorInfoForeground, nls.localize('problemsInfoIconForeground', "The color used for the problems info icon.")); diff --git a/patched-vscode/src/vs/platform/theme/common/colors/inputColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/inputColors.ts index dc38222d..f31b804f 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/inputColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/inputColors.ts @@ -21,7 +21,7 @@ export const inputBackground = registerColor('input.background', nls.localize('inputBoxBackground', "Input box background.")); export const inputForeground = registerColor('input.foreground', - { dark: foreground, light: foreground, hcDark: foreground, hcLight: foreground }, + foreground, nls.localize('inputBoxForeground', "Input box foreground.")); export const inputBorder = registerColor('input.border', @@ -110,11 +110,11 @@ export const selectBorder = registerColor('dropdown.border', // ------ button export const buttonForeground = registerColor('button.foreground', - { dark: Color.white, light: Color.white, hcDark: Color.white, hcLight: Color.white }, + Color.white, nls.localize('buttonForeground', "Button foreground color.")); export const buttonSeparator = registerColor('button.separator', - { dark: transparent(buttonForeground, .4), light: transparent(buttonForeground, .4), hcDark: transparent(buttonForeground, .4), hcLight: transparent(buttonForeground, .4) }, + transparent(buttonForeground, .4), nls.localize('buttonSeparator', "Button separator color.")); export const buttonBackground = registerColor('button.background', @@ -126,7 +126,7 @@ export const buttonHoverBackground = registerColor('button.hoverBackground', nls.localize('buttonHoverBackground', "Button background color when hovering.")); export const buttonBorder = registerColor('button.border', - { dark: contrastBorder, light: contrastBorder, hcDark: contrastBorder, hcLight: contrastBorder }, + contrastBorder, nls.localize('buttonBorder', "Button border color.")); export const buttonSecondaryForeground = registerColor('button.secondaryForeground', @@ -141,27 +141,56 @@ export const buttonSecondaryHoverBackground = registerColor('button.secondaryHov { dark: lighten(buttonSecondaryBackground, 0.2), light: darken(buttonSecondaryBackground, 0.2), hcDark: null, hcLight: null }, nls.localize('buttonSecondaryHoverBackground', "Secondary button background color when hovering.")); +// ------ radio + +export const radioActiveForeground = registerColor('radio.activeForeground', + inputActiveOptionForeground, + nls.localize('radioActiveForeground', "Foreground color of active radio option.")); + +export const radioActiveBackground = registerColor('radio.activeBackground', + inputActiveOptionBackground, + nls.localize('radioBackground', "Background color of active radio option.")); + +export const radioActiveBorder = registerColor('radio.activeBorder', + inputActiveOptionBorder, + nls.localize('radioActiveBorder', "Border color of the active radio option.")); + +export const radioInactiveForeground = registerColor('radio.inactiveForeground', + null, + nls.localize('radioInactiveForeground', "Foreground color of inactive radio option.")); + +export const radioInactiveBackground = registerColor('radio.inactiveBackground', + null, + nls.localize('radioInactiveBackground', "Background color of inactive radio option.")); + +export const radioInactiveBorder = registerColor('radio.inactiveBorder', + { light: transparent(radioActiveForeground, .2), dark: transparent(radioActiveForeground, .2), hcDark: transparent(radioActiveForeground, .4), hcLight: transparent(radioActiveForeground, .2) }, + nls.localize('radioInactiveBorder', "Border color of the inactive radio option.")); + +export const radioInactiveHoverBackground = registerColor('radio.inactiveHoverBackground', + inputActiveOptionHoverBackground, + nls.localize('radioHoverBackground', "Background color of inactive active radio option when hovering.")); // ------ checkbox export const checkboxBackground = registerColor('checkbox.background', - { dark: selectBackground, light: selectBackground, hcDark: selectBackground, hcLight: selectBackground }, + selectBackground, nls.localize('checkbox.background', "Background color of checkbox widget.")); export const checkboxSelectBackground = registerColor('checkbox.selectBackground', - { dark: editorWidgetBackground, light: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, + editorWidgetBackground, nls.localize('checkbox.select.background', "Background color of checkbox widget when the element it's in is selected.")); export const checkboxForeground = registerColor('checkbox.foreground', - { dark: selectForeground, light: selectForeground, hcDark: selectForeground, hcLight: selectForeground }, + selectForeground, nls.localize('checkbox.foreground', "Foreground color of checkbox widget.")); export const checkboxBorder = registerColor('checkbox.border', - { dark: selectBorder, light: selectBorder, hcDark: selectBorder, hcLight: selectBorder }, + selectBorder, nls.localize('checkbox.border', "Border color of checkbox widget.")); export const checkboxSelectBorder = registerColor('checkbox.selectBorder', - { dark: iconForeground, light: iconForeground, hcDark: iconForeground, hcLight: iconForeground }, + iconForeground, nls.localize('checkbox.select.border', "Border color of checkbox widget when the element it's in is selected.")); diff --git a/patched-vscode/src/vs/platform/theme/common/colors/listColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/listColors.ts index b6f51e36..8fb7c4a7 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/listColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/listColors.ts @@ -11,15 +11,15 @@ import { registerColor, darken, lighten, transparent, ifDefinedThenElse } from ' // Import the colors we need import { foreground, contrastBorder, activeContrastBorder, focusBorder, iconForeground } from 'vs/platform/theme/common/colors/baseColors'; -import { editorWidgetBackground, editorFindMatchHighlightBorder, editorFindMatchHighlight, widgetShadow } from 'vs/platform/theme/common/colors/editorColors'; +import { editorWidgetBackground, editorFindMatchHighlightBorder, editorFindMatchHighlight, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colors/editorColors'; export const listFocusBackground = registerColor('list.focusBackground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listFocusBackground', "List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listFocusForeground = registerColor('list.focusForeground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listFocusOutline = registerColor('list.focusOutline', @@ -27,7 +27,7 @@ export const listFocusOutline = registerColor('list.focusOutline', nls.localize('listFocusOutline', "List/Tree outline color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listFocusAndSelectionOutline = registerColor('list.focusAndSelectionOutline', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listFocusAndSelectionOutline', "List/Tree outline color for the focused item when the list/tree is active and selected. An active list/tree has keyboard focus, an inactive does not.")); export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', @@ -39,7 +39,7 @@ export const listActiveSelectionForeground = registerColor('list.activeSelection nls.localize('listActiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listActiveSelectionIconForeground = registerColor('list.activeSelectionIconForeground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listActiveSelectionIconForeground', "List/Tree icon foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', @@ -47,19 +47,19 @@ export const listInactiveSelectionBackground = registerColor('list.inactiveSelec nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveSelectionIconForeground = registerColor('list.inactiveSelectionIconForeground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listInactiveSelectionIconForeground', "List/Tree icon foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listInactiveFocusBackground', "List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveFocusOutline = registerColor('list.inactiveFocusOutline', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listInactiveFocusOutline', "List/Tree outline color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listHoverBackground = registerColor('list.hoverBackground', @@ -67,7 +67,7 @@ export const listHoverBackground = registerColor('list.hoverBackground', nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse.")); export const listHoverForeground = registerColor('list.hoverForeground', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse.")); export const listDropOverBackground = registerColor('list.dropBackground', @@ -109,7 +109,7 @@ export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget. nls.localize('listFilterWidgetNoMatchesOutline', 'Outline color of the type filter widget in lists and trees, when there are no matches.')); export const listFilterWidgetShadow = registerColor('listFilterWidget.shadow', - { dark: widgetShadow, light: widgetShadow, hcDark: widgetShadow, hcLight: widgetShadow }, + widgetShadow, nls.localize('listFilterWidgetShadow', 'Shadow color of the type filter widget in lists and trees.')); export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', @@ -132,7 +132,7 @@ export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); export const treeInactiveIndentGuidesStroke = registerColor('tree.inactiveIndentGuidesStroke', - { dark: transparent(treeIndentGuidesStroke, 0.4), light: transparent(treeIndentGuidesStroke, 0.4), hcDark: transparent(treeIndentGuidesStroke, 0.4), hcLight: transparent(treeIndentGuidesStroke, 0.4) }, + transparent(treeIndentGuidesStroke, 0.4), nls.localize('treeInactiveIndentGuidesStroke', "Tree stroke color for the indentation guides that are not active.")); @@ -145,3 +145,21 @@ export const tableColumnsBorder = registerColor('tree.tableColumnsBorder', export const tableOddRowsBackgroundColor = registerColor('tree.tableOddRowsBackground', { dark: transparent(foreground, 0.04), light: transparent(foreground, 0.04), hcDark: null, hcLight: null }, nls.localize('tableOddRowsBackgroundColor', "Background color for odd table rows.")); + +// ------ action list + +export const editorActionListBackground = registerColor('editorActionList.background', + editorWidgetBackground, + nls.localize('editorActionListBackground', "Action List background color.")); + +export const editorActionListForeground = registerColor('editorActionList.foreground', + editorWidgetForeground, + nls.localize('editorActionListForeground', "Action List foreground color.")); + +export const editorActionListFocusForeground = registerColor('editorActionList.focusForeground', + listActiveSelectionForeground, + nls.localize('editorActionListFocusForeground', "Action List foreground color for the focused item.")); + +export const editorActionListFocusBackground = registerColor('editorActionList.focusBackground', + listActiveSelectionBackground, + nls.localize('editorActionListFocusBackground', "Action List background color for the focused item.")); diff --git a/patched-vscode/src/vs/platform/theme/common/colors/menuColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/menuColors.ts index 6fa9a0ec..05bf5491 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/menuColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/menuColors.ts @@ -19,19 +19,19 @@ export const menuBorder = registerColor('menu.border', nls.localize('menuBorder', "Border color of menus.")); export const menuForeground = registerColor('menu.foreground', - { dark: selectForeground, light: selectForeground, hcDark: selectForeground, hcLight: selectForeground }, + selectForeground, nls.localize('menuForeground', "Foreground color of menu items.")); export const menuBackground = registerColor('menu.background', - { dark: selectBackground, light: selectBackground, hcDark: selectBackground, hcLight: selectBackground }, + selectBackground, nls.localize('menuBackground', "Background color of menu items.")); export const menuSelectionForeground = registerColor('menu.selectionForeground', - { dark: listActiveSelectionForeground, light: listActiveSelectionForeground, hcDark: listActiveSelectionForeground, hcLight: listActiveSelectionForeground }, + listActiveSelectionForeground, nls.localize('menuSelectionForeground', "Foreground color of the selected menu item in menus.")); export const menuSelectionBackground = registerColor('menu.selectionBackground', - { dark: listActiveSelectionBackground, light: listActiveSelectionBackground, hcDark: listActiveSelectionBackground, hcLight: listActiveSelectionBackground }, + listActiveSelectionBackground, nls.localize('menuSelectionBackground', "Background color of the selected menu item in menus.")); export const menuSelectionBorder = registerColor('menu.selectionBorder', diff --git a/patched-vscode/src/vs/platform/theme/common/colors/minimapColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/minimapColors.ts index 0b051994..ade38578 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/minimapColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/minimapColors.ts @@ -39,21 +39,21 @@ export const minimapError = registerColor('minimap.errorHighlight', nls.localize('minimapError', 'Minimap marker color for errors.')); export const minimapBackground = registerColor('minimap.background', - { dark: null, light: null, hcDark: null, hcLight: null }, + null, nls.localize('minimapBackground', "Minimap background color.")); export const minimapForegroundOpacity = registerColor('minimap.foregroundOpacity', - { dark: Color.fromHex('#000f'), light: Color.fromHex('#000f'), hcDark: Color.fromHex('#000f'), hcLight: Color.fromHex('#000f') }, + Color.fromHex('#000f'), nls.localize('minimapForegroundOpacity', 'Opacity of foreground elements rendered in the minimap. For example, "#000000c0" will render the elements with 75% opacity.')); export const minimapSliderBackground = registerColor('minimapSlider.background', - { light: transparent(scrollbarSliderBackground, 0.5), dark: transparent(scrollbarSliderBackground, 0.5), hcDark: transparent(scrollbarSliderBackground, 0.5), hcLight: transparent(scrollbarSliderBackground, 0.5) }, + transparent(scrollbarSliderBackground, 0.5), nls.localize('minimapSliderBackground', "Minimap slider background color.")); export const minimapSliderHoverBackground = registerColor('minimapSlider.hoverBackground', - { light: transparent(scrollbarSliderHoverBackground, 0.5), dark: transparent(scrollbarSliderHoverBackground, 0.5), hcDark: transparent(scrollbarSliderHoverBackground, 0.5), hcLight: transparent(scrollbarSliderHoverBackground, 0.5) }, + transparent(scrollbarSliderHoverBackground, 0.5), nls.localize('minimapSliderHoverBackground', "Minimap slider background color when hovering.")); export const minimapSliderActiveBackground = registerColor('minimapSlider.activeBackground', - { light: transparent(scrollbarSliderActiveBackground, 0.5), dark: transparent(scrollbarSliderActiveBackground, 0.5), hcDark: transparent(scrollbarSliderActiveBackground, 0.5), hcLight: transparent(scrollbarSliderActiveBackground, 0.5) }, + transparent(scrollbarSliderActiveBackground, 0.5), nls.localize('minimapSliderActiveBackground', "Minimap slider background color when clicked on.")); diff --git a/patched-vscode/src/vs/platform/theme/common/colors/miscColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/miscColors.ts index 5a2ea49b..42a00e23 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/miscColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/miscColors.ts @@ -16,7 +16,7 @@ import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colors/bas // ----- sash export const sashHoverBorder = registerColor('sash.hoverBorder', - { dark: focusBorder, light: focusBorder, hcDark: focusBorder, hcLight: focusBorder }, + focusBorder, nls.localize('sashActiveBorder', "Border color of active sashes.")); diff --git a/patched-vscode/src/vs/platform/theme/common/colors/quickpickColors.ts b/patched-vscode/src/vs/platform/theme/common/colors/quickpickColors.ts index 7f8fc271..3b109a21 100644 --- a/patched-vscode/src/vs/platform/theme/common/colors/quickpickColors.ts +++ b/patched-vscode/src/vs/platform/theme/common/colors/quickpickColors.ts @@ -15,11 +15,11 @@ import { listActiveSelectionBackground, listActiveSelectionForeground, listActiv export const quickInputBackground = registerColor('quickInput.background', - { dark: editorWidgetBackground, light: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, + editorWidgetBackground, nls.localize('pickerBackground', "Quick picker background color. The quick picker widget is the container for pickers like the command palette.")); export const quickInputForeground = registerColor('quickInput.foreground', - { dark: editorWidgetForeground, light: editorWidgetForeground, hcDark: editorWidgetForeground, hcLight: editorWidgetForeground }, + editorWidgetForeground, nls.localize('pickerForeground', "Quick picker foreground color. The quick picker widget is the container for pickers like the command palette.")); export const quickInputTitleBackground = registerColor('quickInputTitle.background', @@ -35,15 +35,15 @@ export const pickerGroupBorder = registerColor('pickerGroup.border', nls.localize('pickerGroupBorder', "Quick picker color for grouping borders.")); export const _deprecatedQuickInputListFocusBackground = registerColor('quickInput.list.focusBackground', - { dark: null, light: null, hcDark: null, hcLight: null }, '', undefined, + null, '', undefined, nls.localize('quickInput.list.focusBackground deprecation', "Please use quickInputList.focusBackground instead")); export const quickInputListFocusForeground = registerColor('quickInputList.focusForeground', - { dark: listActiveSelectionForeground, light: listActiveSelectionForeground, hcDark: listActiveSelectionForeground, hcLight: listActiveSelectionForeground }, + listActiveSelectionForeground, nls.localize('quickInput.listFocusForeground', "Quick picker foreground color for the focused item.")); export const quickInputListFocusIconForeground = registerColor('quickInputList.focusIconForeground', - { dark: listActiveSelectionIconForeground, light: listActiveSelectionIconForeground, hcDark: listActiveSelectionIconForeground, hcLight: listActiveSelectionIconForeground }, + listActiveSelectionIconForeground, nls.localize('quickInput.listFocusIconForeground', "Quick picker icon foreground color for the focused item.")); export const quickInputListFocusBackground = registerColor('quickInputList.focusBackground', diff --git a/patched-vscode/src/vs/platform/theme/electron-main/themeMainService.ts b/patched-vscode/src/vs/platform/theme/electron-main/themeMainService.ts index caef715f..e332feed 100644 --- a/patched-vscode/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/patched-vscode/src/vs/platform/theme/electron-main/themeMainService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, nativeTheme } from 'electron'; +import electron from 'electron'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; @@ -64,30 +64,30 @@ export class ThemeMainService extends Disposable implements IThemeMainService { this.updateSystemColorTheme(); // Color Scheme changes - this._register(Event.fromNodeEventEmitter(nativeTheme, 'updated')(() => this._onDidChangeColorScheme.fire(this.getColorScheme()))); + this._register(Event.fromNodeEventEmitter(electron.nativeTheme, 'updated')(() => this._onDidChangeColorScheme.fire(this.getColorScheme()))); } private updateSystemColorTheme(): void { if (isLinux || this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { // only with `system` we can detect the system color scheme - nativeTheme.themeSource = 'system'; + electron.nativeTheme.themeSource = 'system'; } else { switch (this.configurationService.getValue<'default' | 'auto' | 'light' | 'dark'>(ThemeSettings.SYSTEM_COLOR_THEME)) { case 'dark': - nativeTheme.themeSource = 'dark'; + electron.nativeTheme.themeSource = 'dark'; break; case 'light': - nativeTheme.themeSource = 'light'; + electron.nativeTheme.themeSource = 'light'; break; case 'auto': switch (this.getBaseTheme()) { - case 'vs': nativeTheme.themeSource = 'light'; break; - case 'vs-dark': nativeTheme.themeSource = 'dark'; break; - default: nativeTheme.themeSource = 'system'; + case 'vs': electron.nativeTheme.themeSource = 'light'; break; + case 'vs-dark': electron.nativeTheme.themeSource = 'dark'; break; + default: electron.nativeTheme.themeSource = 'system'; } break; default: - nativeTheme.themeSource = 'system'; + electron.nativeTheme.themeSource = 'system'; break; } @@ -97,23 +97,23 @@ export class ThemeMainService extends Disposable implements IThemeMainService { getColorScheme(): IColorScheme { if (isWindows) { // high contrast is refelected by the shouldUseInvertedColorScheme property - if (nativeTheme.shouldUseHighContrastColors) { + if (electron.nativeTheme.shouldUseHighContrastColors) { // shouldUseInvertedColorScheme is dark, !shouldUseInvertedColorScheme is light - return { dark: nativeTheme.shouldUseInvertedColorScheme, highContrast: true }; + return { dark: electron.nativeTheme.shouldUseInvertedColorScheme, highContrast: true }; } } else if (isMacintosh) { // high contrast is set if one of shouldUseInvertedColorScheme or shouldUseHighContrastColors is set, reflecting the 'Invert colours' and `Increase contrast` settings in MacOS - if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) { - return { dark: nativeTheme.shouldUseDarkColors, highContrast: true }; + if (electron.nativeTheme.shouldUseInvertedColorScheme || electron.nativeTheme.shouldUseHighContrastColors) { + return { dark: electron.nativeTheme.shouldUseDarkColors, highContrast: true }; } } else if (isLinux) { // ubuntu gnome seems to have 3 states, light dark and high contrast - if (nativeTheme.shouldUseHighContrastColors) { + if (electron.nativeTheme.shouldUseHighContrastColors) { return { dark: true, highContrast: true }; } } return { - dark: nativeTheme.shouldUseDarkColors, + dark: electron.nativeTheme.shouldUseDarkColors, highContrast: false }; } @@ -170,7 +170,7 @@ export class ThemeMainService extends Disposable implements IThemeMainService { } private updateBackgroundColor(windowId: number, splash: IPartsSplash): void { - for (const window of BrowserWindow.getAllWindows()) { + for (const window of electron.BrowserWindow.getAllWindows()) { if (window.id === windowId) { window.setBackgroundColor(splash.colorInfo.background); break; diff --git a/patched-vscode/src/vs/platform/tunnel/common/tunnel.ts b/patched-vscode/src/vs/platform/tunnel/common/tunnel.ts index 86b4da4b..b1433f2e 100644 --- a/patched-vscode/src/vs/platform/tunnel/common/tunnel.ts +++ b/patched-vscode/src/vs/platform/tunnel/common/tunnel.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { OperatingSystem } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -215,7 +215,7 @@ export class DisposableTunnel { } } -export abstract class AbstractTunnelService implements ITunnelService { +export abstract class AbstractTunnelService extends Disposable implements ITunnelService { declare readonly _serviceBrand: undefined; private _onTunnelOpened: Emitter = new Emitter(); @@ -234,7 +234,7 @@ export abstract class AbstractTunnelService implements ITunnelService { public constructor( @ILogService protected readonly logService: ILogService, @IConfigurationService protected readonly configurationService: IConfigurationService - ) { } + ) { super(); } get hasTunnelProvider(): boolean { return !!this._tunnelProvider; @@ -308,7 +308,8 @@ export abstract class AbstractTunnelService implements ITunnelService { return tunnels; } - async dispose(): Promise { + override async dispose(): Promise { + super.dispose(); for (const portMap of this._tunnels.values()) { for (const { value } of portMap.values()) { await value.then(tunnel => typeof tunnel !== 'string' ? tunnel?.dispose() : undefined); diff --git a/patched-vscode/src/vs/platform/tunnel/test/common/tunnel.test.ts b/patched-vscode/src/vs/platform/tunnel/test/common/tunnel.test.ts index d86d3f47..ae32707e 100644 --- a/patched-vscode/src/vs/platform/tunnel/test/common/tunnel.test.ts +++ b/patched-vscode/src/vs/platform/tunnel/test/common/tunnel.test.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 * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { extractLocalHostUriMetaDataForPortMapping, diff --git a/patched-vscode/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts b/patched-vscode/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts index 27d69e4e..32d33a7d 100644 --- a/patched-vscode/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts +++ b/patched-vscode/src/vs/platform/undoRedo/test/common/undoRedoService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/platform/update/common/update.config.contribution.ts b/patched-vscode/src/vs/platform/update/common/update.config.contribution.ts index d7181f8f..4134233d 100644 --- a/patched-vscode/src/vs/platform/update/common/update.config.contribution.ts +++ b/patched-vscode/src/vs/platform/update/common/update.config.contribution.ts @@ -18,7 +18,7 @@ configurationRegistry.registerConfiguration({ 'update.mode': { type: 'string', enum: ['none', 'manual', 'start', 'default'], - default: 'none', + default: 'default', scope: ConfigurationScope.APPLICATION, description: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), tags: ['usesOnlineServices'], @@ -50,7 +50,7 @@ configurationRegistry.registerConfiguration({ }, 'update.showReleaseNotes': { type: 'boolean', - default: false, + default: true, scope: ConfigurationScope.APPLICATION, description: localize('showReleaseNotes', "Show Release Notes after an update. The Release Notes are fetched from a Microsoft online service."), tags: ['usesOnlineServices'] diff --git a/patched-vscode/src/vs/platform/update/electron-main/updateService.win32.ts b/patched-vscode/src/vs/platform/update/electron-main/updateService.win32.ts index 4c49a758..a2561be0 100644 --- a/patched-vscode/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/patched-vscode/src/vs/platform/update/electron-main/updateService.win32.ts @@ -55,7 +55,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun @memoize get cachePath(): Promise { const result = path.join(tmpdir(), `vscode-${this.productService.quality}-${this.productService.target}-${process.arch}`); - return pfs.Promises.mkdir(result, { recursive: true }).then(() => result); + return fs.promises.mkdir(result, { recursive: true }).then(() => result); } constructor( @@ -197,7 +197,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun const promises = versions.filter(filter).map(async one => { try { - await pfs.Promises.unlink(path.join(cachePath, one)); + await fs.promises.unlink(path.join(cachePath, one)); } catch (err) { // ignore } diff --git a/patched-vscode/src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts b/patched-vscode/src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts index 32dde9e1..339e86bf 100644 --- a/patched-vscode/src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts +++ b/patched-vscode/src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { mock } from 'vs/base/test/common/mock'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; diff --git a/patched-vscode/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts b/patched-vscode/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts index b6a05ce5..8f0c5641 100644 --- a/patched-vscode/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts +++ b/patched-vscode/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; diff --git a/patched-vscode/src/vs/platform/userDataProfile/common/userDataProfile.ts b/patched-vscode/src/vs/platform/userDataProfile/common/userDataProfile.ts index b65c078f..f18ae097 100644 --- a/patched-vscode/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/patched-vscode/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -168,6 +168,10 @@ export type UserDataProfilesObject = { emptyWindows: Map; }; +type TransientUserDataProfilesObject = UserDataProfilesObject & { + folders: ResourceMap; +}; + export type StoredUserDataProfile = { name: string; location: URI; @@ -209,8 +213,9 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf private profileCreationPromises = new Map>(); - protected readonly transientProfilesObject: UserDataProfilesObject = { + protected readonly transientProfilesObject: TransientUserDataProfilesObject = { profiles: [], + folders: new ResourceMap(), workspaces: new ResourceMap(), emptyWindows: new Map() }; @@ -454,6 +459,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf } async resetWorkspaces(): Promise { + this.transientProfilesObject.folders.clear(); this.transientProfilesObject.workspaces.clear(); this.transientProfilesObject.emptyWindows.clear(); this.profilesObject.workspaces.clear(); @@ -484,7 +490,17 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf getProfileForWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier): IUserDataProfile | undefined { const workspace = this.getWorkspace(workspaceIdentifier); - return URI.isUri(workspace) ? this.transientProfilesObject.workspaces.get(workspace) ?? this.profilesObject.workspaces.get(workspace) : this.transientProfilesObject.emptyWindows.get(workspace) ?? this.profilesObject.emptyWindows.get(workspace); + const profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindows.get(workspace); + if (profile) { + return profile; + } + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { + return this.transientProfilesObject.folders.get(workspaceIdentifier.uri); + } + if (isWorkspaceIdentifier(workspaceIdentifier)) { + return this.transientProfilesObject.workspaces.get(workspaceIdentifier.configPath); + } + return this.transientProfilesObject.emptyWindows.get(workspaceIdentifier.id); } protected getWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier): URI | string { @@ -498,16 +514,19 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf } private isProfileAssociatedToWorkspace(profile: IUserDataProfile): boolean { - if ([...this.transientProfilesObject.emptyWindows.values()].some(windowProfile => this.uriIdentityService.extUri.isEqual(windowProfile.location, profile.location))) { + if ([...this.profilesObject.emptyWindows.values()].some(windowProfile => this.uriIdentityService.extUri.isEqual(windowProfile.location, profile.location))) { return true; } - if ([...this.transientProfilesObject.workspaces.values()].some(workspaceProfile => this.uriIdentityService.extUri.isEqual(workspaceProfile.location, profile.location))) { + if ([...this.profilesObject.workspaces.values()].some(workspaceProfile => this.uriIdentityService.extUri.isEqual(workspaceProfile.location, profile.location))) { return true; } - if ([...this.profilesObject.emptyWindows.values()].some(windowProfile => this.uriIdentityService.extUri.isEqual(windowProfile.location, profile.location))) { + if ([...this.transientProfilesObject.emptyWindows.values()].some(windowProfile => this.uriIdentityService.extUri.isEqual(windowProfile.location, profile.location))) { return true; } - if ([...this.profilesObject.workspaces.values()].some(workspaceProfile => this.uriIdentityService.extUri.isEqual(workspaceProfile.location, profile.location))) { + if ([...this.transientProfilesObject.workspaces.values()].some(workspaceProfile => this.uriIdentityService.extUri.isEqual(workspaceProfile.location, profile.location))) { + return true; + } + if ([...this.transientProfilesObject.folders.values()].some(workspaceProfile => this.uriIdentityService.extUri.isEqual(workspaceProfile.location, profile.location))) { return true; } return false; @@ -516,6 +535,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[], updated: IUserDataProfile[]): void { const allProfiles = [...this.profiles, ...added]; const storedProfiles: StoredUserDataProfile[] = []; + const transientProfiles = this.transientProfilesObject.profiles; this.transientProfilesObject.profiles = []; for (let profile of allProfiles) { if (profile.isDefault) { @@ -525,9 +545,30 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf continue; } profile = updated.find(p => profile.id === p.id) ?? profile; + const transientProfile = transientProfiles.find(p => profile.id === p.id); if (profile.isTransient) { this.transientProfilesObject.profiles.push(profile); } else { + if (transientProfile) { + for (const [windowId, p] of this.transientProfilesObject.emptyWindows.entries()) { + if (profile.id === p.id) { + this.updateWorkspaceAssociation({ id: windowId }, profile); + break; + } + } + for (const [workspace, p] of this.transientProfilesObject.workspaces.entries()) { + if (profile.id === p.id) { + this.updateWorkspaceAssociation({ id: '', configPath: workspace }, profile); + break; + } + } + for (const [folder, p] of this.transientProfilesObject.folders.entries()) { + if (profile.id === p.id) { + this.updateWorkspaceAssociation({ id: '', uri: folder }, profile); + break; + } + } + } storedProfiles.push({ location: profile.location, name: profile.name, shortName: profile.shortName, icon: profile.icon, useDefaultFlags: profile.useDefaultFlags }); } } @@ -544,30 +585,48 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf // Force transient if the new profile to associate is transient transient = newProfile?.isTransient ? true : transient; - if (!transient) { - // Unset the transiet workspace association if any - this.updateWorkspaceAssociation(workspaceIdentifier, undefined, true); - } + if (transient) { + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { + this.transientProfilesObject.folders.delete(workspaceIdentifier.uri); + if (newProfile) { + this.transientProfilesObject.folders.set(workspaceIdentifier.uri, newProfile); + } + } - const workspace = this.getWorkspace(workspaceIdentifier); - const profilesObject = transient ? this.transientProfilesObject : this.profilesObject; + else if (isWorkspaceIdentifier(workspaceIdentifier)) { + this.transientProfilesObject.workspaces.delete(workspaceIdentifier.configPath); + if (newProfile) { + this.transientProfilesObject.workspaces.set(workspaceIdentifier.configPath, newProfile); + } + } - // Folder or Multiroot workspace - if (URI.isUri(workspace)) { - profilesObject.workspaces.delete(workspace); - if (newProfile) { - profilesObject.workspaces.set(workspace, newProfile); + else { + this.transientProfilesObject.emptyWindows.delete(workspaceIdentifier.id); + if (newProfile) { + this.transientProfilesObject.emptyWindows.set(workspaceIdentifier.id, newProfile); + } } } - // Empty Window + else { - profilesObject.emptyWindows.delete(workspace); - if (newProfile) { - profilesObject.emptyWindows.set(workspace, newProfile); - } - } + // Unset the transiet workspace association if any + this.updateWorkspaceAssociation(workspaceIdentifier, undefined, true); + const workspace = this.getWorkspace(workspaceIdentifier); - if (!transient) { + // Folder or Multiroot workspace + if (URI.isUri(workspace)) { + this.profilesObject.workspaces.delete(workspace); + if (newProfile) { + this.profilesObject.workspaces.set(workspace, newProfile); + } + } + // Empty Window + else { + this.profilesObject.emptyWindows.delete(workspace); + if (newProfile) { + this.profilesObject.emptyWindows.set(workspace, newProfile); + } + } this.updateStoredProfileAssociations(); } } diff --git a/patched-vscode/src/vs/platform/userDataProfile/common/userDataProfileStorageService.ts b/patched-vscode/src/vs/platform/userDataProfile/common/userDataProfileStorageService.ts index a04c44f9..a9a7b377 100644 --- a/patched-vscode/src/vs/platform/userDataProfile/common/userDataProfileStorageService.ts +++ b/patched-vscode/src/vs/platform/userDataProfile/common/userDataProfileStorageService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, MutableDisposable, isDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, MutableDisposable, isDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IStorage, IStorageDatabase, Storage } from 'vs/base/parts/storage/common/storage'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { AbstractStorageService, IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget, isProfileUsingDefaultStorage } from 'vs/platform/storage/common/storage'; @@ -63,10 +63,16 @@ export abstract class AbstractUserDataProfileStorageService extends Disposable i readonly abstract onDidChange: Event; + private readonly storageServicesMap: DisposableMap | undefined; + constructor( + persistStorages: boolean, @IStorageService protected readonly storageService: IStorageService ) { super(); + if (persistStorages) { + this.storageServicesMap = this._register(new DisposableMap()); + } } async readStorageData(profile: IUserDataProfile): Promise> { @@ -82,16 +88,30 @@ export abstract class AbstractUserDataProfileStorageService extends Disposable i return fn(this.storageService); } - const storageDatabase = await this.createStorageDatabase(profile); - const storageService = new StorageService(storageDatabase); + let storageService = this.storageServicesMap?.get(profile.id); + if (!storageService) { + storageService = new StorageService(this.createStorageDatabase(profile)); + this.storageServicesMap?.set(profile.id, storageService); + + try { + await storageService.initialize(); + } catch (error) { + if (this.storageServicesMap?.has(profile.id)) { + this.storageServicesMap.deleteAndDispose(profile.id); + } else { + storageService.dispose(); + } + throw error; + } + } try { - await storageService.initialize(); const result = await fn(storageService); await storageService.flush(); return result; } finally { - storageService.dispose(); - await this.closeAndDispose(storageDatabase); + if (!this.storageServicesMap?.has(profile.id)) { + storageService.dispose(); + } } } @@ -111,16 +131,6 @@ export abstract class AbstractUserDataProfileStorageService extends Disposable i storageService.storeAll(Array.from(items.entries()).map(([key, value]) => ({ key, value, scope: StorageScope.PROFILE, target })), true); } - protected async closeAndDispose(storageDatabase: IStorageDatabase): Promise { - try { - await storageDatabase.close(); - } finally { - if (isDisposable(storageDatabase)) { - storageDatabase.dispose(); - } - } - } - protected abstract createStorageDatabase(profile: IUserDataProfile): Promise; } @@ -130,12 +140,13 @@ export class RemoteUserDataProfileStorageService extends AbstractUserDataProfile readonly onDidChange: Event; constructor( + persistStorages: boolean, private readonly remoteService: IRemoteService, userDataProfilesService: IUserDataProfilesService, storageService: IStorageService, logService: ILogService, ) { - super(storageService); + super(persistStorages, storageService); const channel = remoteService.getChannel('profileStorageListener'); const disposable = this._register(new MutableDisposable()); @@ -164,14 +175,26 @@ export class RemoteUserDataProfileStorageService extends AbstractUserDataProfile class StorageService extends AbstractStorageService { - private readonly profileStorage: IStorage; + private profileStorage: IStorage | undefined; - constructor(profileStorageDatabase: IStorageDatabase) { + constructor(private readonly profileStorageDatabase: Promise) { super({ flushInterval: 100 }); - this.profileStorage = this._register(new Storage(profileStorageDatabase)); } - protected doInitialize(): Promise { + protected async doInitialize(): Promise { + const profileStorageDatabase = await this.profileStorageDatabase; + const profileStorage = new Storage(profileStorageDatabase); + this._register(profileStorage.onDidChangeStorage(e => { + this.emitDidChangeValue(StorageScope.PROFILE, e); + })); + this._register(toDisposable(() => { + profileStorage.close(); + profileStorage.dispose(); + if (isDisposable(profileStorageDatabase)) { + profileStorageDatabase.dispose(); + } + })); + this.profileStorage = profileStorage; return this.profileStorage.init(); } diff --git a/patched-vscode/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts b/patched-vscode/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts index 0679efcc..313bedc9 100644 --- a/patched-vscode/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts +++ b/patched-vscode/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts @@ -18,7 +18,7 @@ export class NativeUserDataProfileStorageService extends RemoteUserDataProfileSt @IStorageService storageService: IStorageService, @ILogService logService: ILogService, ) { - super(mainProcessService, userDataProfilesService, storageService, logService); + super(false, mainProcessService, userDataProfilesService, storageService, logService); } } diff --git a/patched-vscode/src/vs/platform/userDataProfile/node/userDataProfile.ts b/patched-vscode/src/vs/platform/userDataProfile/node/userDataProfile.ts index b5fc8592..e8de67d7 100644 --- a/patched-vscode/src/vs/platform/userDataProfile/node/userDataProfile.ts +++ b/patched-vscode/src/vs/platform/userDataProfile/node/userDataProfile.ts @@ -93,7 +93,7 @@ export class UserDataProfilesService extends UserDataProfilesReadonlyService imp result[URI.revive(workspace).toString()] = URI.revive(profile).toString(); return result; }, {}); - this.stateService.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, { workspaces }); + this.stateService.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, { workspaces } satisfies StoredProfileAssociations); } const associations = super.getStoredProfileAssociations(); if (!this.stateService.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_MIGRATION_KEY, false)) { diff --git a/patched-vscode/src/vs/platform/userDataProfile/node/userDataProfileStorageService.ts b/patched-vscode/src/vs/platform/userDataProfile/node/userDataProfileStorageService.ts index 3b37d056..703011c9 100644 --- a/patched-vscode/src/vs/platform/userDataProfile/node/userDataProfileStorageService.ts +++ b/patched-vscode/src/vs/platform/userDataProfile/node/userDataProfileStorageService.ts @@ -9,7 +9,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; import { RemoteUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; -export class NativeUserDataProfileStorageService extends RemoteUserDataProfileStorageService { +export class SharedProcessUserDataProfileStorageService extends RemoteUserDataProfileStorageService { constructor( @IMainProcessService mainProcessService: IMainProcessService, @@ -17,6 +17,6 @@ export class NativeUserDataProfileStorageService extends RemoteUserDataProfileSt @IStorageService storageService: IStorageService, @ILogService logService: ILogService, ) { - super(mainProcessService, userDataProfilesService, storageService, logService); + super(true, mainProcessService, userDataProfilesService, storageService, logService); } } diff --git a/patched-vscode/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts b/patched-vscode/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts index 7e898dd1..a7e2da87 100644 --- a/patched-vscode/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts +++ b/patched-vscode/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; diff --git a/patched-vscode/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts b/patched-vscode/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts index 48a68ee9..36cfb4ef 100644 --- a/patched-vscode/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts +++ b/patched-vscode/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { InMemoryStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest, Storage } from 'vs/base/parts/storage/common/storage'; @@ -43,7 +43,6 @@ export class TestUserDataProfileStorageService extends AbstractUserDataProfileSt return this.createStorageDatabase(profile); } - protected override async closeAndDispose(): Promise { } } suite('ProfileStorageService', () => { @@ -54,7 +53,7 @@ suite('ProfileStorageService', () => { let storage: Storage; setup(async () => { - testObject = disposables.add(new TestUserDataProfileStorageService(disposables.add(new InMemoryStorageService()))); + testObject = disposables.add(new TestUserDataProfileStorageService(false, disposables.add(new InMemoryStorageService()))); storage = disposables.add(new Storage(await testObject.setupStorageDatabase(profile))); await storage.init(); }); diff --git a/patched-vscode/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts b/patched-vscode/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts index 515b2ce8..8797fafc 100644 --- a/patched-vscode/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts +++ b/patched-vscode/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; diff --git a/patched-vscode/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/patched-vscode/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 9f52f0fd..b62a1be3 100644 --- a/patched-vscode/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/patched-vscode/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -592,7 +592,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa return { syncResource: this.resource, profile: this.syncResource.profile, remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine: isRemoteDataFromCurrentMachine }; } - async getLastSyncUserData(): Promise { + async getLastSyncUserData(): Promise { let storedLastSyncUserDataStateContent = this.getStoredLastSyncUserDataStateContent(); if (!storedLastSyncUserDataStateContent) { storedLastSyncUserDataStateContent = await this.migrateLastSyncUserData(); @@ -664,7 +664,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa return { ...lastSyncUserDataState, syncData, - } as T; + }; } protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary = {}): Promise { diff --git a/patched-vscode/src/vs/platform/userDataSync/common/extensionsSync.ts b/patched-vscode/src/vs/platform/userDataSync/common/extensionsSync.ts index 53561e24..7af2df91 100644 --- a/patched-vscode/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/patched-vscode/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -533,7 +533,7 @@ export class LocalExtensionsProvider { addToSkipped.push(e); this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension`, gallery.displayName || gallery.identifier.id); } - if (error instanceof ExtensionManagementError && [ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform].includes(error.code)) { + if (error instanceof ExtensionManagementError && [ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleApi, ExtensionManagementErrorCode.IncompatibleTargetPlatform].includes(error.code)) { this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, gallery.displayName || gallery.identifier.id); } else if (error) { this.logService.error(error); diff --git a/patched-vscode/src/vs/platform/userDataSync/common/globalStateSync.ts b/patched-vscode/src/vs/platform/userDataSync/common/globalStateSync.ts index 4c698553..7a28512a 100644 --- a/patched-vscode/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/patched-vscode/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -244,7 +244,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (remoteChange !== Change.None) { // update remote this.logService.trace(`${this.syncResourceLogLabel}: Updating remote ui state...`); - const content = JSON.stringify({ storage: remote.all }); + const content = JSON.stringify({ storage: remote.all }); remoteUserData = await this.updateRemoteUserData(content, force ? null : remoteUserData.ref); this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state.${remote.added.length ? ` Added: ${remote.added}.` : ''}${remote.updated.length ? ` Updated: ${remote.updated}.` : ''}${remote.removed.length ? ` Removed: ${remote.removed}.` : ''}`); } diff --git a/patched-vscode/src/vs/platform/userDataSync/common/settingsSync.ts b/patched-vscode/src/vs/platform/userDataSync/common/settingsSync.ts index b1c7523e..c9499d33 100644 --- a/patched-vscode/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/patched-vscode/src/vs/platform/userDataSync/common/settingsSync.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct } from 'vs/base/common/arrays'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; @@ -12,6 +13,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -19,7 +21,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { getIgnoredSettings, isEmpty, merge, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest, getIgnoredSettingsForExtension } from 'vs/platform/userDataSync/common/userDataSync'; interface ISettingsResourcePreview extends IFileResourcePreview { previewResult: IMergeResult; @@ -51,7 +53,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( - profile: IUserDataProfile, + private readonly profile: IUserDataProfile, collection: string | undefined, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @@ -315,21 +317,39 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return { settings }; } - private _defaultIgnoredSettings: Promise | undefined = undefined; + private coreIgnoredSettings: Promise | undefined = undefined; + private systemExtensionsIgnoredSettings: Promise | undefined = undefined; + private userExtensionsIgnoredSettings: Promise | undefined = undefined; private async getIgnoredSettings(content?: string): Promise { - if (!this._defaultIgnoredSettings) { - this._defaultIgnoredSettings = this.userDataSyncUtilService.resolveDefaultIgnoredSettings(); + if (!this.coreIgnoredSettings) { + this.coreIgnoredSettings = this.userDataSyncUtilService.resolveDefaultCoreIgnoredSettings(); + } + if (!this.systemExtensionsIgnoredSettings) { + this.systemExtensionsIgnoredSettings = this.getIgnoredSettingForSystemExtensions(); + } + if (!this.userExtensionsIgnoredSettings) { + this.userExtensionsIgnoredSettings = this.getIgnoredSettingForUserExtensions(); const disposable = this._register(Event.any( Event.filter(this.extensionManagementService.onDidInstallExtensions, (e => e.some(({ local }) => !!local))), Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)))(() => { disposable.dispose(); - this._defaultIgnoredSettings = undefined; + this.userExtensionsIgnoredSettings = undefined; })); } - const defaultIgnoredSettings = await this._defaultIgnoredSettings; + const defaultIgnoredSettings = (await Promise.all([this.coreIgnoredSettings, this.systemExtensionsIgnoredSettings, this.userExtensionsIgnoredSettings])).flat(); return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content); } + private async getIgnoredSettingForSystemExtensions(): Promise { + const systemExtensions = await this.extensionManagementService.getInstalled(ExtensionType.System); + return distinct(systemExtensions.map(e => getIgnoredSettingsForExtension(e.manifest)).flat()); + } + + private async getIgnoredSettingForUserExtensions(): Promise { + const userExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User, this.profile.extensionsResource); + return distinct(userExtensions.map(e => getIgnoredSettingsForExtension(e.manifest)).flat()); + } + private validateContent(content: string): void { if (this.hasErrors(content, false)) { throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); diff --git a/patched-vscode/src/vs/platform/userDataSync/common/userDataSync.ts b/patched-vscode/src/vs/platform/userDataSync/common/userDataSync.ts index 08475542..ed81c919 100644 --- a/patched-vscode/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/patched-vscode/src/vs/platform/userDataSync/common/userDataSync.ts @@ -15,9 +15,10 @@ import { isObject, isString } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IHeaders } from 'vs/base/parts/request/common/request'; import { localize } from 'vs/nls'; -import { allSettings, ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { allSettings, ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry, IRegisteredConfigurationPropertySchema, getAllConfigurationProperties, parseScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { EXTENSION_IDENTIFIER_PATTERN, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ILogService } from 'vs/platform/log/common/log'; @@ -30,12 +31,40 @@ export function getDisallowedIgnoredSettings(): string[] { return Object.keys(allSettings).filter(setting => !!allSettings[setting].disallowSyncIgnore); } -export function getDefaultIgnoredSettings(): string[] { +export function getDefaultIgnoredSettings(excludeExtensions: boolean = false): string[] { const allSettings = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); - const ignoreSyncSettings = Object.keys(allSettings).filter(setting => !!allSettings[setting].ignoreSync); - const machineSettings = Object.keys(allSettings).filter(setting => allSettings[setting].scope === ConfigurationScope.MACHINE || allSettings[setting].scope === ConfigurationScope.MACHINE_OVERRIDABLE); + const ignoredSettings = getIgnoredSettings(allSettings, excludeExtensions); const disallowedSettings = getDisallowedIgnoredSettings(); - return distinct([...ignoreSyncSettings, ...machineSettings, ...disallowedSettings]); + return distinct([...ignoredSettings, ...disallowedSettings]); +} + +export function getIgnoredSettingsForExtension(manifest: IExtensionManifest): string[] { + if (!manifest.contributes?.configuration) { + return []; + } + const configurations = Array.isArray(manifest.contributes.configuration) ? manifest.contributes.configuration : [manifest.contributes.configuration]; + if (!configurations.length) { + return []; + } + const properties = getAllConfigurationProperties(configurations); + return getIgnoredSettings(properties, false); +} + +function getIgnoredSettings(properties: IStringDictionary, excludeExtensions: boolean): string[] { + const ignoredSettings = new Set(); + for (const key in properties) { + if (excludeExtensions && !!properties[key].source) { + continue; + } + const scope = isString(properties[key].scope) ? parseScope(properties[key].scope) : properties[key].scope; + if (properties[key].ignoreSync + || scope === ConfigurationScope.MACHINE + || scope === ConfigurationScope.MACHINE_OVERRIDABLE + ) { + ignoredSettings.add(key); + } + } + return [...ignoredSettings.values()]; } export const USER_DATA_SYNC_CONFIGURATION_SCOPE = 'settingsSync'; @@ -591,7 +620,7 @@ export interface IUserDataSyncUtilService { readonly _serviceBrand: undefined; resolveUserBindings(userbindings: string[]): Promise>; resolveFormattingOptions(resource: URI): Promise; - resolveDefaultIgnoredSettings(): Promise; + resolveDefaultCoreIgnoredSettings(): Promise; } export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); diff --git a/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts b/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts index 28d2400c..8c505b3a 100644 --- a/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts +++ b/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts @@ -53,7 +53,7 @@ export class UserDataSyncLocalStoreService extends Disposable implements IUserDa if (stat.children) { for (const child of stat.children) { - if (child.isDirectory && !this.userDataProfilesService.profiles.some(profile => profile.id === child.name)) { + if (child.isDirectory && !ALL_SYNC_RESOURCES.includes(child.name) && !this.userDataProfilesService.profiles.some(profile => profile.id === child.name)) { try { this.logService.info('Deleting non existing profile from backup', child.resource.path); await this.fileService.del(child.resource, { recursive: true }); diff --git a/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncService.ts b/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncService.ts index 85600358..a11d2cb0 100644 --- a/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -702,8 +702,7 @@ class ProfileSynchronizer extends Disposable { const [[synchronizer, , disposable]] = this._enabled.splice(index, 1); disposable.dispose(); this.updateStatus(); - Promise.allSettled([synchronizer.stop(), synchronizer.resetLocal()]) - .then(null, error => this.logService.error(error)); + synchronizer.stop().then(null, error => this.logService.error(error)); } } diff --git a/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 32c4086b..491dad6e 100644 --- a/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/patched-vscode/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -148,7 +148,7 @@ export class UserDataSyncStoreClient extends Disposable { private userDataSyncStoreUrl: URI | undefined; private authToken: { token: string; type: string } | undefined; - private readonly commonHeadersPromise: Promise<{ [key: string]: string }>; + private readonly commonHeadersPromise: Promise; private readonly session: RequestsSession; private _onTokenFailed = this._register(new Emitter()); diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index 3436a31f..1ecc7b90 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { ILocalSyncExtension, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts index 3c61cd98..7a17fac2 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/globalStateMerge.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts index d27a2d8d..7d3ecaca 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index 935c553a..c7a1f867 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { TestUserDataSyncUtilService } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index 47992010..660185fd 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts index 274ac5ee..625df212 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { addSetting, merge, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import type { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index 0cd11020..39246f25 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts index f41039cb..50e5caa5 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/snippetsMerge.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { merge } from 'vs/platform/userDataSync/common/snippetsMerge'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index fc9644c2..97f4ab83 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { IStringDictionary } from 'vs/base/common/collections'; import { dirname, joinPath } from 'vs/base/common/resources'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 1e854038..a336ca32 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Barrier } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/tasksSync.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/tasksSync.test.ts index 73fe0a6b..c6cfd18a 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/tasksSync.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/tasksSync.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index 47cde800..e9c86afd 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { joinPath } from 'vs/base/common/resources'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts index 40cc6f36..e60f5314 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataProfilesManifestMerge.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IUserDataProfile, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts index 2d6d7de2..2227e3d1 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index b622a3ef..f83ecab1 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -26,7 +26,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IRequestService } from 'vs/platform/request/common/request'; +import { AuthInfo, Credentials, IRequestService } from 'vs/platform/request/common/request'; import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -95,7 +95,7 @@ export class UserDataSyncClient extends Disposable { const storageService = this._register(new TestStorageService(userDataProfilesService.defaultProfile)); this.instantiationService.stub(IStorageService, this._register(storageService)); - this.instantiationService.stub(IUserDataProfileStorageService, this._register(new TestUserDataProfileStorageService(storageService))); + this.instantiationService.stub(IUserDataProfileStorageService, this._register(new TestUserDataProfileStorageService(false, storageService))); const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, new NullPolicyService(), logService)); await configurationService.initialize(); @@ -188,6 +188,8 @@ export class UserDataSyncTestServer implements IRequestService { constructor(private readonly rateLimit = Number.MAX_SAFE_INTEGER, private readonly retryAfter?: number) { } async resolveProxy(url: string): Promise { return url; } + async lookupAuthorization(authInfo: AuthInfo): Promise { return undefined; } + async lookupKerberosAuthorization(url: string): Promise { return undefined; } async loadCertificates(): Promise { return []; } async request(options: IRequestOptions, token: CancellationToken): Promise { @@ -355,7 +357,7 @@ export class TestUserDataSyncUtilService implements IUserDataSyncUtilService { _serviceBrand: any; - async resolveDefaultIgnoredSettings(): Promise { + async resolveDefaultCoreIgnoredSettings(): Promise { return getDefaultIgnoredSettings(); } diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index 665d09ed..a06d7112 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { dirname, joinPath } from 'vs/base/common/resources'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index db83d62e..fefa58ad 100644 --- a/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/patched-vscode/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { newWriteableBufferStream } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -414,6 +414,8 @@ suite('UserDataSyncRequestsSession', () => { _serviceBrand: undefined, async request() { return { res: { headers: {} }, stream: newWriteableBufferStream() }; }, async resolveProxy() { return undefined; }, + async lookupAuthorization() { return undefined; }, + async lookupKerberosAuthorization() { return undefined; }, async loadCertificates() { return []; } }; diff --git a/patched-vscode/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/patched-vscode/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index bfa2c0cb..3b1cee5f 100644 --- a/patched-vscode/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/patched-vscode/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -18,6 +18,7 @@ import { removeDangerousEnvVariables } from 'vs/base/common/processes'; import { deepClone } from 'vs/base/common/objects'; import { isWindows } from 'vs/base/common/platform'; import { isUNCAccessRestrictionsDisabled, getUNCHostAllowlist } from 'vs/base/node/unc'; +import { upcast } from 'vs/base/common/types'; export interface IUtilityProcessConfiguration { @@ -76,6 +77,13 @@ export interface IUtilityProcessConfiguration { * the V8 sandbox. */ readonly forceAllocationsToV8Sandbox?: boolean; + + /** + * HTTP 401 and 407 requests created via electron:net module + * will be redirected to the main process and can be handled + * via the app#login event. + */ + readonly respondToAuthRequestsFromMainProcess?: boolean; } export interface IWindowUtilityProcessConfiguration extends IUtilityProcessConfiguration { @@ -235,20 +243,25 @@ export class UtilityProcess extends Disposable { const execArgv = this.configuration.execArgv ?? []; const allowLoadingUnsignedLibraries = this.configuration.allowLoadingUnsignedLibraries; const forceAllocationsToV8Sandbox = this.configuration.forceAllocationsToV8Sandbox; + const respondToAuthRequestsFromMainProcess = this.configuration.respondToAuthRequestsFromMainProcess; const stdio = 'pipe'; const env = this.createEnv(configuration); this.log('creating new...', Severity.Info); // Fork utility process - this.process = utilityProcess.fork(modulePath, args, { + this.process = utilityProcess.fork(modulePath, args, upcast({ serviceName, env, execArgv, allowLoadingUnsignedLibraries, forceAllocationsToV8Sandbox, + respondToAuthRequestsFromMainProcess, stdio - } as ForkOptions & { forceAllocationsToV8Sandbox?: Boolean }); + })); // Register to events this.registerListeners(this.process, this.configuration, serviceName); diff --git a/patched-vscode/src/vs/platform/window/common/window.ts b/patched-vscode/src/vs/platform/window/common/window.ts index 16eef424..dc5de9f2 100644 --- a/patched-vscode/src/vs/platform/window/common/window.ts +++ b/patched-vscode/src/vs/platform/window/common/window.ts @@ -5,7 +5,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { PerformanceMark } from 'vs/base/common/performance'; -import { isLinux, isMacintosh, isNative, isWeb, isWindows } from 'vs/base/common/platform'; +import { isLinux, isMacintosh, isNative, isWeb } from 'vs/base/common/platform'; import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -49,6 +49,9 @@ export interface IBaseOpenWindowsOptions { * If not set, defaults to the remote authority of the current window. */ readonly remoteAuthority?: string | null; + + readonly forceProfile?: string; + readonly forceTempProfile?: boolean; } export interface IOpenWindowOptions extends IBaseOpenWindowsOptions { @@ -64,9 +67,6 @@ export interface IOpenWindowOptions extends IBaseOpenWindowsOptions { readonly gotoLineMode?: boolean; readonly waitMarkerFileURI?: URI; - - readonly forceProfile?: string; - readonly forceTempProfile?: boolean; } export interface IAddFoldersRequest { @@ -160,6 +160,7 @@ export interface IWindowSettings { readonly clickThroughInactive: boolean; readonly newWindowProfile: string; readonly density: IDensitySettings; + readonly experimentalControlOverlay?: boolean; } export interface IDensitySettings { @@ -225,14 +226,21 @@ export function getTitleBarStyle(configurationService: IConfigurationService): T export const DEFAULT_CUSTOM_TITLEBAR_HEIGHT = 35; // includes space for command center export function useWindowControlsOverlay(configurationService: IConfigurationService): boolean { - if (!isWindows || isWeb) { - return false; // only supported on a desktop Windows instance + if (isMacintosh || isWeb) { + return false; // only supported on a Windows/Linux desktop instances } if (hasNativeTitlebar(configurationService)) { return false; // only supported when title bar is custom } + if (isLinux) { + const setting = configurationService.getValue('window.experimentalControlOverlay'); + if (typeof setting === 'boolean') { + return setting; + } + } + // Default to true. return true; } diff --git a/patched-vscode/src/vs/platform/window/electron-main/window.ts b/patched-vscode/src/vs/platform/window/electron-main/window.ts index b63a6117..15fcb179 100644 --- a/patched-vscode/src/vs/platform/window/electron-main/window.ts +++ b/patched-vscode/src/vs/platform/window/electron-main/window.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, Rectangle, screen, WebContents } from 'electron'; +import electron from 'electron'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -23,7 +23,7 @@ export interface IBaseWindow extends IDisposable { readonly onDidClose: Event; readonly id: number; - readonly win: BrowserWindow | null; + readonly win: electron.BrowserWindow | null; readonly lastFocusTime: number; focus(options?: { force: boolean }): void; @@ -41,7 +41,7 @@ export interface IBaseWindow extends IDisposable { updateWindowControls(options: { height?: number; backgroundColor?: string; foregroundColor?: string }): void; - matches(webContents: WebContents): boolean; + matches(webContents: electron.WebContents): boolean; } export interface ICodeWindow extends IBaseWindow { @@ -76,7 +76,7 @@ export interface ICodeWindow extends IBaseWindow { close(): void; - getBounds(): Rectangle; + getBounds(): electron.Rectangle; send(channel: string, ...args: any[]): void; sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void; @@ -158,7 +158,7 @@ export const defaultAuxWindowState = function (): IWindowState { const width = 800; const height = 600; - const workArea = screen.getPrimaryDisplay().workArea; + const workArea = electron.screen.getPrimaryDisplay().workArea; const x = Math.max(workArea.x + (workArea.width / 2) - (width / 2), 0); const y = Math.max(workArea.y + (workArea.height / 2) - (height / 2), 0); diff --git a/patched-vscode/src/vs/platform/windows/electron-main/windowImpl.ts b/patched-vscode/src/vs/platform/windows/electron-main/windowImpl.ts index dcb3a177..5d3b1c57 100644 --- a/patched-vscode/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/patched-vscode/src/vs/platform/windows/electron-main/windowImpl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, BrowserWindow, Display, nativeImage, NativeImage, Rectangle, screen, SegmentedControlSegment, systemPreferences, TouchBar, TouchBarSegmentedControl, WebContents, Event as ElectronEvent } from 'electron'; +import electron, { BrowserWindowConstructorOptions } from 'electron'; import { DeferredPromise, RunOnceScheduler, timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -32,7 +32,7 @@ import { IApplicationStorageMainService, IStorageMainService } from 'vs/platform import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ThemeIcon } from 'vs/base/common/themables'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; -import { getMenuBarVisibility, IFolderToOpen, INativeWindowConfiguration, IWindowSettings, IWorkspaceToOpen, MenuBarVisibility, hasNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay, DEFAULT_CUSTOM_TITLEBAR_HEIGHT } from 'vs/platform/window/common/window'; +import { getMenuBarVisibility, IFolderToOpen, INativeWindowConfiguration, IWindowSettings, IWorkspaceToOpen, MenuBarVisibility, hasNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay, DEFAULT_CUSTOM_TITLEBAR_HEIGHT, TitlebarStyle } from 'vs/platform/window/common/window'; import { defaultBrowserWindowOptions, IWindowsMainService, OpenContext, WindowStateValidator } from 'vs/platform/windows/electron-main/windows'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; @@ -44,6 +44,7 @@ import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electr import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; import { firstOrDefault } from 'vs/base/common/arrays'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { isESM } from 'vs/base/common/amd'; export interface IWindowCreationOptions { readonly state: IWindowState; @@ -51,7 +52,7 @@ export interface IWindowCreationOptions { readonly isExtensionTestHost?: boolean; } -interface ITouchBarSegment extends SegmentedControlSegment { +interface ITouchBarSegment extends electron.SegmentedControlSegment { readonly id: string; } @@ -111,9 +112,9 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { protected _lastFocusTime = Date.now(); // window is shown on creation so take current time get lastFocusTime(): number { return this._lastFocusTime; } - protected _win: BrowserWindow | null = null; + protected _win: electron.BrowserWindow | null = null; get win() { return this._win; } - protected setWin(win: BrowserWindow): void { + protected setWin(win: electron.BrowserWindow, options?: BrowserWindowConstructorOptions): void { this._win = win; // Window Events @@ -131,13 +132,13 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { this._register(Event.fromNodeEventEmitter(this._win, 'leave-full-screen')(() => this._onDidLeaveFullScreen.fire())); // Sheet Offsets - const useCustomTitleStyle = !hasNativeTitlebar(this.configurationService); + const useCustomTitleStyle = !hasNativeTitlebar(this.configurationService, options?.titleBarStyle === 'hidden' ? TitlebarStyle.CUSTOM : undefined /* unknown */); if (isMacintosh && useCustomTitleStyle) { win.setSheetOffset(isBigSurOrNewer(release()) ? 28 : 22); // offset dialogs by the height of the custom title bar if we have any } // Update the window controls immediately based on cached or default values - if (useCustomTitleStyle && ((isWindows && useWindowControlsOverlay(this.configurationService)) || isMacintosh)) { + if (useCustomTitleStyle && (useWindowControlsOverlay(this.configurationService) || isMacintosh)) { const cachedWindowControlHeight = this.stateService.getItem((BaseWindow.windowControlHeightStateStorageKey)); if (cachedWindowControlHeight) { this.updateWindowControls({ height: cachedWindowControlHeight }); @@ -160,7 +161,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { // This sets up a listener for the window hook. This is a Windows-only API provided by electron. win.hookWindowMessage(WM_INITMENU, () => { const [x, y] = win.getPosition(); - const cursorPos = screen.getCursorScreenPoint(); + const cursorPos = electron.screen.getCursorScreenPoint(); const cx = cursorPos.x - x; const cy = cursorPos.y - y; @@ -218,7 +219,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { super(); } - protected applyState(state: IWindowState, hasMultipleDisplays = screen.getAllDisplays().length > 0): void { + protected applyState(state: IWindowState, hasMultipleDisplays = electron.screen.getAllDisplays().length > 0): void { // TODO@electron (Electron 4 regression): when running on multiple displays where the target display // to open the window has a larger resolution than the primary display, the window will not size @@ -232,7 +233,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { const windowSettings = this.configurationService.getValue('window'); const useNativeTabs = isMacintosh && windowSettings?.nativeTabs === true; - if ((isMacintosh || isWindows) && hasMultipleDisplays && (!useNativeTabs || BrowserWindow.getAllWindows().length === 1)) { + if ((isMacintosh || isWindows) && hasMultipleDisplays && (!useNativeTabs || electron.BrowserWindow.getAllWindows().length === 1)) { if ([state.width, state.height, state.x, state.y].every(value => typeof value === 'number')) { this._win?.setBounds({ width: state.width, @@ -299,7 +300,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { focus(options?: { force: boolean }): void { if (isMacintosh && options?.force) { - app.focus({ steal: true }); + electron.app.focus({ steal: true }); } const win = this.win; @@ -322,7 +323,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { // Respect system settings on mac with regards to title click on windows title if (isMacintosh) { - const action = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string'); + const action = electron.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string'); switch (action) { case 'Minimize': win.minimize(); @@ -366,8 +367,8 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { this.stateService.setItem((CodeWindow.windowControlHeightStateStorageKey), options.height); } - // Windows: window control overlay (WCO) - if (isWindows && this.hasWindowControlOverlay) { + // Windows/Linux: window control overlay (WCO) + if (this.hasWindowControlOverlay) { win.setTitleBarOverlay({ color: options.backgroundColor?.trim() === '' ? undefined : options.backgroundColor, symbolColor: options.foregroundColor?.trim() === '' ? undefined : options.foregroundColor, @@ -494,7 +495,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { //#endregion - abstract matches(webContents: WebContents): boolean; + abstract matches(webContents: electron.WebContents): boolean; override dispose(): void { super.dispose(); @@ -524,7 +525,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { private _id: number; get id(): number { return this._id; } - protected override _win: BrowserWindow; + protected override _win: electron.BrowserWindow; get backupPath(): string | undefined { return this._config?.backupPath; } @@ -561,7 +562,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[] = []; - private readonly touchBarGroups: TouchBarSegmentedControl[] = []; + private readonly touchBarGroups: electron.TouchBarSegmentedControl[] = []; private currentHttpProxy: string | undefined = undefined; private currentNoProxy: string | undefined = undefined; @@ -604,7 +605,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { this.windowState = state; this.logService.trace('window#ctor: using window state', state); - const options = instantiationService.invokeFunction(defaultBrowserWindowOptions, this.windowState, { + const options = instantiationService.invokeFunction(defaultBrowserWindowOptions, this.windowState, undefined, { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload.js').fsPath, additionalArguments: [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`], v8CacheOptions: this.environmentMainService.useCodeCache ? 'bypassHeatCheck' : 'none', @@ -612,11 +613,11 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { // Create the browser window mark('code/willCreateCodeBrowserWindow'); - this._win = new BrowserWindow(options); + this._win = new electron.BrowserWindow(options); mark('code/didCreateCodeBrowserWindow'); this._id = this._win.id; - this.setWin(this._win); + this.setWin(this._win, options); // Apply some state after window creation this.applyState(this.windowState, hasMultipleDisplays); @@ -693,7 +694,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { // unloading a window that should not be confused // with the DOM way. // (https://github.com/microsoft/vscode/issues/122736) - this._register(Event.fromNodeEventEmitter(this._win.webContents, 'will-prevent-unload')(event => event.preventDefault())); + this._register(Event.fromNodeEventEmitter(this._win.webContents, 'will-prevent-unload')(event => event.preventDefault())); // Remember that we loaded this._register(Event.fromNodeEventEmitter(this._win.webContents, 'did-finish-load')(() => { @@ -957,16 +958,25 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { } // Proxy - if (!e || e.affectsConfiguration('http.proxy')) { + if (!e || e.affectsConfiguration('http.proxy') || e.affectsConfiguration('http.noProxy')) { let newHttpProxy = (this.configurationService.getValue('http.proxy') || '').trim() || (process.env['https_proxy'] || process.env['HTTPS_PROXY'] || process.env['http_proxy'] || process.env['HTTP_PROXY'] || '').trim() // Not standardized. || undefined; + if (newHttpProxy?.indexOf('@') !== -1) { + const uri = URI.parse(newHttpProxy!); + const i = uri.authority.indexOf('@'); + if (i !== -1) { + newHttpProxy = uri.with({ authority: uri.authority.substring(i + 1) }) + .toString(); + } + } if (newHttpProxy?.endsWith('/')) { newHttpProxy = newHttpProxy.substr(0, newHttpProxy.length - 1); } - const newNoProxy = (process.env['no_proxy'] || process.env['NO_PROXY'] || '').trim() || undefined; // Not standardized. + const newNoProxy = (this.configurationService.getValue('http.noProxy') || []).map((item) => item.trim()).join(',') + || (process.env['no_proxy'] || process.env['NO_PROXY'] || '').trim() || undefined; // Not standardized. if ((newHttpProxy || '').indexOf('@') === -1 && (newHttpProxy !== this.currentHttpProxy || newNoProxy !== this.currentNoProxy)) { this.currentHttpProxy = newHttpProxy; this.currentNoProxy = newNoProxy; @@ -975,13 +985,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { const proxyBypassRules = newNoProxy ? `${newNoProxy},` : ''; this.logService.trace(`Setting proxy to '${proxyRules}', bypassing '${proxyBypassRules}'`); this._win.webContents.session.setProxy({ proxyRules, proxyBypassRules, pacScript: '' }); - type appWithProxySupport = Electron.App & { - setProxy(config: Electron.Config): Promise; - resolveProxy(url: string): Promise; - }; - if (typeof (app as appWithProxySupport).setProxy === 'function') { - (app as appWithProxySupport).setProxy({ proxyRules, proxyBypassRules, pacScript: '' }); - } + electron.app.setProxy({ proxyRules, proxyBypassRules, pacScript: '' }); } } } @@ -1033,7 +1037,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { this.readyState = ReadyState.NAVIGATING; // Load URL - this._win.loadURL(FileAccess.asBrowserUri(`vs/code/electron-sandbox/workbench/workbench${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true)); + this._win.loadURL(FileAccess.asBrowserUri(`vs/code/electron-sandbox/workbench/workbench${this.environmentMainService.isBuilt ? '' : '-dev'}.${isESM ? 'esm.' : ''}html`).toString(true)); // Remember that we did load const wasLoaded = this.wasLoaded; @@ -1132,7 +1136,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { configuration['extensions-dir'] = cli['extensions-dir']; } - configuration.accessibilitySupport = app.isAccessibilitySupportEnabled(); + configuration.accessibilitySupport = electron.app.isAccessibilitySupportEnabled(); configuration.isInitialStartup = false; // since this is a reload configuration.policiesData = this.policyService.serialize(); // set policies data again configuration.continueOn = this.environmentMainService.continueOn; @@ -1186,9 +1190,9 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { // fullscreen gets special treatment if (this.isFullScreen) { - let display: Display | undefined; + let display: electron.Display | undefined; try { - display = screen.getDisplayMatching(this.getBounds()); + display = electron.screen.getDisplayMatching(this.getBounds()); } catch (error) { // Electron has weird conditions under which it throws errors // e.g. https://github.com/microsoft/vscode/issues/100334 when @@ -1233,7 +1237,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { // only consider non-minimized window states if (mode === WindowMode.Normal || mode === WindowMode.Maximized) { - let bounds: Rectangle; + let bounds: electron.Rectangle; if (mode === WindowMode.Normal) { bounds = this.getBounds(); } else { @@ -1262,7 +1266,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { // Window dimensions try { - const displays = screen.getAllDisplays(); + const displays = electron.screen.getAllDisplays(); hasMultipleDisplays = displays.length > 1; state = WindowStateValidator.validateWindowState(this.logService, state, displays); @@ -1276,7 +1280,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { return [state || defaultWindowState(), hasMultipleDisplays]; } - getBounds(): Rectangle { + getBounds(): electron.Rectangle { const [x, y] = this._win.getPosition(); const [width, height] = this._win.getSize(); @@ -1425,16 +1429,16 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { this.touchBarGroups.push(groupTouchBar); } - this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups })); + this._win.setTouchBar(new electron.TouchBar({ items: this.touchBarGroups })); } - private createTouchBarGroup(items: ISerializableCommandAction[] = []): TouchBarSegmentedControl { + private createTouchBarGroup(items: ISerializableCommandAction[] = []): electron.TouchBarSegmentedControl { // Group Segments const segments = this.createTouchBarGroupSegments(items); // Group Control - const control = new TouchBar.TouchBarSegmentedControl({ + const control = new electron.TouchBar.TouchBarSegmentedControl({ segments, mode: 'buttons', segmentStyle: 'automatic', @@ -1448,9 +1452,9 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] { const segments: ITouchBarSegment[] = items.map(item => { - let icon: NativeImage | undefined; + let icon: electron.NativeImage | undefined; if (item.icon && !ThemeIcon.isThemeIcon(item.icon) && item.icon?.dark?.scheme === Schemas.file) { - icon = nativeImage.createFromPath(URI.revive(item.icon.dark).fsPath); + icon = electron.nativeImage.createFromPath(URI.revive(item.icon.dark).fsPath); if (icon.isEmpty()) { icon = undefined; } @@ -1473,7 +1477,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { return segments; } - matches(webContents: WebContents): boolean { + matches(webContents: electron.WebContents): boolean { return this._win?.webContents.id === webContents.id; } diff --git a/patched-vscode/src/vs/platform/windows/electron-main/windows.ts b/patched-vscode/src/vs/platform/windows/electron-main/windows.ts index abfc98bc..5ed14c91 100644 --- a/patched-vscode/src/vs/platform/windows/electron-main/windows.ts +++ b/patched-vscode/src/vs/platform/windows/electron-main/windows.ts @@ -3,22 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindowConstructorOptions, Display, Rectangle, WebContents, WebPreferences, screen } from 'electron'; +import electron from 'electron'; +import { Color } from 'vs/base/common/color'; import { Event } from 'vs/base/common/event'; +import { join } from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ICodeWindow, IWindowState, WindowMode, defaultWindowState } from 'vs/platform/window/electron-main/window'; -import { IOpenEmptyWindowOptions, IWindowOpenable, IWindowSettings, WindowMinimumSize, hasNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay, zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; -import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; -import { IProductService } from 'vs/platform/product/common/productService'; +import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { join } from 'vs/base/common/path'; -import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; -import { Color } from 'vs/base/common/color'; +import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; +import { IOpenEmptyWindowOptions, IWindowOpenable, IWindowSettings, TitlebarStyle, WindowMinimumSize, hasNativeTitlebar, useNativeFullScreen, useWindowControlsOverlay, zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; +import { ICodeWindow, IWindowState, WindowMode, defaultWindowState } from 'vs/platform/window/electron-main/window'; export const IWindowsMainService = createDecorator('windowsMainService'); @@ -53,7 +53,7 @@ export interface IWindowsMainService { getLastActiveWindow(): ICodeWindow | undefined; getWindowById(windowId: number): ICodeWindow | undefined; - getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined; + getWindowByWebContents(webContents: electron.WebContents): ICodeWindow | undefined; } export interface IWindowsCountChangedEvent { @@ -79,7 +79,10 @@ export const enum OpenContext { DESKTOP, // opening through the API - API + API, + + // opening from a protocol link + LINK } export interface IBaseOpenConfiguration { @@ -115,7 +118,12 @@ export interface IOpenConfiguration extends IBaseOpenConfiguration { export interface IOpenEmptyConfiguration extends IBaseOpenConfiguration { } -export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowState: IWindowState, webPreferences?: WebPreferences): BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } { +export interface IDefaultBrowserWindowOptionsOverrides { + forceNativeTitlebar?: boolean; + disableFullscreen?: boolean; +} + +export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowState: IWindowState, overrides?: IDefaultBrowserWindowOptionsOverrides, webPreferences?: electron.WebPreferences): electron.BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } { const themeMainService = accessor.get(IThemeMainService); const productService = accessor.get(IProductService); const configurationService = accessor.get(IConfigurationService); @@ -123,7 +131,7 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt const windowSettings = configurationService.getValue('window'); - const options: BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } = { + const options: electron.BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } = { backgroundColor: themeMainService.getBackgroundColor(), minWidth: WindowMinimumSize.WIDTH, minHeight: WindowMinimumSize.HEIGHT, @@ -161,7 +169,9 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt } } - if (isMacintosh && !useNativeFullScreen(configurationService)) { + if (overrides?.disableFullscreen) { + options.fullscreen = false; + } else if (isMacintosh && !useNativeFullScreen(configurationService)) { options.fullscreenable = false; // enables simple fullscreen mode } @@ -170,7 +180,7 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt options.tabbingIdentifier = productService.nameShort; // this opts in to sierra tabs } - const hideNativeTitleBar = !hasNativeTitlebar(configurationService); + const hideNativeTitleBar = !hasNativeTitlebar(configurationService, overrides?.forceNativeTitlebar ? TitlebarStyle.NATIVE : undefined); if (hideNativeTitleBar) { options.titleBarStyle = 'hidden'; if (!isMacintosh) { @@ -215,7 +225,7 @@ export function getLastFocused(windows: ICodeWindow[] | IAuxiliaryWindow[]): ICo export namespace WindowStateValidator { - export function validateWindowState(logService: ILogService, state: IWindowState, displays = screen.getAllDisplays()): IWindowState | undefined { + export function validateWindowState(logService: ILogService, state: IWindowState, displays = electron.screen.getAllDisplays()): IWindowState | undefined { logService.trace(`window#validateWindowState: validating window state on ${displays.length} display(s)`, state); if ( @@ -313,10 +323,10 @@ export namespace WindowStateValidator { } // Multi Monitor (non-fullscreen): ensure window is within display bounds - let display: Display | undefined; - let displayWorkingArea: Rectangle | undefined; + let display: electron.Display | undefined; + let displayWorkingArea: electron.Rectangle | undefined; try { - display = screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height }); + display = electron.screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height }); displayWorkingArea = getWorkingArea(display); logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); @@ -343,7 +353,7 @@ export namespace WindowStateValidator { return undefined; } - function getWorkingArea(display: Display): Rectangle | undefined { + function getWorkingArea(display: electron.Display): electron.Rectangle | undefined { // Prefer the working area of the display to account for taskbars on the // desktop being positioned somewhere (https://github.com/microsoft/vscode/issues/50830). diff --git a/patched-vscode/src/vs/platform/windows/electron-main/windowsMainService.ts b/patched-vscode/src/vs/platform/windows/electron-main/windowsMainService.ts index 8c9cb2b1..4757e7ee 100644 --- a/patched-vscode/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/patched-vscode/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { app, BrowserWindow, WebContents, shell } from 'electron'; -import { Promises } from 'vs/base/node/pfs'; import { addUNCHostToAllowlist } from 'vs/base/node/unc'; import { hostname, release, arch } from 'os'; import { coalesce, distinct } from 'vs/base/common/arrays'; @@ -22,7 +22,7 @@ import { cwd } from 'vs/base/common/process'; import { extUriBiasedIgnorePathCase, isEqualAuthority, normalizePath, originalFSPath, removeTrailingPathSeparator } from 'vs/base/common/resources'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; +import { getNLSLanguage, getNLSMessages, localize } from 'vs/nls'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -56,6 +56,7 @@ import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electr import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; +import { ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; //#region Helper Interfaces @@ -229,7 +230,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic @IFileService private readonly fileService: IFileService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService, @IThemeMainService private readonly themeMainService: IThemeMainService, - @IAuxiliaryWindowsMainService private readonly auxiliaryWindowsMainService: IAuxiliaryWindowsMainService + @IAuxiliaryWindowsMainService private readonly auxiliaryWindowsMainService: IAuxiliaryWindowsMainService, + @ICSSDevelopmentService private readonly cssDevelopmentService: ICSSDevelopmentService ) { super(); @@ -269,7 +271,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const forceReuseWindow = options?.forceReuseWindow; const forceNewWindow = !forceReuseWindow; - return this.open({ ...openConfig, cli, forceEmpty, forceNewWindow, forceReuseWindow, remoteAuthority }); + return this.open({ ...openConfig, cli, forceEmpty, forceNewWindow, forceReuseWindow, remoteAuthority, forceTempProfile: options?.forceTempProfile, forceProfile: options?.forceProfile }); } openExistingWindow(window: ICodeWindow, openConfig: IOpenConfiguration): void { @@ -297,7 +299,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const emptyWindowsWithBackupsToRestore: IEmptyWindowBackupInfo[] = []; let filesToOpen: IFilesToOpen | undefined; - let emptyToOpen = 0; + let openOneEmptyWindow = false; // Identify things to open from open config const pathsToOpen = await this.getPathsToOpen(openConfig); @@ -321,7 +323,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } else if (path.backupPath) { emptyWindowsWithBackupsToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority }); } else { - emptyToOpen++; + openOneEmptyWindow = true; } } @@ -357,9 +359,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Open based on config - const { windows: usedWindows, filesOpenedInWindow } = await this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyWindowsWithBackupsToRestore, emptyToOpen, filesToOpen, foldersToAdd); + const { windows: usedWindows, filesOpenedInWindow } = await this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyWindowsWithBackupsToRestore, openOneEmptyWindow, filesToOpen, foldersToAdd); - this.logService.trace(`windowsManager#open used window count ${usedWindows.length} (workspacesToOpen: ${workspacesToOpen.length}, foldersToOpen: ${foldersToOpen.length}, emptyToRestore: ${emptyWindowsWithBackupsToRestore.length}, emptyToOpen: ${emptyToOpen})`); + this.logService.trace(`windowsManager#open used window count ${usedWindows.length} (workspacesToOpen: ${workspacesToOpen.length}, foldersToOpen: ${foldersToOpen.length}, emptyToRestore: ${emptyWindowsWithBackupsToRestore.length}, openOneEmptyWindow: ${openOneEmptyWindow})`); // Make sure to pass focus to the most relevant of the windows if we open multiple if (usedWindows.length > 1) { @@ -458,7 +460,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic workspacesToOpen: IWorkspacePathToOpen[], foldersToOpen: ISingleFolderWorkspacePathToOpen[], emptyToRestore: IEmptyWindowBackupInfo[], - emptyToOpen: number, + openOneEmptyWindow: boolean, filesToOpen: IFilesToOpen | undefined, foldersToAdd: ISingleFolderWorkspacePathToOpen[] ): Promise<{ windows: ICodeWindow[]; filesOpenedInWindow: ICodeWindow | undefined }> { @@ -506,7 +508,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // this step. let windowToUseForFiles: ICodeWindow | undefined = undefined; if (fileToCheck?.fileUri && !openFilesInNewWindow) { - if (openConfig.context === OpenContext.DESKTOP || openConfig.context === OpenContext.CLI || openConfig.context === OpenContext.DOCK) { + if (openConfig.context === OpenContext.DESKTOP || openConfig.context === OpenContext.CLI || openConfig.context === OpenContext.DOCK || openConfig.context === OpenContext.LINK) { windowToUseForFiles = await findWindowOnFile(windows, fileToCheck.fileUri, async workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesManagementMainService.resolveLocalWorkspace(workspace.configPath) : undefined); } @@ -627,20 +629,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } } - // Handle empty to open (only if no other window opened) - if (usedWindows.length === 0 || filesToOpen) { - if (filesToOpen && !emptyToOpen) { - emptyToOpen++; - } - + // Open empty window either if enforced or when files still have to open + if (filesToOpen || openOneEmptyWindow) { const remoteAuthority = filesToOpen ? filesToOpen.remoteAuthority : openConfig.remoteAuthority; - for (let i = 0; i < emptyToOpen; i++) { - addUsedWindow(await this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, filesToOpen), !!filesToOpen); - - // any other window to open must open in new window then - openFolderInNewWindow = true; - } + addUsedWindow(await this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, filesToOpen), !!filesToOpen); } return { windows: distinct(usedWindows), filesOpenedInWindow }; @@ -1057,7 +1050,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic path = sanitizeFilePath(normalize(path), cwd()); try { - const pathStat = await Promises.stat(path); + const pathStat = await fs.promises.stat(path); // File if (pathStat.isFile()) { @@ -1444,6 +1437,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic workspace: options.workspace, userEnv: { ...this.initialUserEnv, ...options.userEnv }, + nls: { + messages: getNLSMessages(), + language: getNLSLanguage() + }, + filesToOpenOrCreate: options.filesToOpen?.filesToOpenOrCreate, filesToDiff: options.filesToOpen?.filesToDiff, filesToMerge: options.filesToOpen?.filesToMerge, @@ -1466,7 +1464,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic accessibilitySupport: app.accessibilitySupportEnabled, colorScheme: this.themeMainService.getColorScheme(), policiesData: this.policyService.serialize(), - continueOn: this.environmentMainService.continueOn + continueOn: this.environmentMainService.continueOn, + + cssModules: this.cssDevelopmentService.isEnabled ? await this.cssDevelopmentService.getCssModules() : undefined }; // New window diff --git a/patched-vscode/src/vs/platform/windows/electron-main/windowsStateHandler.ts b/patched-vscode/src/vs/platform/windows/electron-main/windowsStateHandler.ts index df866ea3..6a6591cd 100644 --- a/patched-vscode/src/vs/platform/windows/electron-main/windowsStateHandler.ts +++ b/patched-vscode/src/vs/platform/windows/electron-main/windowsStateHandler.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, Display, screen } from 'electron'; +import electron from 'electron'; import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; @@ -78,26 +78,26 @@ export class WindowsStateHandler extends Disposable { // When a window looses focus, save all windows state. This allows to // prevent loss of window-state data when OS is restarted without properly // shutting down the application (https://github.com/microsoft/vscode/issues/87171) - app.on('browser-window-blur', () => { + electron.app.on('browser-window-blur', () => { if (!this.shuttingDown) { this.saveWindowsState(); } }); // Handle various lifecycle events around windows - this.lifecycleMainService.onBeforeCloseWindow(window => this.onBeforeCloseWindow(window)); - this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); - this.windowsMainService.onDidChangeWindowsCount(e => { + this._register(this.lifecycleMainService.onBeforeCloseWindow(window => this.onBeforeCloseWindow(window))); + this._register(this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown())); + this._register(this.windowsMainService.onDidChangeWindowsCount(e => { if (e.newCount - e.oldCount > 0) { // clear last closed window state when a new window opens. this helps on macOS where // otherwise closing the last window, opening a new window and then quitting would // use the state of the previously closed window when restarting. this.lastClosedState = undefined; } - }); + })); // try to save state before destroy because close will not fire - this.windowsMainService.onDidDestroyWindow(window => this.onBeforeCloseWindow(window)); + this._register(this.windowsMainService.onDidDestroyWindow(window => this.onBeforeCloseWindow(window))); } // Note that onBeforeShutdown() and onBeforeCloseWindow() are fired in different order depending on the OS: @@ -339,8 +339,8 @@ export class WindowsStateHandler extends Disposable { // // We want the new window to open on the same display that the last active one is in - let displayToUse: Display | undefined; - const displays = screen.getAllDisplays(); + let displayToUse: electron.Display | undefined; + const displays = electron.screen.getAllDisplays(); // Single Display if (displays.length === 1) { @@ -352,18 +352,18 @@ export class WindowsStateHandler extends Disposable { // on mac there is 1 menu per window so we need to use the monitor where the cursor currently is if (isMacintosh) { - const cursorPoint = screen.getCursorScreenPoint(); - displayToUse = screen.getDisplayNearestPoint(cursorPoint); + const cursorPoint = electron.screen.getCursorScreenPoint(); + displayToUse = electron.screen.getDisplayNearestPoint(cursorPoint); } // if we have a last active window, use that display for the new window if (!displayToUse && lastActive) { - displayToUse = screen.getDisplayMatching(lastActive.getBounds()); + displayToUse = electron.screen.getDisplayMatching(lastActive.getBounds()); } // fallback to primary display or first display if (!displayToUse) { - displayToUse = screen.getPrimaryDisplay() || displays[0]; + displayToUse = electron.screen.getPrimaryDisplay() || displays[0]; } } diff --git a/patched-vscode/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/patched-vscode/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index 490d6858..f3ce84f9 100644 --- a/patched-vscode/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/patched-vscode/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { join } from 'vs/base/common/path'; diff --git a/patched-vscode/src/vs/platform/windows/test/electron-main/windowsStateHandler.test.ts b/patched-vscode/src/vs/platform/windows/test/electron-main/windowsStateHandler.test.ts index 0b96b1bf..65d1a993 100644 --- a/patched-vscode/src/vs/platform/windows/test/electron-main/windowsStateHandler.test.ts +++ b/patched-vscode/src/vs/platform/windows/test/electron-main/windowsStateHandler.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { tmpdir } from 'os'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/platform/workspace/test/common/workspace.test.ts b/patched-vscode/src/vs/platform/workspace/test/common/workspace.test.ts index 2464d5e4..fb8c1cf1 100644 --- a/patched-vscode/src/vs/platform/workspace/test/common/workspace.test.ts +++ b/patched-vscode/src/vs/platform/workspace/test/common/workspace.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { join } from 'vs/base/common/path'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; diff --git a/patched-vscode/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/patched-vscode/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts index 236f6d6f..bfb4fcd5 100644 --- a/patched-vscode/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts +++ b/patched-vscode/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow } from 'electron'; +import * as fs from 'fs'; +import electron from 'electron'; import { Emitter, Event } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -102,7 +103,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork } resolveLocalWorkspace(uri: URI): Promise { - return this.doResolveLocalWorkspace(uri, path => Promises.readFile(path, 'utf8')); + return this.doResolveLocalWorkspace(uri, path => fs.promises.readFile(path, 'utf8')); } private doResolveLocalWorkspace(uri: URI, contentsFn: (path: string) => string): IResolvedWorkspace | undefined; @@ -169,7 +170,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); const configPath = workspace.configPath.fsPath; - await Promises.mkdir(dirname(configPath), { recursive: true }); + await fs.promises.mkdir(dirname(configPath), { recursive: true }); await Promises.writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t')); this.untitledWorkspaces.push({ workspace, remoteAuthority }); @@ -280,7 +281,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork buttons: [localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK")], message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(workspacePath)), detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.") - }, BrowserWindow.getFocusedWindow() ?? undefined); + }, electron.BrowserWindow.getFocusedWindow() ?? undefined); return false; } diff --git a/patched-vscode/src/vs/platform/workspaces/test/common/workspaces.test.ts b/patched-vscode/src/vs/platform/workspaces/test/common/workspaces.test.ts index a1f2f262..a08f1035 100644 --- a/patched-vscode/src/vs/platform/workspaces/test/common/workspaces.test.ts +++ b/patched-vscode/src/vs/platform/workspaces/test/common/workspaces.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ISerializedSingleFolderWorkspaceIdentifier, ISerializedWorkspaceIdentifier, reviveIdentifier, hasWorkspaceFileExtension, isWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IEmptyWorkspaceIdentifier, toWorkspaceIdentifier, isEmptyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; diff --git a/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts b/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts index 56e4d92f..553be4fa 100644 --- a/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts +++ b/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'vs/base/common/path'; @@ -23,7 +23,7 @@ flakySuite('Workspaces', () => { setup(async () => { testDir = getRandomTestPath(tmpDir, 'vsctests', 'workspacesmanagementmainservice'); - return pfs.Promises.mkdir(testDir, { recursive: true }); + return fs.promises.mkdir(testDir, { recursive: true }); }); teardown(() => { diff --git a/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts b/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts index 2677ffba..f2102c3b 100644 --- a/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts +++ b/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { tmpdir } from 'os'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts b/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts index 0234ce57..92f1ac9f 100644 --- a/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts +++ b/patched-vscode/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as fs from 'fs'; import * as os from 'os'; import { isUNC, toSlashes } from 'vs/base/common/extpath'; @@ -112,7 +112,7 @@ flakySuite('WorkspacesManagementMainService', () => { const fileService = new FileService(logService); service = new WorkspacesManagementMainService(environmentMainService, logService, new UserDataProfilesMainService(new StateService(SaveStrategy.DELAYED, environmentMainService, logService, fileService), new UriIdentityService(fileService), environmentMainService, fileService, logService), new TestBackupMainService(), new TestDialogMainService()); - return pfs.Promises.mkdir(untitledWorkspacesHomePath, { recursive: true }); + return fs.promises.mkdir(untitledWorkspacesHomePath, { recursive: true }); }); teardown(() => { diff --git a/patched-vscode/src/vs/server/node/extensionHostConnection.ts b/patched-vscode/src/vs/server/node/extensionHostConnection.ts index f259ea2c..f345bd69 100644 --- a/patched-vscode/src/vs/server/node/extensionHostConnection.ts +++ b/patched-vscode/src/vs/server/node/extensionHostConnection.ts @@ -5,23 +5,23 @@ import * as cp from 'child_process'; import * as net from 'net'; -import { getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks'; -import { FileAccess } from 'vs/base/common/network'; -import { join, delimiter } from 'vs/base/common/path'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { FileAccess } from 'vs/base/common/network'; +import { delimiter, join } from 'vs/base/common/path'; +import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; +import { removeDangerousEnvVariables } from 'vs/base/common/processes'; import { createRandomIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; -import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection'; -import { IExtHostReadyMessage, IExtHostSocketMessage, IExtHostReduceGraceTimeMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; -import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; -import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; -import { removeDangerousEnvVariables } from 'vs/base/common/processes'; +import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; import { IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { IPCExtHostConnection, writeExtHostConnection, SocketExtHostConnection } from 'vs/workbench/services/extensions/common/extensionHostEnv'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks'; +import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; +import { IPCExtHostConnection, SocketExtHostConnection, writeExtHostConnection } from 'vs/workbench/services/extensions/common/extensionHostEnv'; +import { IExtHostReadyMessage, IExtHostReduceGraceTimeMessage, IExtHostSocketMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; export async function buildUserEnvironment(startParamsEnv: { [key: string]: string | null } = {}, withUserShellEnvironment: boolean, language: string, environmentService: IServerEnvironmentService, logService: ILogService, configurationService: IConfigurationService): Promise { const nlsConfig = await getNLSConfiguration(language, environmentService.userDataPath); @@ -43,7 +43,7 @@ export async function buildUserEnvironment(startParamsEnv: { [key: string]: stri ...{ VSCODE_AMD_ENTRYPOINT: 'vs/workbench/api/node/extensionHostProcess', VSCODE_HANDLES_UNCAUGHT_ERRORS: 'true', - VSCODE_NLS_CONFIG: JSON.stringify(nlsConfig, undefined, 0) + VSCODE_NLS_CONFIG: JSON.stringify(nlsConfig) }, ...startParamsEnv }; @@ -103,7 +103,7 @@ class ConnectionData { } } -export class ExtensionHostConnection { +export class ExtensionHostConnection extends Disposable { private _onClose = new Emitter(); readonly onClose: Event = this._onClose.event; @@ -124,6 +124,7 @@ export class ExtensionHostConnection { @IExtensionHostStatusService private readonly _extensionHostStatusService: IExtensionHostStatusService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { + super(); this._canSendSocket = (!isWindows || !this._environmentService.args['socket-path']); this._disposed = false; this._remoteAddress = remoteAddress; @@ -133,6 +134,11 @@ export class ExtensionHostConnection { this._log(`New connection established.`); } + override dispose(): void { + this._cleanResources(); + super.dispose(); + } + private get _logPrefix(): string { return `[${this._remoteAddress}][${this._reconnectionToken.substr(0, 8)}][ExtensionHostConnection] `; } @@ -271,8 +277,8 @@ export class ExtensionHostConnection { this._extensionHostProcess.stderr!.setEncoding('utf8'); const onStdout = Event.fromNodeEventEmitter(this._extensionHostProcess.stdout!, 'data'); const onStderr = Event.fromNodeEventEmitter(this._extensionHostProcess.stderr!, 'data'); - onStdout((e) => this._log(`<${pid}> ${e}`)); - onStderr((e) => this._log(`<${pid}> ${e}`)); + this._register(onStdout((e) => this._log(`<${pid}> ${e}`))); + this._register(onStderr((e) => this._log(`<${pid}> ${e}`))); // Lifecycle this._extensionHostProcess.on('error', (err) => { diff --git a/patched-vscode/src/vs/server/node/extensionsScannerService.ts b/patched-vscode/src/vs/server/node/extensionsScannerService.ts index 5430ae19..78dc3bce 100644 --- a/patched-vscode/src/vs/server/node/extensionsScannerService.ts +++ b/patched-vscode/src/vs/server/node/extensionsScannerService.ts @@ -14,7 +14,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { getNLSConfiguration, InternalNLSConfiguration } from 'vs/server/node/remoteLanguagePacks'; +import { getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks'; export class ExtensionsScannerService extends AbstractExtensionsScannerService implements IExtensionsScannerService { @@ -38,9 +38,9 @@ export class ExtensionsScannerService extends AbstractExtensionsScannerService i protected async getTranslations(language: string): Promise { const config = await getNLSConfiguration(language, this.nativeEnvironmentService.userDataPath); - if (InternalNLSConfiguration.is(config)) { + if (config.languagePack) { try { - const content = await this.fileService.readFile(URI.file(config._translationsConfigFile)); + const content = await this.fileService.readFile(URI.file(config.languagePack.translationsConfigFile)); return JSON.parse(content.value.toString()); } catch (err) { /* Ignore error */ } } diff --git a/patched-vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts b/patched-vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts index 9eace08d..c076b5c1 100644 --- a/patched-vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/patched-vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -9,7 +9,7 @@ import * as http from 'http'; import * as net from 'net'; import { performance } from 'perf_hooks'; import * as url from 'url'; -import { LoaderStats } from 'vs/base/common/amd'; +import { LoaderStats, isESM } from 'vs/base/common/amd'; import { VSBuffer } from 'vs/base/common/buffer'; import { CharCode } from 'vs/base/common/charCode'; import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; @@ -505,6 +505,7 @@ class RemoteExtensionHostAgentServer extends Disposable implements IServerAPI { this._extHostConnections[reconnectionToken] = con; this._allReconnectionTokens.add(reconnectionToken); con.onClose(() => { + con.dispose(); delete this._extHostConnections[reconnectionToken]; this._onDidCloseExtHostConnection(); }); @@ -695,7 +696,7 @@ export async function createServer(address: string | net.AddressInfo | null, arg let didLogAboutSIGPIPE = false; process.on('SIGPIPE', () => { // See https://github.com/microsoft/vscode-remote-release/issues/6543 - // We would normally install a SIGPIPE listener in bootstrap.js + // We would normally install a SIGPIPE listener in bootstrap-node.js // But in certain situations, the console itself can be in a broken pipe state // so logging SIGPIPE to the console will cause an infinite async loop if (!didLogAboutSIGPIPE) { @@ -780,7 +781,7 @@ export async function createServer(address: string | net.AddressInfo | null, arg serverBasePath = `/${serverBasePath}`; } - const hasWebClient = fs.existsSync(FileAccess.asFileUri('vs/code/browser/workbench/workbench.html').fsPath); + const hasWebClient = fs.existsSync(FileAccess.asFileUri(`vs/code/browser/workbench/workbench.${isESM ? 'esm.' : ''}html`).fsPath); if (hasWebClient && address && typeof address !== 'string') { // ships the web ui! diff --git a/patched-vscode/src/vs/server/node/remoteExtensionsScanner.ts b/patched-vscode/src/vs/server/node/remoteExtensionsScanner.ts index 19a50f7b..7b58140e 100644 --- a/patched-vscode/src/vs/server/node/remoteExtensionsScanner.ts +++ b/patched-vscode/src/vs/server/node/remoteExtensionsScanner.ts @@ -103,26 +103,6 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS return extensions; } - async scanSingleExtension(extensionLocation: URI, isBuiltin: boolean, language?: string): Promise { - await this._whenBuiltinExtensionsReady; - - const extensionPath = extensionLocation.scheme === Schemas.file ? extensionLocation.fsPath : null; - - if (!extensionPath) { - return null; - } - - const extension = await this._scanSingleExtension(extensionPath, isBuiltin, language ?? platform.language); - - if (!extension) { - return null; - } - - this._massageWhenConditions([extension]); - - return extension; - } - private async _scanExtensions(profileLocation: URI, language: string, workspaceInstalledExtensionLocations: URI[] | undefined, extensionDevelopmentPath: string[] | undefined, languagePackId: string | undefined): Promise { await this._ensureLanguagePackIsInstalled(language, languagePackId); @@ -168,13 +148,6 @@ export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerS return scannedExtensions.map(e => toExtensionDescription(e, false)); } - private async _scanSingleExtension(extensionPath: string, isBuiltin: boolean, language: string): Promise { - const extensionLocation = URI.file(resolve(extensionPath)); - const type = isBuiltin ? ExtensionType.System : ExtensionType.User; - const scannedExtension = await this._extensionsScannerService.scanExistingExtension(extensionLocation, type, { language }); - return scannedExtension ? toExtensionDescription(scannedExtension, false) : null; - } - private async _ensureLanguagePackIsInstalled(language: string, languagePackId: string | undefined): Promise { if ( // No need to install language packs for the default language @@ -351,10 +324,6 @@ export class RemoteExtensionsScannerChannel implements IServerChannel { ); return extensions.map(extension => transformOutgoingURIs(extension, uriTransformer)); } - case 'scanSingleExtension': { - const extension = await this.service.scanSingleExtension(URI.revive(uriTransformer.transformIncoming(args[0])), args[1], args[2]); - return extension ? transformOutgoingURIs(extension, uriTransformer) : null; - } } throw new Error('Invalid call'); } diff --git a/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts b/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts index 682b6f2b..2a1ea9f5 100644 --- a/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts +++ b/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts @@ -3,46 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; import { FileAccess } from 'vs/base/common/network'; -import * as path from 'vs/base/common/path'; - -import * as lp from 'vs/base/node/languagePacks'; +import { join } from 'vs/base/common/path'; +import type { INLSConfiguration } from 'vs/nls'; +import { resolveNLSConfiguration } from 'vs/base/node/nls'; +import { Promises } from 'vs/base/node/pfs'; import product from 'vs/platform/product/common/product'; -const metaData = path.join(FileAccess.asFileUri('').fsPath, 'nls.metadata.json'); -const _cache: Map> = new Map(); +const nlsMetadataPath = join(FileAccess.asFileUri('').fsPath); +const defaultMessagesFile = join(nlsMetadataPath, 'nls.messages.json'); +const nlsConfigurationCache = new Map>(); -function exists(file: string) { - return new Promise(c => fs.exists(file, c)); -} +export async function getNLSConfiguration(language: string, userDataPath: string): Promise { + if (!product.commit || !(await Promises.exists(defaultMessagesFile))) { + return { + userLocale: 'en', + osLocale: 'en', + resolvedLanguage: 'en', + defaultMessagesFile, -export function getNLSConfiguration(language: string, userDataPath: string): Promise { - return exists(metaData).then((fileExists) => { - if (!fileExists || !product.commit) { - // console.log(`==> MetaData or commit unknown. Using default language.`); - // The OS Locale on the remote side really doesn't matter, so we return the default locale - return Promise.resolve({ locale: 'en', osLocale: 'en', availableLanguages: {} }); - } - const key = `${language}||${userDataPath}`; - let result = _cache.get(key); - if (!result) { - // The OS Locale on the remote side really doesn't matter, so we pass in the same language - result = lp.getNLSConfiguration(product.commit, userDataPath, metaData, language, language).then(value => { - if (InternalNLSConfiguration.is(value)) { - value._languagePackSupport = true; - } - return value; - }); - _cache.set(key, result); - } - return result; - }); -} + // NLS: below 2 are a relic from old times only used by vscode-nls and deprecated + locale: 'en', + availableLanguages: {} + }; + } -export namespace InternalNLSConfiguration { - export function is(value: lp.NLSConfiguration): value is lp.InternalNLSConfiguration { - const candidate: lp.InternalNLSConfiguration = value as lp.InternalNLSConfiguration; - return candidate && typeof candidate._languagePackId === 'string'; + const cacheKey = `${language}||${userDataPath}`; + let result = nlsConfigurationCache.get(cacheKey); + if (!result) { + result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: product.commit, userDataPath, nlsMetadataPath }); + nlsConfigurationCache.set(cacheKey, result); } + + return result; } diff --git a/patched-vscode/src/vs/server/node/server.cli.ts b/patched-vscode/src/vs/server/node/server.cli.ts index 2eed66f7..fc0f7c25 100644 --- a/patched-vscode/src/vs/server/node/server.cli.ts +++ b/patched-vscode/src/vs/server/node/server.cli.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as _fs from 'fs'; -import * as _url from 'url'; -import * as _cp from 'child_process'; -import * as _http from 'http'; -import * as _os from 'os'; +import * as fs from 'fs'; +import * as url from 'url'; +import * as cp from 'child_process'; +import * as http from 'http'; import { cwd } from 'vs/base/common/process'; import { dirname, extname, resolve, join } from 'vs/base/common/path'; import { parseArgs, buildHelpMessage, buildVersionMessage, OPTIONS, OptionDescriptions, ErrorReporter } from 'vs/platform/environment/node/argv'; @@ -240,8 +239,8 @@ export async function main(desc: ProductDescription, args: string[]): Promise console.log(err)); + const childProcess = cp.fork(join(__dirname, '../../../server-main.js'), cmdLine, { stdio: 'inherit' }); + childProcess.on('error', err => console.log(err)); return; } @@ -270,7 +269,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise process.stdout.write(data)); - cp.stderr.on('data', data => process.stderr.write(data)); + const childProcess = cp.spawn(cliCommand, newCommandline, { cwd: cliCwd, env, stdio: ['inherit', 'pipe', 'pipe'] }); + childProcess.stdout.on('data', data => process.stdout.write(data)); + childProcess.stderr.on('data', data => process.stderr.write(data)); } else { - _cp.spawn(cliCommand, newCommandline, { cwd: cliCwd, env, stdio: 'inherit' }); + cp.spawn(cliCommand, newCommandline, { cwd: cliCwd, env, stdio: 'inherit' }); } } } else { @@ -357,7 +356,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise setTimeout(res, 1000)); } } @@ -376,7 +375,7 @@ function openInBrowser(args: string[], verbose: boolean) { for (const location of args) { try { if (/^(http|https|file):\/\//.test(location)) { - uris.push(_url.parse(location).href); + uris.push(url.parse(location).href); } else { uris.push(pathToURI(location).href); } @@ -406,7 +405,7 @@ function sendToPipe(args: PipeCommand, verbose: boolean): Promise { return; } - const opts: _http.RequestOptions = { + const opts: http.RequestOptions = { socketPath: cliPipe, path: '/', method: 'POST', @@ -416,7 +415,7 @@ function sendToPipe(args: PipeCommand, verbose: boolean): Promise { } }; - const req = _http.request(opts, res => { + const req = http.request(opts, res => { if (res.headers['content-type'] !== 'application/json') { reject('Error in response: Invalid content type: Expected \'application/json\', is: ' + res.headers['content-type']); return; @@ -461,18 +460,18 @@ function fatal(message: string, err: any): void { const preferredCwd = process.env.PWD || cwd(); // prefer process.env.PWD as it does not follow symlinks -function pathToURI(input: string): _url.URL { +function pathToURI(input: string): url.URL { input = input.trim(); input = resolve(preferredCwd, input); - return _url.pathToFileURL(input); + return url.pathToFileURL(input); } function translatePath(input: string, mapFileUri: (input: string) => string, folderURIS: string[], fileURIS: string[]) { const url = pathToURI(input); const mappedUri = mapFileUri(url.href); try { - const stat = _fs.lstatSync(_fs.realpathSync(input)); + const stat = fs.lstatSync(fs.realpathSync(input)); if (stat.isFile()) { fileURIS.push(mappedUri); diff --git a/patched-vscode/src/vs/server/node/serverConnectionToken.ts b/patched-vscode/src/vs/server/node/serverConnectionToken.ts index f976d0e3..6a6bdcdb 100644 --- a/patched-vscode/src/vs/server/node/serverConnectionToken.ts +++ b/patched-vscode/src/vs/server/node/serverConnectionToken.ts @@ -100,7 +100,7 @@ export async function determineServerConnectionToken(args: ServerParsedArgs): Pr // First try to find a connection token try { - const fileContents = await Promises.readFile(storageLocation); + const fileContents = await fs.promises.readFile(storageLocation); const connectionToken = fileContents.toString().replace(/\r?\n$/, ''); if (connectionTokenRegex.test(connectionToken)) { return connectionToken; diff --git a/patched-vscode/src/vs/server/node/serverEnvironmentService.ts b/patched-vscode/src/vs/server/node/serverEnvironmentService.ts index 83d4aac7..fce1842f 100644 --- a/patched-vscode/src/vs/server/node/serverEnvironmentService.ts +++ b/patched-vscode/src/vs/server/node/serverEnvironmentService.ts @@ -89,9 +89,6 @@ export const serverOptions: OptionDescriptions> = { 'compatibility': { type: 'string' }, - /* ----- sagemaker ----- */ - 'base-path': { type: 'string' }, - _: OPTIONS['_'] }; @@ -215,9 +212,6 @@ export interface ServerParsedArgs { compatibility: string; - /* ----- sagemaker ----- */ - 'base-path'?: string, - _: string[]; } diff --git a/patched-vscode/src/vs/server/node/serverServices.ts b/patched-vscode/src/vs/server/node/serverServices.ts index 46f2f004..f9ca0e7c 100644 --- a/patched-vscode/src/vs/server/node/serverServices.ts +++ b/patched-vscode/src/vs/server/node/serverServices.ts @@ -77,6 +77,7 @@ import { RemoteExtensionsScannerChannel, RemoteExtensionsScannerService } from ' import { RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { RemoteUserDataProfilesServiceChannel } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; import { NodePtyHostStarter } from 'vs/platform/terminal/node/nodePtyHostStarter'; +import { CSSDevelopmentService, ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; const eventPrefix = 'monacoworkbench'; @@ -131,6 +132,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IUserDataProfilesService, userDataProfilesService); socketServer.registerChannel('userDataProfiles', new RemoteUserDataProfilesServiceChannel(userDataProfilesService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); + // Dev Only: CSS service (for ESM) + services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true)); + // Initialize const [, , machineId, sqmId, devDeviceId] = await Promise.all([ configurationService.initialize(), diff --git a/patched-vscode/src/vs/server/node/webClientServer.ts b/patched-vscode/src/vs/server/node/webClientServer.ts index 670d3bf0..f66972fe 100644 --- a/patched-vscode/src/vs/server/node/webClientServer.ts +++ b/patched-vscode/src/vs/server/node/webClientServer.ts @@ -3,9 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createReadStream } from 'fs'; -import {readFile } from 'fs/promises'; -import { Promises } from 'vs/base/node/pfs'; +import { createReadStream, promises } from 'fs'; import * as path from 'path'; import * as http from 'http'; import * as url from 'url'; @@ -30,6 +28,8 @@ import { IProductConfiguration } from 'vs/base/common/product'; import { isString } from 'vs/base/common/types'; import { CharCode } from 'vs/base/common/charCode'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { isESM } from 'vs/base/common/amd'; +import { ICSSDevelopmentService } from 'vs/platform/cssDev/node/cssDevService'; const textMimeType: { [ext: string]: string | undefined } = { '.html': 'text/html', @@ -56,7 +56,7 @@ export const enum CacheControl { */ export async function serveFile(filePath: string, cacheControl: CacheControl, logService: ILogService, req: http.IncomingMessage, res: http.ServerResponse, responseHeaders: Record): Promise { try { - const stat = await Promises.stat(filePath); // throws an error if file doesn't exist + const stat = await promises.stat(filePath); // throws an error if file doesn't exist if (cacheControl === CacheControl.ETAG) { // Check if file modified since @@ -101,7 +101,6 @@ export class WebClientServer { private readonly _staticRoute: string; private readonly _callbackRoute: string; private readonly _webExtensionRoute: string; - private readonly _idleRoute: string; constructor( private readonly _connectionToken: ServerConnectionToken, @@ -111,13 +110,13 @@ export class WebClientServer { @ILogService private readonly _logService: ILogService, @IRequestService private readonly _requestService: IRequestService, @IProductService private readonly _productService: IProductService, + @ICSSDevelopmentService private readonly _cssDevService: ICSSDevelopmentService ) { this._webExtensionResourceUrlTemplate = this._productService.extensionsGallery?.resourceUrlTemplate ? URI.parse(this._productService.extensionsGallery.resourceUrlTemplate) : undefined; this._staticRoute = `${serverRootPath}/static`; this._callbackRoute = `${serverRootPath}/callback`; this._webExtensionRoute = `${serverRootPath}/web-extension-resource`; - this._idleRoute = '/api/idle'; } /** @@ -135,9 +134,6 @@ export class WebClientServer { if (pathname === this._basePath) { return this._handleRoot(req, res, parsedUrl); } - if (pathname === this._idleRoute) { - return this._handleIdle(req, res); - } if (pathname === this._callbackRoute) { // callback support return this._handleCallback(res); @@ -228,7 +224,7 @@ export class WebClientServer { return serveError(req, res, status, text || `Request failed with status ${status}`); } - const responseHeaders: Record = Object.create(null); + const responseHeaders: Record = Object.create(null); const setResponseHeader = (header: string) => { const value = context.res.headers[header]; if (value) { @@ -285,7 +281,7 @@ export class WebClientServer { const remoteAuthority = ( useTestResolver ? 'test+test' - : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host || window.location.host) + : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host) ); if (!remoteAuthority) { return serveError(req, res, 400, `Bad request.`); @@ -304,7 +300,7 @@ export class WebClientServer { const resolveWorkspaceURI = (defaultLocation?: string) => defaultLocation && URI.file(path.resolve(defaultLocation)).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }); - const filePath = FileAccess.asFileUri(this._environmentService.isBuilt ? 'vs/code/browser/workbench/workbench.html' : 'vs/code/browser/workbench/workbench-dev.html').fsPath; + const filePath = FileAccess.asFileUri(`vs/code/browser/workbench/workbench${this._environmentService.isBuilt ? '' : '-dev'}.${isESM ? 'esm.' : ''}html`).fsPath; const authSessionInfo = !this._environmentService.isBuilt && this._environmentService.args['github-auth'] ? { id: generateUuid(), providerId: 'github', @@ -312,12 +308,7 @@ export class WebClientServer { scopes: [['user:email'], ['repo']] } : undefined; - const basePath: string = this._environmentService.args['base-path'] || "/" - const base = relativeRoot(basePath) - const vscodeBase = relativePath(basePath) - const productConfiguration = { - rootEndpoint: base, embedderIdentifier: 'server-distro', extensionsGallery: this._webExtensionResourceUrlTemplate && this._productService.extensionsGallery ? { ...this._productService.extensionsGallery, @@ -331,7 +322,7 @@ export class WebClientServer { if (!this._environmentService.isBuilt) { try { - const productOverrides = JSON.parse((await Promises.readFile(join(APP_ROOT, 'product.overrides.json'))).toString()); + const productOverrides = JSON.parse((await promises.readFile(join(APP_ROOT, 'product.overrides.json'))).toString()); Object.assign(productConfiguration, productOverrides); } catch (err) {/* Ignore Error */ } } @@ -339,8 +330,6 @@ export class WebClientServer { const workbenchWebConfiguration = { remoteAuthority, serverBasePath: this._basePath, - webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', - userDataPath: this._environmentService.userDataPath, _wrapWebWorkerExtHostInIframe, developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() }, settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined, @@ -351,20 +340,39 @@ export class WebClientServer { callbackRoute: this._callbackRoute }; - const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl; + const cookies = cookie.parse(req.headers.cookie || ''); + const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; + let WORKBENCH_NLS_BASE_URL: string | undefined; + let WORKBENCH_NLS_URL: string; + if (!locale.startsWith('en') && this._productService.nlsCoreBaseUrl) { + WORKBENCH_NLS_BASE_URL = this._productService.nlsCoreBaseUrl; + WORKBENCH_NLS_URL = `${WORKBENCH_NLS_BASE_URL}${this._productService.commit}/${this._productService.version}/${locale}/nls.messages.js`; + } else { + WORKBENCH_NLS_URL = ''; // fallback will apply + } + const values: { [key: string]: string } = { WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', - WORKBENCH_WEB_BASE_URL: vscodeBase + this._staticRoute, - WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''), - BASE: base, - VS_BASE: vscodeBase, + WORKBENCH_WEB_BASE_URL: this._staticRoute, + WORKBENCH_NLS_URL, + WORKBENCH_NLS_FALLBACK_URL: `${this._staticRoute}/out/nls.messages.js` }; + // DEV --------------------------------------------------------------------------------------- + // DEV: This is for development and enables loading CSS via import-statements via import-maps. + // DEV: The server needs to send along all CSS modules so that the client can construct the + // DEV: import-map. + // DEV --------------------------------------------------------------------------------------- + if (this._cssDevService.isEnabled) { + const cssModules = await this._cssDevService.getCssModules(); + values['WORKBENCH_DEV_CSS_MODULES'] = JSON.stringify(cssModules); + } + if (useTestResolver) { const bundledExtensions: { extensionPath: string; packageJSON: IExtensionManifest }[] = []; for (const extensionPath of ['vscode-test-resolver', 'github-authentication']) { - const packageJSON = JSON.parse((await Promises.readFile(FileAccess.asFileUri(`${builtinExtensionsPath}/${extensionPath}/package.json`).fsPath)).toString()); + const packageJSON = JSON.parse((await promises.readFile(FileAccess.asFileUri(`${builtinExtensionsPath}/${extensionPath}/package.json`).fsPath)).toString()); bundledExtensions.push({ extensionPath, packageJSON }); } values['WORKBENCH_BUILTIN_EXTENSIONS'] = asJSON(bundledExtensions); @@ -372,20 +380,22 @@ export class WebClientServer { let data; try { - const workbenchTemplate = (await Promises.readFile(filePath)).toString(); + const workbenchTemplate = (await promises.readFile(filePath)).toString(); data = workbenchTemplate.replace(/\{\{([^}]+)\}\}/g, (_, key) => values[key] ?? 'undefined'); } catch (e) { res.writeHead(404, { 'Content-Type': 'text/plain' }); return void res.end('Not found'); } - const webWorkerExtensionHostIframeScriptSHA = 'sha256-75NYUUvf+5++1WbfCZOV3PSWxBhONpaxwx+mkOFRv/Y='; + const webWorkerExtensionHostIframeScriptSHA = isESM ? 'sha256-2Q+j4hfT09+1+imS46J2YlkCtHWQt0/BE79PXjJ0ZJ8=' : 'sha256-V28GQnL3aYxbwgpV3yW1oJ+VKKe/PBSzWntNyH8zVXA='; const cspDirectives = [ 'default-src \'self\';', 'img-src \'self\' https: data: blob:;', 'media-src \'self\';', - `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html + isESM ? + `script-src 'self' 'unsafe-eval' ${WORKBENCH_NLS_BASE_URL ?? ''} blob: 'nonce-1nline-m4p' ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' 'sha256-/r7rqQ+yrxt57sxLuQ6AMYcy/lUpvAIzHjIJt/OeLWU=' ${useTestResolver ? '' : `http://${remoteAuthority}`};` : // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.esm.html + `script-src 'self' 'unsafe-eval' ${WORKBENCH_NLS_BASE_URL ?? ''} ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html 'child-src \'self\';', `frame-src 'self' https://*.vscode-cdn.net data:;`, 'worker-src \'self\' data: blob:;', @@ -441,7 +451,7 @@ export class WebClientServer { */ private async _handleCallback(res: http.ServerResponse): Promise { const filePath = FileAccess.asFileUri('vs/code/browser/workbench/callback.html').fsPath; - const data = (await Promises.readFile(filePath)).toString(); + const data = (await promises.readFile(filePath)).toString(); const cspDirectives = [ 'default-src \'self\';', 'img-src \'self\' https: data: blob:;', @@ -457,92 +467,4 @@ export class WebClientServer { }); return void res.end(data); } - - /** - * Handles API requests to retrieve the last activity timestamp. - */ - private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { - try { - const homeDirectory = process.env.HOME || process.env.USERPROFILE; - if (!homeDirectory) { - throw new Error('Home directory not found'); - } - - const idleFilePath = path.join(homeDirectory, '.code-editor-last-active-timestamp'); - const data = await readFile(idleFilePath, 'utf8'); - - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ lastActiveTimestamp: data })); - } catch (error) { - serveError(req, res, 500, error.message) - } - } -} - -/** - * Remove extra slashes in a URL. - * - * This is meant to fill the job of `path.join` so you can concatenate paths and - * then normalize out any extra slashes. - * - * If you are using `path.join` you do not need this but note that `path` is for - * file system paths, not URLs. - */ -export const normalizeUrlPath = (url: string, keepTrailing = false): string => { - return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "") -} - -/** - * Get the relative path that will get us to the root of the page. For each - * slash we need to go up a directory. Will not have a trailing slash. - * - * For example: - * - * / => . - * /foo => . - * /foo/ => ./.. - * /foo/bar => ./.. - * /foo/bar/ => ./../.. - * - * All paths must be relative in order to work behind a reverse proxy since we - * we do not know the base path. Anything that needs to be absolute (for - * example cookies) must get the base path from the frontend. - * - * All relative paths must be prefixed with the relative root to ensure they - * work no matter the depth at which they happen to appear. - * - * For Express `req.originalUrl` should be used as they remove the base from the - * standard `url` property making it impossible to get the true depth. - */ -export const relativeRoot = (originalUrl: string): string => { - const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length - return normalizeUrlPath("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) -} - -/** - * Get the relative path to the current resource. - * - * For example: - * - * / => . - * /foo => ./foo - * /foo/ => . - * /foo/bar => ./bar - * /foo/bar/ => . - */ -export const relativePath = (originalUrl: string): string => { - const parts = originalUrl.split("?", 1)[0].split("/") - return normalizeUrlPath("./" + parts[parts.length - 1]) -} - -/** - * code-server serves Code using Express. Express removes the base from the url - * and puts the original in `originalUrl` so we must use this to get the correct - * depth. Code is not aware it is behind Express so the types do not match. We - * may want to continue moving code into Code and eventually remove the Express - * wrapper or move the web server back into code-server. - */ -export const getOriginalUrl = (req: http.IncomingMessage): string => { - return (req as any).originalUrl || req.url } diff --git a/patched-vscode/src/vs/server/test/node/serverConnectionToken.test.ts b/patched-vscode/src/vs/server/test/node/serverConnectionToken.test.ts index b624dd6d..b04dab4d 100644 --- a/patched-vscode/src/vs/server/test/node/serverConnectionToken.test.ts +++ b/patched-vscode/src/vs/server/test/node/serverConnectionToken.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; diff --git a/patched-vscode/src/vs/workbench/api/browser/extensionHost.contribution.ts b/patched-vscode/src/vs/workbench/api/browser/extensionHost.contribution.ts index 9fdc4b30..1d563ea1 100644 --- a/patched-vscode/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/patched-vscode/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -20,6 +20,7 @@ import './mainThreadBulkEdits'; import './mainThreadLanguageModels'; import './mainThreadChatAgents2'; import './mainThreadChatVariables'; +import './mainThreadLanguageModelTools'; import './mainThreadEmbeddings'; import './mainThreadCodeInsets'; import './mainThreadCLICommands'; diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 37198258..c1500981 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -6,7 +6,7 @@ import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { IAuthenticationCreateSessionOptions, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService, INTERNAL_AUTH_PROVIDER_PREFIX as INTERNAL_MODEL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; +import { IAuthenticationCreateSessionOptions, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService, INTERNAL_AUTH_PROVIDER_PREFIX as INTERNAL_MODEL_AUTH_PROVIDER_PREFIX, AuthenticationSessionAccount, IAuthenticationProviderSessionOptions } from 'vs/workbench/services/authentication/common/authentication'; import { ExtHostAuthenticationShape, ExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; @@ -19,6 +19,7 @@ import { IAuthenticationUsageService } from 'vs/workbench/services/authenticatio import { getAuthenticationProviderActivationEvent } from 'vs/workbench/services/authentication/browser/authenticationService'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { CancellationError } from 'vs/base/common/errors'; interface AuthenticationForceNewSessionOptions { detail?: string; @@ -31,6 +32,7 @@ interface AuthenticationGetSessionOptions { createIfNone?: boolean; forceNewSession?: boolean | AuthenticationForceNewSessionOptions; silent?: boolean; + account?: AuthenticationSessionAccount; } export class MainThreadAuthenticationProvider extends Disposable implements IAuthenticationProvider { @@ -49,8 +51,8 @@ export class MainThreadAuthenticationProvider extends Disposable implements IAut this.onDidChangeSessions = onDidChangeSessionsEmitter.event; } - async getSessions(scopes?: string[]) { - return this._proxy.$getSessions(this.id, scopes); + async getSessions(scopes: string[] | undefined, options: IAuthenticationProviderSessionOptions) { + return this._proxy.$getSessions(this.id, scopes, options); } createSession(scopes: string[], options: IAuthenticationCreateSessionOptions): Promise { @@ -158,8 +160,33 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu return result ?? false; } + private async continueWithIncorrectAccountPrompt(chosenAccountLabel: string, requestedAccountLabel: string): Promise { + const result = await this.dialogService.prompt({ + message: nls.localize('incorrectAccount', "Incorrect account detected"), + detail: nls.localize('incorrectAccountDetail', "The chosen account, {0}, does not match the requested account, {1}.", chosenAccountLabel, requestedAccountLabel), + type: Severity.Warning, + cancelButton: true, + buttons: [ + { + label: nls.localize('keep', 'Keep {0}', chosenAccountLabel), + run: () => chosenAccountLabel + }, + { + label: nls.localize('loginWith', 'Login with {0}', requestedAccountLabel), + run: () => requestedAccountLabel + } + ], + }); + + if (!result.result) { + throw new CancellationError(); + } + + return result.result === chosenAccountLabel; + } + private async doGetSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise { - const sessions = await this.authenticationService.getSessions(providerId, scopes, true); + const sessions = await this.authenticationService.getSessions(providerId, scopes, options.account, true); const provider = this.authenticationService.getProvider(providerId); // Error cases @@ -173,21 +200,21 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu throw new Error('Invalid combination of options. Please remove one of the following: createIfNone, silent'); } + if (options.clearSessionPreference) { + // Clearing the session preference is usually paired with createIfNone, so just remove the preference and + // defer to the rest of the logic in this function to choose the session. + this.authenticationExtensionsService.removeSessionPreference(providerId, extensionId, scopes); + } + // Check if the sessions we have are valid if (!options.forceNewSession && sessions.length) { if (provider.supportsMultipleAccounts) { - if (options.clearSessionPreference) { - // Clearing the session preference is usually paired with createIfNone, so just remove the preference and - // defer to the rest of the logic in this function to choose the session. - this.authenticationExtensionsService.removeSessionPreference(providerId, extensionId, scopes); - } else { - // If we have an existing session preference, use that. If not, we'll return any valid session at the end of this function. - const existingSessionPreference = this.authenticationExtensionsService.getSessionPreference(providerId, extensionId, scopes); - if (existingSessionPreference) { - const matchingSession = sessions.find(session => session.id === existingSessionPreference); - if (matchingSession && this.authenticationAccessService.isAccessAllowed(providerId, matchingSession.account.label, extensionId)) { - return matchingSession; - } + // If we have an existing session preference, use that. If not, we'll return any valid session at the end of this function. + const existingSessionPreference = this.authenticationExtensionsService.getSessionPreference(providerId, extensionId, scopes); + if (existingSessionPreference) { + const matchingSession = sessions.find(session => session.id === existingSessionPreference); + if (matchingSession && this.authenticationAccessService.isAccessAllowed(providerId, matchingSession.account.label, extensionId)) { + return matchingSession; } } } else if (this.authenticationAccessService.isAccessAllowed(providerId, sessions[0].account.label, extensionId)) { @@ -211,20 +238,25 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu throw new Error('User did not consent to login.'); } - let session; + let session: AuthenticationSession; if (sessions?.length && !options.forceNewSession) { - session = provider.supportsMultipleAccounts + session = provider.supportsMultipleAccounts && !options.account ? await this.authenticationExtensionsService.selectSession(providerId, extensionId, extensionName, scopes, sessions) : sessions[0]; } else { - let sessionToRecreate: AuthenticationSession | undefined; - if (typeof options.forceNewSession === 'object' && options.forceNewSession.sessionToRecreate) { - sessionToRecreate = options.forceNewSession.sessionToRecreate as AuthenticationSession; - } else { + let accountToCreate: AuthenticationSessionAccount | undefined = options.account; + if (!accountToCreate) { const sessionIdToRecreate = this.authenticationExtensionsService.getSessionPreference(providerId, extensionId, scopes); - sessionToRecreate = sessionIdToRecreate ? sessions.find(session => session.id === sessionIdToRecreate) : undefined; + accountToCreate = sessionIdToRecreate ? sessions.find(session => session.id === sessionIdToRecreate)?.account : undefined; } - session = await this.authenticationService.createSession(providerId, scopes, { activateImmediate: true, sessionToRecreate }); + + do { + session = await this.authenticationService.createSession(providerId, scopes, { activateImmediate: true, account: accountToCreate }); + } while ( + accountToCreate + && accountToCreate.label !== session.account.label + && !await this.continueWithIncorrectAccountPrompt(session.account.label, accountToCreate.label) + ); } this.authenticationAccessService.updateAllowedExtensions(providerId, session.account.label, [{ id: extensionId, name: extensionName, allowed: true }]); @@ -261,16 +293,9 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu return session; } - async $getSessions(providerId: string, scopes: readonly string[], extensionId: string, extensionName: string): Promise { - const sessions = await this.authenticationService.getSessions(providerId, [...scopes], true); - const accessibleSessions = sessions.filter(s => this.authenticationAccessService.isAccessAllowed(providerId, s.account.label, extensionId)); - if (accessibleSessions.length) { - this.sendProviderUsageTelemetry(extensionId, providerId); - for (const session of accessibleSessions) { - this.authenticationUsageService.addAccountUsage(providerId, session.account.label, extensionId, extensionName); - } - } - return accessibleSessions; + async $getAccounts(providerId: string): Promise> { + const accounts = await this.authenticationService.getAccounts(providerId); + return accounts; } private sendProviderUsageTelemetry(extensionId: string, providerId: string): void { diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 0d63847d..ec9be12f 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -21,11 +21,11 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IDynamicChatAgentProps, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatAgentsShape2, ExtHostContext, IChatParticipantMetadata, IChatProgressDto, IDynamicChatAgentProps, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; -import { ChatAgentLocation, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentLocation, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatContentReference, IChatFollowup, IChatProgress, IChatService, IChatTask, IChatWarningMessage } from 'vs/workbench/contrib/chat/common/chatService'; @@ -76,6 +76,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA private readonly _agentCompletionProviders = this._register(new DisposableMap()); private readonly _agentIdsToCompletionProviders = this._register(new DisposableMap); + private readonly _chatParticipantDetectionProviders = this._register(new DisposableMap()); + private readonly _pendingProgress = new Map void>(); private readonly _proxy: ExtHostChatAgentsShape2; @@ -103,7 +105,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA for (const [handle, agent] of this._agents) { if (agent.id === e.agentId) { if (e.action.kind === 'vote') { - this._proxy.$acceptFeedback(handle, e.result ?? {}, e.action.direction); + this._proxy.$acceptFeedback(handle, e.result ?? {}, e.action); } else { this._proxy.$acceptAction(handle, e.result || {}, e); } @@ -161,6 +163,9 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA provideWelcomeMessage: (location: ChatAgentLocation, token: CancellationToken) => { return this._proxy.$provideWelcomeMessage(handle, location, token); }, + provideChatTitle: (history, token) => { + return this._proxy.$provideChatTitle(handle, history, token); + }, provideSampleQuestions: (location: ChatAgentLocation, token: CancellationToken) => { return this._proxy.$provideSampleQuestions(handle, location, token); } @@ -172,7 +177,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA disposable = this._chatAgentService.registerDynamicAgent( { id, - name: dynamicProps.name ?? '', // This case is for an API change and can be removed tomorrow + name: dynamicProps.name, description: dynamicProps.description, extensionId: extension, extensionDisplayName: extensionDescription?.displayName ?? extension.value, @@ -181,6 +186,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA fullName: dynamicProps.fullName, metadata: revive(metadata), slashCommands: [], + disambiguation: [], locations: [ChatAgentLocation.Panel] // TODO all dynamic participants are panel only? }, impl); @@ -199,7 +205,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void { const data = this._agents.get(handle); if (!data) { - throw new Error(`No agent with handle ${handle} registered`); + this._logService.error(`MainThreadChatAgents2#$updateAgent: No agent with handle ${handle} registered`); + return; } data.hasFollowups = metadataUpdate.hasFollowups; this._chatAgentService.updateAgent(data.id, revive(metadataUpdate)); @@ -297,6 +304,20 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._agentCompletionProviders.deleteAndDispose(handle); this._agentIdsToCompletionProviders.deleteAndDispose(id); } + + $registerChatParticipantDetectionProvider(handle: number): void { + this._chatParticipantDetectionProviders.set(handle, this._chatAgentService.registerChatParticipantDetectionProvider(handle, + { + provideParticipantDetection: async (request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation; participants: IChatParticipantMetadata[] }, token: CancellationToken) => { + return await this._proxy.$detectChatParticipant(handle, request, { history }, options, token); + } + } + )); + } + + $unregisterChatParticipantDetectionProvider(handle: number): void { + this._chatParticipantDetectionProviders.deleteAndDispose(handle); + } } diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadChatVariables.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadChatVariables.ts index bf710320..9e08e5d1 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadChatVariables.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadChatVariables.ts @@ -5,10 +5,7 @@ import { DisposableMap } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; -import { URI } from 'vs/base/common/uri'; -import { Location } from 'vs/editor/common/languages'; import { ExtHostChatVariablesShape, ExtHostContext, IChatVariableResolverProgressDto, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @@ -50,8 +47,4 @@ export class MainThreadChatVariables implements MainThreadChatVariablesShape { $unregisterVariable(handle: number): void { this._variables.deleteAndDispose(handle); } - - $attachContext(name: string, value: string | URI | Location | unknown, location: ChatAgentLocation.Panel): void { - this._chatVariablesService.attachContext(name, revive(value), location); - } } diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadComments.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadComments.ts index 4eb6385c..1504c0d2 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadComments.ts @@ -27,6 +27,9 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { Schemas } from 'vs/base/common/network'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { MarshalledCommentThread } from 'vs/workbench/common/comments'; +import { revealCommentThread } from 'vs/workbench/contrib/comments/browser/commentsController'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; export class MainThreadCommentThread implements languages.CommentThread { private _input?: languages.CommentInput; @@ -66,13 +69,13 @@ export class MainThreadCommentThread implements languages.CommentThread { private readonly _onDidChangeLabel = new Emitter(); readonly onDidChangeLabel: Event = this._onDidChangeLabel.event; - private _comments: languages.Comment[] | undefined; + private _comments: ReadonlyArray | undefined; - public get comments(): languages.Comment[] | undefined { + public get comments(): ReadonlyArray | undefined { return this._comments; } - public set comments(newComments: languages.Comment[] | undefined) { + public set comments(newComments: ReadonlyArray | undefined) { this._comments = newComments; this._onDidChangeComments.fire(this._comments); } @@ -181,6 +184,7 @@ export class MainThreadCommentThread implements languages.CommentThread { public threadId: string, public resource: string, private _range: T | undefined, + comments: languages.Comment[] | undefined, private _canReply: boolean, private _isTemplate: boolean, public editorId?: string @@ -188,6 +192,8 @@ export class MainThreadCommentThread implements languages.CommentThread { this._isDisposed = false; if (_isTemplate) { this.comments = []; + } else if (comments) { + this._comments = comments; } } @@ -198,7 +204,7 @@ export class MainThreadCommentThread implements languages.CommentThread { if (modified('range')) { this._range = changes.range!; } if (modified('label')) { this._label = changes.label; } if (modified('contextValue')) { this._contextValue = changes.contextValue === null ? undefined : changes.contextValue; } - if (modified('comments')) { this._comments = changes.comments; } + if (modified('comments')) { this.comments = changes.comments; } if (modified('collapseState')) { this.initialCollapsibleState = changes.collapseState; } if (modified('canReply')) { this.canReply = changes.canReply!; } if (modified('state')) { this.state = changes.state!; } @@ -206,6 +212,10 @@ export class MainThreadCommentThread implements languages.CommentThread { if (modified('isTemplate')) { this._isTemplate = changes.isTemplate!; } } + hasComments(): boolean { + return !!this.comments && this.comments.length > 0; + } + dispose() { this._isDisposed = true; this._onDidChangeCollapsibleState.dispose(); @@ -294,6 +304,7 @@ export class MainThreadCommentController implements ICommentController { threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, + comments: languages.Comment[], isTemplate: boolean, editorId?: string ): languages.CommentThread { @@ -304,6 +315,7 @@ export class MainThreadCommentController implements ICommentController { threadId, URI.revive(resource).toString(), range, + comments, true, isTemplate, editorId @@ -520,7 +532,9 @@ export class MainThreadComments extends Disposable implements MainThreadComments extHostContext: IExtHostContext, @ICommentService private readonly _commentService: ICommentService, @IViewsService private readonly _viewsService: IViewsService, - @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, + @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService, + @IEditorService private readonly _editorService: IEditorService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); @@ -584,6 +598,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, + comments: languages.Comment[], extensionId: ExtensionIdentifier, isTemplate: boolean, editorId?: string @@ -594,7 +609,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments return undefined; } - return provider.createCommentThread(extensionId.value, commentThreadHandle, threadId, resource, range, isTemplate, editorId); + return provider.createCommentThread(extensionId.value, commentThreadHandle, threadId, resource, range, comments, isTemplate, editorId); } $updateCommentThread(handle: number, @@ -631,6 +646,38 @@ export class MainThreadComments extends Disposable implements MainThreadComments provider.updateCommentingRanges(resourceHints); } + async $revealCommentThread(handle: number, commentThreadHandle: number, commentUniqueIdInThread: number, options: languages.CommentThreadRevealOptions): Promise { + const provider = this._commentControllers.get(handle); + + if (!provider) { + return Promise.resolve(); + } + + const thread = provider.getAllComments().find(thread => thread.commentThreadHandle === commentThreadHandle); + if (!thread || !thread.isDocumentCommentThread()) { + return Promise.resolve(); + } + + const comment = thread.comments?.find(comment => comment.uniqueIdInThread === commentUniqueIdInThread); + + revealCommentThread(this._commentService, this._editorService, this._uriIdentityService, thread, comment, options.focusReply, undefined, options.preserveFocus); + } + + async $hideCommentThread(handle: number, commentThreadHandle: number): Promise { + const provider = this._commentControllers.get(handle); + + if (!provider) { + return Promise.resolve(); + } + + const thread = provider.getAllComments().find(thread => thread.commentThreadHandle === commentThreadHandle); + if (!thread || !thread.isDocumentCommentThread()) { + return Promise.resolve(); + } + + thread.collapsibleState = languages.CommentThreadCollapsibleState.Collapsed; + } + private registerView(commentsViewAlreadyRegistered: boolean) { if (!commentsViewAlreadyRegistered) { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadDebugService.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadDebugService.ts index f58ba4c4..36b5a4df 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -329,6 +329,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb compact: options.compact, compoundRoot: parentSession?.compoundRoot, saveBeforeRestart: saveBeforeStart, + testRun: options.testRun, suppressDebugStatusbar: options.suppressDebugStatusbar, suppressDebugToolbar: options.suppressDebugToolbar, diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index 48888b0d..9bf27279 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -207,11 +207,11 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape { if (editor instanceof MultiDiffEditorInput) { const diffEditors: TextDiffInputDto[] = []; for (const resource of (editor?.resources.get() ?? [])) { - if (resource.original && resource.modified) { + if (resource.originalUri && resource.modifiedUri) { diffEditors.push({ kind: TabInputKind.TextDiffInput, - original: resource.original, - modified: resource.modified + original: resource.originalUri, + modified: resource.modifiedUri }); } } diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadErrors.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadErrors.ts index 2d05a6a0..e1591179 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadErrors.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadErrors.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SerializedError, onUnexpectedError, ErrorNoTelemetry } from 'vs/base/common/errors'; +import { SerializedError, onUnexpectedError, transformErrorFromSerialization } from 'vs/base/common/errors'; import { extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { MainContext, MainThreadErrorsShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -16,11 +16,7 @@ export class MainThreadErrors implements MainThreadErrorsShape { $onUnexpectedError(err: any | SerializedError): void { if (err && err.$isError) { - const { name, message, stack } = err; - err = err.noTelemetry ? new ErrorNoTelemetry() : new Error(); - err.message = message; - err.name = name; - err.stack = stack; + err = transformErrorFromSerialization(err); } onUnexpectedError(err); } diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 6732d38f..7c4db0a5 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -6,7 +6,7 @@ import { Action } from 'vs/base/common/actions'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { SerializedError } from 'vs/base/common/errors'; +import { SerializedError, transformErrorFromSerialization } from 'vs/base/common/errors'; import { FileAccess } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -73,19 +73,13 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha this._internalExtensionService._onDidActivateExtension(extensionId, codeLoadingTime, activateCallTime, activateResolvedTime, activationReason); } $onExtensionRuntimeError(extensionId: ExtensionIdentifier, data: SerializedError): void { - const error = new Error(); - error.name = data.name; - error.message = data.message; - error.stack = data.stack; + const error = transformErrorFromSerialization(data); this._internalExtensionService._onExtensionRuntimeError(extensionId, error); console.error(`[${extensionId.value}]${error.message}`); console.error(error.stack); } async $onExtensionActivationError(extensionId: ExtensionIdentifier, data: SerializedError, missingExtensionDependency: MissingExtensionDependency | null): Promise { - const error = new Error(); - error.name = data.name; - error.message = data.message; - error.stack = data.stack; + const error = transformErrorFromSerialization(data); this._internalExtensionService._onDidActivateExtensionError(extensionId, error); diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 1781e73a..629af25a 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { createStringDataTransferItem, IReadonlyVSDataTransfer, VSDataTransfer } from 'vs/base/common/dataTransfer'; @@ -321,7 +320,9 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread selector: selector, provideMultiDocumentHighlights: (model: ITextModel, position: EditorPosition, otherModels: ITextModel[], token: CancellationToken): Promise | undefined> => { return this._proxy.$provideMultiDocumentHighlights(handle, model.uri, position, otherModels.map(model => model.uri), token).then(dto => { - if (isFalsyOrEmpty(dto)) { + // dto should be non-null + non-undefined + // dto length of 0 is valid, just no highlights, pass this through. + if (dto === undefined || dto === null) { return undefined; } const result = new ResourceMap(); @@ -612,6 +613,9 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread provideInlineCompletions: async (model: ITextModel, position: EditorPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise => { return this._proxy.$provideInlineCompletions(handle, model.uri, position, context, token); }, + provideInlineEdits: async (model: ITextModel, range: EditorRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise => { + return this._proxy.$provideInlineEdits(handle, model.uri, range, context, token); + }, handleItemDidShow: async (completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion, updatedInsertText: string): Promise => { if (supportsHandleEvents) { await this._proxy.$handleInlineCompletionDidShow(handle, completions.pid, item.idx, updatedInsertText); @@ -998,8 +1002,8 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread // --- mapped edits - $registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[]): void { - const provider = new MainThreadMappedEditsProvider(handle, this._proxy, this._uriIdentService); + $registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[], displayName: string): void { + const provider = new MainThreadMappedEditsProvider(displayName, handle, this._proxy, this._uriIdentService); this._registrations.set(handle, this._languageFeaturesService.mappedEditsProvider.register(selector, provider)); } } @@ -1124,7 +1128,7 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentDropEdit } } - async provideDocumentDropEdits(model: ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { + async provideDocumentDropEdits(model: ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { const request = this.dataTransfers.add(dataTransfer); try { const dataTransferDto = await typeConvert.DataTransfer.from(dataTransfer); @@ -1137,14 +1141,19 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentDropEdit return; } - return edits.map(edit => { - return { - ...edit, - yieldTo: edit.yieldTo?.map(x => ({ kind: new HierarchicalKind(x) })), - kind: edit.kind ? new HierarchicalKind(edit.kind) : undefined, - additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveDocumentOnDropFileData(request.id, dataId)), - }; - }); + return { + edits: edits.map(edit => { + return { + ...edit, + yieldTo: edit.yieldTo?.map(x => ({ kind: new HierarchicalKind(x) })), + kind: edit.kind ? new HierarchicalKind(edit.kind) : undefined, + additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveDocumentOnDropFileData(request.id, dataId)), + }; + }), + dispose: () => { + this._proxy.$releaseDocumentOnDropEdits(this._handle, request.id); + }, + }; } finally { request.dispose(); } @@ -1233,6 +1242,7 @@ export class MainThreadDocumentRangeSemanticTokensProvider implements languages. export class MainThreadMappedEditsProvider implements languages.MappedEditsProvider { constructor( + public readonly displayName: string, private readonly _handle: number, private readonly _proxy: ExtHostLanguageFeaturesShape, private readonly _uriService: IUriIdentityService, diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts new file mode 100644 index 00000000..411f5816 --- /dev/null +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageModelTools.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; +import { ExtHostContext, ExtHostLanguageModelToolsShape, MainContext, MainThreadLanguageModelToolsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolInvocation, IToolResult } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + +@extHostNamedCustomer(MainContext.MainThreadLanguageModelTools) +export class MainThreadLanguageModelTools extends Disposable implements MainThreadLanguageModelToolsShape { + + private readonly _proxy: ExtHostLanguageModelToolsShape; + private readonly _tools = this._register(new DisposableMap()); + private readonly _countTokenCallbacks = new Map(); + + constructor( + extHostContext: IExtHostContext, + @ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService, + ) { + super(); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageModelTools); + + this._register(this._languageModelToolsService.onDidChangeTools(e => this._proxy.$onDidChangeTools([...this._languageModelToolsService.getTools()]))); + } + + async $getTools(): Promise { + return Array.from(this._languageModelToolsService.getTools()); + } + + $invokeTool(dto: IToolInvocation, token: CancellationToken): Promise { + return this._languageModelToolsService.invokeTool( + dto, + (input, token) => this._proxy.$countTokensForInvocation(dto.callId, input, token), + token, + ); + } + + $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise { + const fn = this._countTokenCallbacks.get(callId); + if (!fn) { + throw new Error(`Tool invocation call ${callId} not found`); + } + + return fn(input, token); + } + + $registerTool(name: string): void { + const disposable = this._languageModelToolsService.registerToolImplementation( + name, + { + invoke: async (dto, countTokens, token) => { + try { + this._countTokenCallbacks.set(dto.callId, countTokens); + return await this._proxy.$invokeTool(dto, token); + } finally { + this._countTokenCallbacks.delete(dto.callId); + } + }, + }); + this._tools.set(name, disposable); + } + + $unregisterTool(name: string): void { + this._tools.deleteAndDispose(name); + } +} diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index 9b14928a..adea6654 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -3,18 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { AsyncIterableSource, DeferredPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { SerializedError, transformErrorForSerialization, transformErrorFromSerialization } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { ExtHostLanguageModelsShape, ExtHostContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ILanguageModelStatsService } from 'vs/workbench/contrib/chat/common/languageModelStats'; -import { ILanguageModelChatMetadata, IChatResponseFragment, ILanguageModelsService, IChatMessage, ILanguageModelChatSelector } from 'vs/workbench/contrib/chat/common/languageModels'; +import { ILanguageModelChatMetadata, IChatResponseFragment, ILanguageModelsService, IChatMessage, ILanguageModelChatSelector, ILanguageModelChatResponse } from 'vs/workbench/contrib/chat/common/languageModels'; import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; -import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationProviderCreateSessionOptions, IAuthenticationService, INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -24,7 +25,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { private readonly _proxy: ExtHostLanguageModelsShape; private readonly _store = new DisposableStore(); private readonly _providerRegistrations = new DisposableMap(); - private readonly _pendingProgress = new Map>(); + private readonly _pendingProgress = new Map; stream: AsyncIterableSource }>(); constructor( extHostContext: IExtHostContext, @@ -49,14 +50,23 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { const dipsosables = new DisposableStore(); dipsosables.add(this._chatProviderService.registerLanguageModelChat(identifier, { metadata, - provideChatResponse: async (messages, from, options, progress, token) => { + sendChatRequest: async (messages, from, options, token) => { const requestId = (Math.random() * 1e6) | 0; - this._pendingProgress.set(requestId, progress); + const defer = new DeferredPromise(); + const stream = new AsyncIterableSource(); + try { - await this._proxy.$provideLanguageModelResponse(handle, requestId, from, messages, options, token); - } finally { + this._pendingProgress.set(requestId, { defer, stream }); + await this._proxy.$startChatRequest(handle, requestId, from, messages, options, token); + } catch (err) { this._pendingProgress.delete(requestId); + throw err; } + + return { + result: defer.p, + stream: stream.asyncIterable + } satisfies ILanguageModelChatResponse; }, provideTokenCount: (str, token) => { return this._proxy.$provideTokenLength(handle, str, token); @@ -68,8 +78,28 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { this._providerRegistrations.set(handle, dipsosables); } - async $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise { - this._pendingProgress.get(requestId)?.report(chunk); + async $reportResponsePart(requestId: number, chunk: IChatResponseFragment): Promise { + const data = this._pendingProgress.get(requestId); + this._logService.trace('[LM] report response PART', Boolean(data), requestId, chunk); + if (data) { + data.stream.emitOne(chunk); + } + } + + async $reportResponseDone(requestId: number, err: SerializedError | undefined): Promise { + const data = this._pendingProgress.get(requestId); + this._logService.trace('[LM] report response DONE', Boolean(data), requestId, err); + if (data) { + this._pendingProgress.delete(requestId); + if (err) { + const error = transformErrorFromSerialization(err); + data.stream.reject(error); + data.defer.error(error); + } else { + data.stream.resolve(); + data.defer.complete(undefined); + } + } } $unregisterProvider(handle: number): void { @@ -84,21 +114,36 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { this._languageModelStatsService.update(identifier, extensionId, participant, tokenCount); } - async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { - this._logService.debug('[CHAT] extension request STARTED', extension.value, requestId); + async $tryStartChatRequest(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { + this._logService.trace('[CHAT] request STARTED', extension.value, requestId); - const task = this._chatProviderService.makeLanguageModelChatRequest(providerId, extension, messages, options, new Progress(value => { - this._proxy.$handleResponseFragment(requestId, value); - }), token); + const response = await this._chatProviderService.sendChatRequest(providerId, extension, messages, options, token); - task.catch(err => { - this._logService.error('[CHAT] extension request ERRORED', err, extension.value, requestId); - throw err; - }).finally(() => { + // !!! IMPORTANT !!! + // This method must return before the response is done (has streamed all parts) + // and because of that we consume the stream without awaiting + // !!! IMPORTANT !!! + const streaming = (async () => { + try { + for await (const part of response.stream) { + this._logService.trace('[CHAT] request PART', extension.value, requestId, part); + await this._proxy.$acceptResponsePart(requestId, part); + } + this._logService.trace('[CHAT] request DONE', extension.value, requestId); + } catch (err) { + this._logService.error('[CHAT] extension request ERRORED in STREAM', err, extension.value, requestId); + this._proxy.$acceptResponseDone(requestId, transformErrorForSerialization(err)); + } + })(); + + // When the response is done (signaled via its result) we tell the EH + Promise.allSettled([response.result, streaming]).then(() => { this._logService.debug('[CHAT] extension request DONE', extension.value, requestId); + this._proxy.$acceptResponseDone(requestId, undefined); + }, err => { + this._logService.error('[CHAT] extension request ERRORED', err, extension.value, requestId); + this._proxy.$acceptResponseDone(requestId, transformErrorForSerialization(err)); }); - - return task; } @@ -161,9 +206,9 @@ class LanguageModelAccessAuthProvider implements IAuthenticationProvider { if (this._session) { return [this._session]; } - return [await this.createSession(scopes || [], {})]; + return [await this.createSession(scopes || [])]; } - async createSession(scopes: string[], options: IAuthenticationProviderCreateSessionOptions): Promise { + async createSession(scopes: string[]): Promise { this._session = this._createFakeSession(scopes); this._onDidChangeSessions.fire({ added: [this._session], changed: [], removed: [] }); return this._session; diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebook.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebook.ts index c036901f..b50fee35 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -104,7 +104,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { }; } - const thisPriorityInfo = coalesce([{ isFromSettings: false, filenamePatterns: includes }, ...allPriorityInfo.get(viewType) ?? []]); + const thisPriorityInfo = coalesce([{ isFromSettings: false, filenamePatterns: includes }, ...allPriorityInfo.get(viewType) ?? []]); const otherEditorsPriorityInfo = Array.from(allPriorityInfo.keys()) .flatMap(key => { if (key !== viewType) { diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts index c18d0ac2..73a5b5a7 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts @@ -125,30 +125,45 @@ export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsS } } - async $tryCreateNotebook(options: { viewType: string; content?: NotebookDataDto }): Promise { - const ref = await this._notebookEditorModelResolverService.resolve({ untitledResource: undefined }, options.viewType); - - // untitled notebooks are disposed when they get saved. we should not hold a reference - // to such a disposed notebook and therefore dispose the reference as well - ref.object.notebook.onWillDispose(() => { - ref.dispose(); - }); - - // untitled notebooks are dirty by default - this._proxy.$acceptDirtyStateChanged(ref.object.resource, true); - - // apply content changes... slightly HACKY -> this triggers a change event if (options.content) { - const data = NotebookDto.fromNotebookDataDto(options.content); - ref.object.notebook.reset(data.cells, data.metadata, ref.object.notebook.transientOptions); + const ref = await this._notebookEditorModelResolverService.resolve({ untitledResource: undefined }, options.viewType); + + // untitled notebooks are disposed when they get saved. we should not hold a reference + // to such a disposed notebook and therefore dispose the reference as well + ref.object.notebook.onWillDispose(() => { + ref.dispose(); + }); + + // untitled notebooks with content are dirty by default + this._proxy.$acceptDirtyStateChanged(ref.object.resource, true); + + // apply content changes... slightly HACKY -> this triggers a change event + if (options.content) { + const data = NotebookDto.fromNotebookDataDto(options.content); + ref.object.notebook.reset(data.cells, data.metadata, ref.object.notebook.transientOptions); + } + return ref.object.notebook.uri; + } else { + // If we aren't adding content, we don't need to resolve the full editor model yet. + // This will allow us to adjust settings when the editor is opened, e.g. scratchpad + const notebook = await this._notebookEditorModelResolverService.createUntitledNotebookTextModel(options.viewType); + return notebook.uri; } - return ref.object.resource; } async $tryOpenNotebook(uriComponents: UriComponents): Promise { const uri = URI.revive(uriComponents); const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined); + + if (uriComponents.scheme === 'untitled') { + // untitled notebooks are disposed when they get saved. we should not hold a reference + // to such a disposed notebook and therefore dispose the reference as well + ref.object.notebook.onWillDispose(() => { + ref.dispose(); + }); + } + this._modelReferenceCollection.add(uri, ref); return uri; } diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts index 0db9edac..95bf3c60 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts @@ -105,6 +105,7 @@ export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape // preserve pre 1.38 behaviour to not make group active when preserveFocus: true // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 activation: options.preserveFocus ? EditorActivation.RESTORE : undefined, + label: options.label, override: viewType }; diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadQuickOpen.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadQuickOpen.ts index 001745bf..7d8ca24b 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadQuickOpen.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadQuickOpen.ts @@ -8,10 +8,12 @@ import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, Transf import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; interface QuickInputSession { input: IQuickInput; handlesToItems: Map; + store: DisposableStore; } function reviveIconPathUris(iconPath: { dark: URI; light?: URI | undefined }) { @@ -40,6 +42,9 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { } public dispose(): void { + for (const [_id, session] of this.sessions) { + session.store.dispose(); + } } $show(instance: number, options: IPickOptions, token: CancellationToken): Promise { @@ -121,38 +126,40 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { const sessionId = params.id; let session = this.sessions.get(sessionId); if (!session) { - + const store = new DisposableStore(); const input = params.type === 'quickPick' ? this._quickInputService.createQuickPick() : this._quickInputService.createInputBox(); - input.onDidAccept(() => { + store.add(input); + store.add(input.onDidAccept(() => { this._proxy.$onDidAccept(sessionId); - }); - input.onDidTriggerButton(button => { + })); + store.add(input.onDidTriggerButton(button => { this._proxy.$onDidTriggerButton(sessionId, (button as TransferQuickInputButton).handle); - }); - input.onDidChangeValue(value => { + })); + store.add(input.onDidChangeValue(value => { this._proxy.$onDidChangeValue(sessionId, value); - }); - input.onDidHide(() => { + })); + store.add(input.onDidHide(() => { this._proxy.$onDidHide(sessionId); - }); + })); if (params.type === 'quickPick') { // Add extra events specific for quickpick const quickpick = input as IQuickPick; - quickpick.onDidChangeActive(items => { + store.add(quickpick.onDidChangeActive(items => { this._proxy.$onDidChangeActive(sessionId, items.map(item => (item as TransferQuickPickItem).handle)); - }); - quickpick.onDidChangeSelection(items => { + })); + store.add(quickpick.onDidChangeSelection(items => { this._proxy.$onDidChangeSelection(sessionId, items.map(item => (item as TransferQuickPickItem).handle)); - }); - quickpick.onDidTriggerItemButton((e) => { + })); + store.add(quickpick.onDidTriggerItemButton((e) => { this._proxy.$onDidTriggerItemButton(sessionId, (e.item as TransferQuickPickItem).handle, (e.button as TransferQuickInputButton).handle); - }); + })); } session = { input, - handlesToItems: new Map() + handlesToItems: new Map(), + store }; this.sessions.set(sessionId, session); } @@ -212,7 +219,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { $dispose(sessionId: number): Promise { const session = this.sessions.get(sessionId); if (session) { - session.input.dispose(); + session.store.dispose(); this.sessions.delete(sessionId); } return Promise.resolve(undefined); diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadSCM.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadSCM.ts index 348cd234..f0feffa0 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Barrier } from 'vs/base/common/async'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; +import { derived, observableValue, observableValueOpts } from 'vs/base/common/observable'; import { IDisposable, DisposableStore, combinedDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService, InputValidationType, ISCMActionButtonDescriptor } from 'vs/workbench/contrib/scm/common/scm'; -import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, SCMHistoryItemGroupDto } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, SCMHistoryItemGroupDto, SCMHistoryItemDto } from '../common/extHost.protocol'; import { Command } from 'vs/editor/common/languages'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -39,6 +41,13 @@ function getIconFromIconDto(iconDto?: UriComponents | { light: UriComponents; da } } +function toISCMHistoryItem(historyItemDto: SCMHistoryItemDto): ISCMHistoryItem { + const icon = getIconFromIconDto(historyItemDto.icon); + const labels = historyItemDto.labels?.map(l => ({ title: l.title, icon: getIconFromIconDto(l.icon) })); + + return { ...historyItemDto, icon, labels }; +} + class SCMInputBoxContentProvider extends Disposable implements ITextModelContentProvider { constructor( textModelService: ITextModelService, @@ -151,16 +160,14 @@ class MainThreadSCMResource implements ISCMResource { } class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { + readonly currentHistoryItemGroupId = derived(this, reader => this.currentHistoryItemGroup.read(reader)?.id); + readonly currentHistoryItemGroupName = derived(this, reader => this.currentHistoryItemGroup.read(reader)?.name); + readonly currentHistoryItemGroupRevision = derived(this, reader => this.currentHistoryItemGroup.read(reader)?.revision); + readonly currentHistoryItemGroupRemoteId = derived(this, reader => this.currentHistoryItemGroup.read(reader)?.remote?.id); + readonly currentHistoryItemGroupRemoteRevision = derived(this, reader => this.currentHistoryItemGroup.read(reader)?.remote?.revision); - private _onDidChangeCurrentHistoryItemGroup = new Emitter(); - readonly onDidChangeCurrentHistoryItemGroup = this._onDidChangeCurrentHistoryItemGroup.event; - - private _currentHistoryItemGroup: ISCMHistoryItemGroup | undefined; - get currentHistoryItemGroup(): ISCMHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } - set currentHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup | undefined) { - this._currentHistoryItemGroup = historyItemGroup; - this._onDidChangeCurrentHistoryItemGroup.fire(); - } + private readonly _currentHistoryItemGroup = observableValueOpts({ owner: this, equalsFn: () => false }, undefined); + get currentHistoryItemGroup() { return this._currentHistoryItemGroup; } constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } @@ -168,14 +175,23 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None); } + async resolveHistoryItemGroupCommonAncestor2(historyItemGroupIds: string[]): Promise { + return this.proxy.$resolveHistoryItemGroupCommonAncestor2(this.handle, historyItemGroupIds, CancellationToken.None); + } + async provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise { const historyItems = await this.proxy.$provideHistoryItems(this.handle, historyItemGroupId, options, CancellationToken.None); - return historyItems?.map(historyItem => ({ ...historyItem, icon: getIconFromIconDto(historyItem.icon) })); + return historyItems?.map(historyItem => toISCMHistoryItem(historyItem)); + } + + async provideHistoryItems2(options: ISCMHistoryOptions): Promise { + const historyItems = await this.proxy.$provideHistoryItems2(this.handle, options, CancellationToken.None); + return historyItems?.map(historyItem => toISCMHistoryItem(historyItem)); } async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise { const historyItem = await this.proxy.$provideHistoryItemSummary(this.handle, historyItemId, historyItemParentId, CancellationToken.None); - return historyItem ? { ...historyItem, icon: getIconFromIconDto(historyItem.icon) } : undefined; + return historyItem ? toISCMHistoryItem(historyItem) : undefined; } async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise { @@ -188,6 +204,9 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { })); } + $onDidChangeCurrentHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup | undefined): void { + this._currentHistoryItemGroup.set(historyItemGroup, undefined); + } } class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { @@ -224,24 +243,20 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { get inputBoxTextModel(): ITextModel { return this._inputBoxTextModel; } get contextValue(): string { return this._providerId; } - get commitTemplate(): string { return this.features.commitTemplate || ''; } - get historyProvider(): ISCMHistoryProvider | undefined { return this._historyProvider; } get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; } get actionButton(): ISCMActionButtonDescriptor | undefined { return this.features.actionButton ?? undefined; } - get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; } - get count(): number | undefined { return this.features.count; } - private readonly _name: string | undefined; - get name(): string { return this._name ?? this._label; } + private readonly _count = observableValue(this, undefined); + get count() { return this._count; } - private readonly _onDidChangeCommitTemplate = new Emitter(); - readonly onDidChangeCommitTemplate: Event = this._onDidChangeCommitTemplate.event; + private readonly _statusBarCommands = observableValue(this, undefined); + get statusBarCommands() { return this._statusBarCommands; } - private readonly _onDidChangeStatusBarCommands = new Emitter(); - get onDidChangeStatusBarCommands(): Event { return this._onDidChangeStatusBarCommands.event; } + private readonly _name: string | undefined; + get name(): string { return this._name ?? this._label; } - private readonly _onDidChangeHistoryProvider = new Emitter(); - readonly onDidChangeHistoryProvider: Event = this._onDidChangeHistoryProvider.event; + private readonly _commitTemplate = observableValue(this, ''); + get commitTemplate() { return this._commitTemplate; } private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; @@ -249,7 +264,8 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { private _quickDiff: IDisposable | undefined; public readonly isSCM: boolean = true; - private _historyProvider: ISCMHistoryProvider | undefined; + private readonly _historyProvider = observableValue(this, undefined); + get historyProvider() { return this._historyProvider; } constructor( private readonly proxy: ExtHostSCMShape, @@ -277,11 +293,15 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { this._onDidChange.fire(); if (typeof features.commitTemplate !== 'undefined') { - this._onDidChangeCommitTemplate.fire(this.commitTemplate); + this._commitTemplate.set(features.commitTemplate, undefined); + } + + if (typeof features.count !== 'undefined') { + this._count.set(features.count, undefined); } if (typeof features.statusBarCommands !== 'undefined') { - this._onDidChangeStatusBarCommands.fire(this.statusBarCommands!); + this._statusBarCommands.set(features.statusBarCommands, undefined); } if (features.hasQuickDiffProvider && !this._quickDiff) { @@ -296,12 +316,11 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { this._quickDiff = undefined; } - if (features.hasHistoryProvider && !this._historyProvider) { - this._historyProvider = new MainThreadSCMHistoryProvider(this.proxy, this.handle); - this._onDidChangeHistoryProvider.fire(); - } else if (features.hasHistoryProvider === false && this._historyProvider) { - this._historyProvider = undefined; - this._onDidChangeHistoryProvider.fire(); + if (features.hasHistoryProvider && !this.historyProvider.get()) { + const historyProvider = new MainThreadSCMHistoryProvider(this.proxy, this.handle); + this._historyProvider.set(historyProvider, undefined); + } else if (features.hasHistoryProvider === false && this.historyProvider.get()) { + this._historyProvider.set(undefined, undefined); } } @@ -418,11 +437,11 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { } $onDidChangeHistoryProviderCurrentHistoryItemGroup(currentHistoryItemGroup?: SCMHistoryItemGroupDto): void { - if (!this._historyProvider) { + if (!this.historyProvider.get()) { return; } - this._historyProvider.currentHistoryItemGroup = currentHistoryItemGroup ?? undefined; + this._historyProvider.get()?.$onDidChangeCurrentHistoryItemGroup(currentHistoryItemGroup); } toJSON(): any { @@ -442,6 +461,7 @@ export class MainThreadSCM implements MainThreadSCMShape { private readonly _proxy: ExtHostSCMShape; private _repositories = new Map(); + private _repositoryBarriers = new Map(); private _repositoryDisposables = new Map(); private readonly _disposables = new DisposableStore(); @@ -472,9 +492,9 @@ export class MainThreadSCM implements MainThreadSCMShape { } async $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined, inputBoxDocumentUri: UriComponents): Promise { - // Eagerly create the text model for the input box - const inputBoxTextModelRef = await this.textModelService.createModelReference(URI.revive(inputBoxDocumentUri)); + this._repositoryBarriers.set(handle, new Barrier()); + const inputBoxTextModelRef = await this.textModelService.createModelReference(URI.revive(inputBoxDocumentUri)); const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri ? URI.revive(rootUri) : undefined, inputBoxTextModelRef.object.textEditorModel, this.quickDiffService, this._uriIdentService, this.workspaceContextService); const repository = this.scmService.registerSCMProvider(provider); this._repositories.set(handle, repository); @@ -484,6 +504,7 @@ export class MainThreadSCM implements MainThreadSCMShape { Event.filter(this.scmViewService.onDidFocusRepository, r => r === repository)(_ => this._proxy.$setSelectedSourceControl(handle)), repository.input.onDidChange(({ value }) => this._proxy.$onInputBoxValueChange(handle, value)) ); + this._repositoryDisposables.set(handle, disposable); if (this.scmViewService.focusedRepository === repository) { setTimeout(() => this._proxy.$setSelectedSourceControl(handle), 0); @@ -493,10 +514,11 @@ export class MainThreadSCM implements MainThreadSCMShape { setTimeout(() => this._proxy.$onInputBoxValueChange(handle, repository.input.value), 0); } - this._repositoryDisposables.set(handle, disposable); + this._repositoryBarriers.get(handle)?.open(); } - $updateSourceControl(handle: number, features: SCMProviderFeatures): void { + async $updateSourceControl(handle: number, features: SCMProviderFeatures): Promise { + await this._repositoryBarriers.get(handle)?.wait(); const repository = this._repositories.get(handle); if (!repository) { @@ -507,7 +529,8 @@ export class MainThreadSCM implements MainThreadSCMShape { provider.$updateSourceControl(features); } - $unregisterSourceControl(handle: number): void { + async $unregisterSourceControl(handle: number): Promise { + await this._repositoryBarriers.get(handle)?.wait(); const repository = this._repositories.get(handle); if (!repository) { @@ -521,7 +544,8 @@ export class MainThreadSCM implements MainThreadSCMShape { this._repositories.delete(handle); } - $registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /* multiDiffEditorEnableViewChanges */ boolean][], splices: SCMRawResourceSplices[]): void { + async $registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /* multiDiffEditorEnableViewChanges */ boolean][], splices: SCMRawResourceSplices[]): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -533,7 +557,8 @@ export class MainThreadSCM implements MainThreadSCMShape { provider.$spliceGroupResourceStates(splices); } - $updateGroup(sourceControlHandle: number, groupHandle: number, features: SCMGroupFeatures): void { + async $updateGroup(sourceControlHandle: number, groupHandle: number, features: SCMGroupFeatures): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -544,7 +569,8 @@ export class MainThreadSCM implements MainThreadSCMShape { provider.$updateGroup(groupHandle, features); } - $updateGroupLabel(sourceControlHandle: number, groupHandle: number, label: string): void { + async $updateGroupLabel(sourceControlHandle: number, groupHandle: number, label: string): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -555,7 +581,8 @@ export class MainThreadSCM implements MainThreadSCMShape { provider.$updateGroupLabel(groupHandle, label); } - $spliceResourceStates(sourceControlHandle: number, splices: SCMRawResourceSplices[]): void { + async $spliceResourceStates(sourceControlHandle: number, splices: SCMRawResourceSplices[]): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -566,7 +593,8 @@ export class MainThreadSCM implements MainThreadSCMShape { provider.$spliceGroupResourceStates(splices); } - $unregisterGroup(sourceControlHandle: number, handle: number): void { + async $unregisterGroup(sourceControlHandle: number, handle: number): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -577,7 +605,8 @@ export class MainThreadSCM implements MainThreadSCMShape { provider.$unregisterGroup(handle); } - $setInputBoxValue(sourceControlHandle: number, value: string): void { + async $setInputBoxValue(sourceControlHandle: number, value: string): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -587,7 +616,8 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.setValue(value, false); } - $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void { + async $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -597,7 +627,8 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.placeholder = placeholder; } - $setInputBoxEnablement(sourceControlHandle: number, enabled: boolean): void { + async $setInputBoxEnablement(sourceControlHandle: number, enabled: boolean): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -607,7 +638,8 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.enabled = enabled; } - $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void { + async $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -617,7 +649,8 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.visible = visible; } - $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType) { + async $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { return; @@ -626,7 +659,8 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.showValidationMessage(message, type); } - $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void { + async $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { @@ -643,7 +677,8 @@ export class MainThreadSCM implements MainThreadSCMShape { } } - $onDidChangeHistoryProviderCurrentHistoryItemGroup(sourceControlHandle: number, historyItemGroup: SCMHistoryItemGroupDto | undefined): void { + async $onDidChangeHistoryProviderCurrentHistoryItemGroup(sourceControlHandle: number, historyItemGroup: SCMHistoryItemGroupDto | undefined): Promise { + await this._repositoryBarriers.get(sourceControlHandle)?.wait(); const repository = this._repositories.get(sourceControlHandle); if (!repository) { diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts index 21092bb8..3429833a 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts @@ -60,7 +60,7 @@ export class MainThreadTerminalShellIntegration extends Disposable implements Ma // TerminalShellExecution.createDataStream // Debounce events to reduce the message count - when this listener is disposed the events will be flushed instanceDataListeners.get(instanceId)?.dispose(); - instanceDataListeners.set(instanceId, Event.accumulate(e.instance.onData, 50, this._store)(events => this._proxy.$shellExecutionData(instanceId, events.join()))); + instanceDataListeners.set(instanceId, Event.accumulate(e.instance.onData, 50, this._store)(events => this._proxy.$shellExecutionData(instanceId, events.join('')))); })); // onDidEndTerminalShellExecution diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadTesting.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadTesting.ts index a3641b66..706feac6 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -7,19 +7,18 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ISettableObservable, transaction } from 'vs/base/common/observable'; +import { ISettableObservable, observableValue, transaction } from 'vs/base/common/observable'; import { WellDefinedPrefixTree } from 'vs/base/common/prefixTree'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService'; import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { IMainThreadTestController, ITestService } from 'vs/workbench/contrib/testing/common/testService'; -import { CoverageDetails, ExtensionRunTestsRequest, IFileCoverage, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestRunProfileBitset, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; +import { CoverageDetails, ExtensionRunTestsRequest, IFileCoverage, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestControllerCapability, TestResultState, TestRunProfileBitset, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { ExtHostContext, ExtHostTestingShape, ILocationDto, ITestControllerPatch, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; @@ -29,8 +28,8 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh private readonly diffListener = this._register(new MutableDisposable()); private readonly testProviderRegistrations = new Map; - canRefresh: MutableObservableValue; + label: ISettableObservable; + capabilities: ISettableObservable; disposable: IDisposable; }>(); @@ -48,10 +47,11 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh provideTestFollowups: (req, token) => this.proxy.$provideTestFollowups(req, token), executeTestFollowup: id => this.proxy.$executeTestFollowup(id), disposeTestFollowups: ids => this.proxy.$disposeTestFollowups(ids), + getTestsRelatedToCode: (uri, position, token) => this.proxy.$getTestsRelatedToCode(uri, position, token), })); - this._register(this.testService.onDidCancelTestRun(({ runId }) => { - this.proxy.$cancelExtensionTestRun(runId); + this._register(this.testService.onDidCancelTestRun(({ runId, taskId }) => { + this.proxy.$cancelExtensionTestRun(runId, taskId); })); this._register(Event.debounce(testProfiles.onDidChange, (_last, e) => e)(() => { @@ -150,7 +150,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh let value = task.coverage.read(undefined); if (!value) { value = new TestCoverage(run, taskId, this.uriIdentityService, { - getCoverageDetails: (id, token) => this.proxy.$getCoverageDetails(id, token) + getCoverageDetails: (id, testId, token) => this.proxy.$getCoverageDetails(id, testId, token) .then(r => r.map(CoverageDetails.deserialize)), }); value.append(deserialized, tx); @@ -225,20 +225,26 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh /** * @inheritdoc */ - public $registerTestController(controllerId: string, labelStr: string, canRefreshValue: boolean) { + public $registerTestController(controllerId: string, _label: string, _capabilities: TestControllerCapability) { const disposable = new DisposableStore(); - const label = disposable.add(new MutableObservableValue(labelStr)); - const canRefresh = disposable.add(new MutableObservableValue(canRefreshValue)); + const label = observableValue(`${controllerId}.label`, _label); + const capabilities = observableValue(`${controllerId}.cap`, _capabilities); const controller: IMainThreadTestController = { id: controllerId, label, - canRefresh, + capabilities, syncTests: () => this.proxy.$syncTests(), refreshTests: token => this.proxy.$refreshTests(controllerId, token), configureRunProfile: id => this.proxy.$configureRunProfile(controllerId, id), runTests: (reqs, token) => this.proxy.$runControllerTests(reqs, token), startContinuousRun: (reqs, token) => this.proxy.$startContinuousRun(reqs, token), expandTest: (testId, levels) => this.proxy.$expandTest(testId, isFinite(levels) ? levels : -1), + getRelatedCode: (testId, token) => this.proxy.$getCodeRelatedToTest(testId, token).then(locations => + locations.map(l => ({ + uri: URI.revive(l.uri), + range: Range.lift(l.range) + })), + ), }; disposable.add(toDisposable(() => this.testProfiles.removeProfile(controllerId))); @@ -247,7 +253,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh this.testProviderRegistrations.set(controllerId, { instance: controller, label, - canRefresh, + capabilities, disposable }); } @@ -261,13 +267,16 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh return; } - if (patch.label !== undefined) { - controller.label.value = patch.label; - } + transaction(tx => { + if (patch.label !== undefined) { + controller.label.set(patch.label, tx); + } + + if (patch.capabilities !== undefined) { + controller.capabilities.set(patch.capabilities, tx); + } + }); - if (patch.canRefresh !== undefined) { - controller.canRefresh.value = patch.canRefresh; - } } /** @@ -301,11 +310,29 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh diff.map(d => TestsDiffOp.deserialize(this.uriIdentityService, d))); } + /** + * @inheritdoc + */ public async $runTests(req: ResolvedTestRunRequest, token: CancellationToken): Promise { const result = await this.testService.runResolvedTests(req, token); return result.id; } + /** + * @inheritdoc + */ + public async $getCoverageDetails(resultId: string, taskIndex: number, uri: UriComponents, token: CancellationToken): Promise { + const details = await this.resultService.getResult(resultId) + ?.tasks[taskIndex] + ?.coverage.get() + ?.getUri(URI.from(uri)) + ?.details(token); + + // Return empty if nothing. Some failure is always possible here because + // results might be cleared in the meantime. + return details || []; + } + public override dispose() { super.dispose(); for (const subscription of this.testProviderRegistrations.values()) { diff --git a/patched-vscode/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/patched-vscode/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 180932b6..880a6026 100644 --- a/patched-vscode/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/patched-vscode/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -14,7 +14,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IRequestService } from 'vs/platform/request/common/request'; +import { AuthInfo, Credentials, IRequestService } from 'vs/platform/request/common/request'; import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; @@ -28,6 +28,7 @@ import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSe import { EditorResourceAccessor, SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { ICanonicalUriService } from 'vs/platform/workspace/common/canonicalUri'; +import { revive } from 'vs/base/common/marshalling'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -146,7 +147,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const query = this._queryBuilder.file( includeFolder ? [includeFolder] : workspace.folders, - options + revive(options) ); return this._searchService.fileSearch(query, token).then(result => { @@ -164,7 +165,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const workspace = this._contextService.getWorkspace(); const folders = folder ? [folder] : workspace.folders.map(folder => folder.uri); - const query = this._queryBuilder.text(pattern, folders, options); + const query = this._queryBuilder.text(pattern, folders, revive(options)); query._reason = 'startTextSearch'; const onProgress = (p: ISearchProgressItem) => { @@ -223,6 +224,14 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return this._requestService.resolveProxy(url); } + $lookupAuthorization(authInfo: AuthInfo): Promise { + return this._requestService.lookupAuthorization(authInfo); + } + + $lookupKerberosAuthorization(url: string): Promise { + return this._requestService.lookupKerberosAuthorization(url); + } + $loadCertificates(): Promise { return this._requestService.loadCertificates(); } diff --git a/patched-vscode/src/vs/workbench/api/common/configurationExtensionPoint.ts b/patched-vscode/src/vs/workbench/api/common/configurationExtensionPoint.ts index 496651fe..8b7c408c 100644 --- a/patched-vscode/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/patched-vscode/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_REGEX, IConfigurationDefaults, configurationDefaultsSchemaId, IConfigurationDelta, getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_REGEX, IConfigurationDefaults, configurationDefaultsSchemaId, IConfigurationDelta, getDefaultValue, getAllConfigurationProperties, parseScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject, isUndefined } from 'vs/base/common/types'; @@ -160,11 +160,17 @@ defaultConfigurationExtPoint.setHandler((extensions, { added, removed }) => { const addedDefaultConfigurations = added.map(extension => { const overrides: IStringDictionary = objects.deepClone(extension.value); for (const key of Object.keys(overrides)) { + const registeredPropertyScheme = registeredProperties[key]; + if (registeredPropertyScheme?.disallowConfigurationDefault) { + extension.collector.warn(nls.localize('config.property.preventDefaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. This setting does not allow contributing configuration defaults.", key)); + delete overrides[key]; + continue; + } if (!OVERRIDE_PROPERTY_REGEX.test(key)) { - const registeredPropertyScheme = registeredProperties[key]; if (registeredPropertyScheme?.scope && !allowedScopes.includes(registeredPropertyScheme.scope)) { extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for machine-overridable, window, resource and language overridable scoped settings are supported.", key)); delete overrides[key]; + continue; } } } @@ -210,8 +216,7 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { const seenProperties = new Set(); - function handleConfiguration(node: IConfigurationNode, extension: IExtensionPointUser): IConfigurationNode[] { - const configurations: IConfigurationNode[] = []; + function handleConfiguration(node: IConfigurationNode, extension: IExtensionPointUser): IConfigurationNode { const configuration = objects.deepClone(node); if (configuration.title && (typeof configuration.title !== 'string')) { @@ -224,8 +229,7 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { configuration.extensionInfo = { id: extension.description.identifier.value, displayName: extension.description.displayName }; configuration.restrictedProperties = extension.description.capabilities?.untrustedWorkspaces?.supported === 'limited' ? extension.description.capabilities?.untrustedWorkspaces.restrictedConfigurations : undefined; configuration.title = configuration.title || extension.description.displayName || extension.description.identifier.value; - configurations.push(configuration); - return configurations; + return configuration; } function validateProperties(configuration: IConfigurationNode, extension: IExtensionPointUser): void { @@ -254,23 +258,7 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { continue; } seenProperties.add(key); - if (propertyConfiguration.scope) { - if (propertyConfiguration.scope.toString() === 'application') { - propertyConfiguration.scope = ConfigurationScope.APPLICATION; - } else if (propertyConfiguration.scope.toString() === 'machine') { - propertyConfiguration.scope = ConfigurationScope.MACHINE; - } else if (propertyConfiguration.scope.toString() === 'resource') { - propertyConfiguration.scope = ConfigurationScope.RESOURCE; - } else if (propertyConfiguration.scope.toString() === 'machine-overridable') { - propertyConfiguration.scope = ConfigurationScope.MACHINE_OVERRIDABLE; - } else if (propertyConfiguration.scope.toString() === 'language-overridable') { - propertyConfiguration.scope = ConfigurationScope.LANGUAGE_OVERRIDABLE; - } else { - propertyConfiguration.scope = ConfigurationScope.WINDOW; - } - } else { - propertyConfiguration.scope = ConfigurationScope.WINDOW; - } + propertyConfiguration.scope = propertyConfiguration.scope ? parseScope(propertyConfiguration.scope.toString()) : ConfigurationScope.WINDOW; } } const subNodes = configuration.allOf; @@ -288,9 +276,9 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { const configurations: IConfigurationNode[] = []; const value = extension.value; if (Array.isArray(value)) { - value.forEach(v => configurations.push(...handleConfiguration(v, extension))); + value.forEach(v => configurations.push(handleConfiguration(v, extension))); } else { - configurations.push(...handleConfiguration(value, extension)); + configurations.push(handleConfiguration(value, extension)); } extensionConfigurations.set(extension.description.identifier, configurations); addedConfigurations.push(...configurations); @@ -400,15 +388,11 @@ class SettingsTableRenderer extends Disposable implements IExtensionFeatureTable } render(manifest: IExtensionManifest): IRenderedData { - const configuration = manifest.contributes?.configuration; - let properties: any = {}; - if (Array.isArray(configuration)) { - configuration.forEach(config => { - properties = { ...properties, ...config.properties }; - }); - } else if (configuration) { - properties = configuration.properties; - } + const configuration: IConfigurationNode[] = manifest.contributes?.configuration + ? Array.isArray(manifest.contributes.configuration) ? manifest.contributes.configuration : [manifest.contributes.configuration] + : []; + + const properties = getAllConfigurationProperties(configuration); const contrib = properties ? Object.keys(properties) : []; const headers = [nls.localize('setting name', "ID"), nls.localize('description', "Description"), nls.localize('default', "Default")]; diff --git a/patched-vscode/src/vs/workbench/api/common/extHost.api.impl.ts b/patched-vscode/src/vs/workbench/api/common/extHost.api.impl.ts index 680d04cb..a9c8d66c 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHost.api.impl.ts @@ -14,7 +14,7 @@ import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; import { score, targetsNotebooks } from 'vs/editor/common/languageSelector'; import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration'; import { OverviewRulerLane } from 'vs/editor/common/model'; -import { ExtensionIdentifier, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as files from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService, ILoggerService, LogLevel } from 'vs/platform/log/common/log'; @@ -55,6 +55,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; +import { ExtHostLanguageModelTools } from 'vs/workbench/api/common/extHostLanguageModelTools'; import { IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages'; import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; @@ -84,7 +85,7 @@ import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration'; -import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; +import { IExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline'; @@ -105,7 +106,7 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; +import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContextNew, TextSearchMatchNew } from 'vs/workbench/services/search/common/searchExtTypes'; import type * as vscode from 'vscode'; export interface IExtensionRegistries { @@ -205,12 +206,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostWebviewPanels = rpcProtocol.set(ExtHostContext.ExtHostWebviewPanels, new ExtHostWebviewPanels(rpcProtocol, extHostWebviews, extHostWorkspace)); const extHostCustomEditors = rpcProtocol.set(ExtHostContext.ExtHostCustomEditors, new ExtHostCustomEditors(rpcProtocol, extHostDocuments, extensionStoragePaths, extHostWebviews, extHostWebviewPanels)); const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews)); - const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostLogService, extHostCommands, extHostDocumentsAndEditors)); + const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, accessor.get(IExtHostTesting)); const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); - const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands, initData.quality)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands, extHostDocuments)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); + const extHostLanguageModelTools = rpcProtocol.set(ExtHostContext.ExtHostLanguageModelTools, new ExtHostLanguageModelTools(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol)); const extHostStatusBar = rpcProtocol.set(ExtHostContext.ExtHostStatusBar, new ExtHostStatusBar(rpcProtocol, extHostCommands.converter)); @@ -289,9 +291,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return extHostAuthentication.getSession(extension, providerId, scopes, options as any); }, - getSessions(providerId: string, scopes: readonly string[]) { - checkProposedApiEnabled(extension, 'authGetSessions'); - return extHostAuthentication.getSessions(extension, providerId, scopes); + getAccounts(providerId: string) { + return extHostAuthentication.getAccounts(providerId); }, // TODO: remove this after GHPR and Codespaces move off of it async hasSession(providerId: string, scopes: readonly string[]) { @@ -741,15 +742,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return _asExtensionEvent(extHostTerminalService.onDidExecuteTerminalCommand)(listener, thisArg, disposables); }, onDidChangeTerminalShellIntegration(listener, thisArg?, disposables?) { - checkProposedApiEnabled(extension, 'terminalShellIntegration'); return _asExtensionEvent(extHostTerminalShellIntegration.onDidChangeTerminalShellIntegration)(listener, thisArg, disposables); }, onDidStartTerminalShellExecution(listener, thisArg?, disposables?) { - checkProposedApiEnabled(extension, 'terminalShellIntegration'); return _asExtensionEvent(extHostTerminalShellIntegration.onDidStartTerminalShellExecution)(listener, thisArg, disposables); }, onDidEndTerminalShellExecution(listener, thisArg?, disposables?) { - checkProposedApiEnabled(extension, 'terminalShellIntegration'); return _asExtensionEvent(extHostTerminalShellIntegration.onDidEndTerminalShellExecution)(listener, thisArg, disposables); }, get state() { @@ -961,10 +959,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Note, undefined/null have different meanings on "exclude" return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token); }, - findFiles2: (filePattern, options?, token?) => { + findFiles2: (filePattern: vscode.GlobPattern, options?: vscode.FindFiles2Options, token?: vscode.CancellationToken): Thenable => { checkProposedApiEnabled(extension, 'findFiles2'); return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token); }, + findFiles2New: (filePattern: vscode.GlobPattern[], options?: vscode.FindFiles2OptionsNew, token?: vscode.CancellationToken): Thenable => { + checkProposedApiEnabled(extension, 'findFiles2New'); + return extHostWorkspace.findFiles2New(filePattern, options, extension.identifier, token); + }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { checkProposedApiEnabled(extension, 'findTextInFiles'); let options: vscode.FindTextInFilesOptions; @@ -981,6 +983,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.identifier, token); }, + findTextInFilesNew: (query: vscode.TextSearchQueryNew, options?: vscode.FindTextInFilesOptionsNew, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse => { + checkProposedApiEnabled(extension, 'findTextInFilesNew'); + checkProposedApiEnabled(extension, 'textSearchProviderNew'); + return extHostWorkspace.findTextInFilesNew(query, extension.identifier, options || {}, token); + }, save: (uri) => { return extHostWorkspace.save(uri); }, @@ -1034,6 +1041,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return uriPromise.then(uri => { + extHostLogService.trace(`openTextDocument from ${extension.identifier}`); if (uri.scheme === Schemas.vscodeRemote && !uri.authority) { extHostApiDeprecation.report('workspace.openTextDocument', extension, `A URI of 'vscode-remote' scheme requires an authority.`); } @@ -1117,16 +1125,30 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, registerFileSearchProvider: (scheme: string, provider: vscode.FileSearchProvider) => { checkProposedApiEnabled(extension, 'fileSearchProvider'); - return extHostSearch.registerFileSearchProvider(scheme, provider); + return extHostSearch.registerFileSearchProviderOld(scheme, provider); }, registerTextSearchProvider: (scheme: string, provider: vscode.TextSearchProvider) => { checkProposedApiEnabled(extension, 'textSearchProvider'); - return extHostSearch.registerTextSearchProvider(scheme, provider); + return extHostSearch.registerTextSearchProviderOld(scheme, provider); }, registerAITextSearchProvider: (scheme: string, provider: vscode.AITextSearchProvider) => { // there are some dependencies on textSearchProvider, so we need to check for both checkProposedApiEnabled(extension, 'aiTextSearchProvider'); checkProposedApiEnabled(extension, 'textSearchProvider'); + return extHostSearch.registerAITextSearchProviderOld(scheme, provider); + }, + registerFileSearchProviderNew: (scheme: string, provider: vscode.FileSearchProviderNew) => { + checkProposedApiEnabled(extension, 'fileSearchProviderNew'); + return extHostSearch.registerFileSearchProvider(scheme, provider); + }, + registerTextSearchProviderNew: (scheme: string, provider: vscode.TextSearchProviderNew) => { + checkProposedApiEnabled(extension, 'textSearchProviderNew'); + return extHostSearch.registerTextSearchProvider(scheme, provider); + }, + registerAITextSearchProviderNew: (scheme: string, provider: vscode.AITextSearchProviderNew) => { + // there are some dependencies on textSearchProvider, so we need to check for both + checkProposedApiEnabled(extension, 'aiTextSearchProviderNew'); + checkProposedApiEnabled(extension, 'textSearchProviderNew'); return extHostSearch.registerAITextSearchProvider(scheme, provider); }, registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { @@ -1381,8 +1403,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: interactive const interactive: typeof vscode.interactive = { - // TODO Can be deleted after another Insiders - _version: 1, transferActiveChat(toWorkspace: vscode.Uri) { checkProposedApiEnabled(extension, 'interactive'); return extHostChatAgents2.transferActiveChat(toWorkspace); @@ -1407,10 +1427,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: chat const chat: typeof vscode.chat = { - // IMPORTANT - // this needs to be updated whenever the API proposal changes and breaks backwards compatibility - _version: 1, - registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { checkProposedApiEnabled(extension, 'chatProvider'); return extHostLanguageModels.registerLanguageModel(extension, id, provider, metadata); @@ -1430,28 +1446,24 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatParticipantPrivate'); return extHostChatAgents2.createDynamicChatAgent(extension, id, dynamicProps, handler); }, - attachContext(name: string, value: string | vscode.Uri | vscode.Location | unknown, location: vscode.ChatLocation.Panel) { - checkProposedApiEnabled(extension, 'chatVariableResolver'); - return extHostChatVariables.attachContext(name, value, location); - } + registerChatParticipantDetectionProvider(provider: vscode.ChatParticipantDetectionProvider) { + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); + return extHostChatAgents2.registerChatParticipantDetectionProvider(provider); + }, }; // namespace: lm const lm: typeof vscode.lm = { selectChatModels: (selector) => { - if (initData.quality === 'stable') { - console.warn(`[${ExtensionIdentifier.toKey(extension.identifier)}] This API is disabled in '${initData.environment.appName}'-stable.`); - return Promise.resolve([]); - } return extHostLanguageModels.selectLanguageModels(extension, selector ?? {}); }, onDidChangeChatModels: (listener, thisArgs?, disposables?) => { - if (initData.quality === 'stable') { - console.warn(`[${ExtensionIdentifier.toKey(extension.identifier)}] This API is disabled in '${initData.environment.appName}'-stable.`); - return Event.None(listener, thisArgs, disposables); - } return extHostLanguageModels.onDidChangeProviders(listener, thisArgs, disposables); }, + registerChatModelProvider: (id, provider, metadata) => { + checkProposedApiEnabled(extension, 'chatProvider'); + return extHostLanguageModels.registerLanguageModel(extension, id, provider, metadata); + }, // --- embeddings get embeddingModels() { checkProposedApiEnabled(extension, 'embeddings'); @@ -1472,7 +1484,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } else { return extHostEmbeddings.computeEmbeddings(embeddingsModel, input, token); } - } + }, + registerTool(toolId: string, tool: vscode.LanguageModelTool) { + checkProposedApiEnabled(extension, 'lmTools'); + return extHostLanguageModelTools.registerTool(extension, toolId, tool); + }, + invokeTool(toolId: string, parameters: vscode.LanguageModelToolInvocationOptions, token: vscode.CancellationToken) { + checkProposedApiEnabled(extension, 'lmTools'); + return extHostLanguageModelTools.invokeTool(toolId, parameters, token); + }, + get tools() { + checkProposedApiEnabled(extension, 'lmTools'); + return extHostLanguageModelTools.tools; + }, }; // namespace: speech @@ -1530,6 +1554,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState, CommentThreadState: extHostTypes.CommentThreadState, CommentThreadApplicability: extHostTypes.CommentThreadApplicability, + CommentThreadFocus: extHostTypes.CommentThreadFocus, CompletionItem: extHostTypes.CompletionItem, CompletionItemKind: extHostTypes.CompletionItemKind, CompletionItemTag: extHostTypes.CompletionItemTag, @@ -1589,6 +1614,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I Position: extHostTypes.Position, ProcessExecution: extHostTypes.ProcessExecution, ProgressLocation: extHostTypes.ProgressLocation, + QuickInputButtonLocation: extHostTypes.QuickInputButtonLocation, QuickInputButtons: extHostTypes.QuickInputButtons, Range: extHostTypes.Range, RelativePattern: extHostTypes.RelativePattern, @@ -1677,6 +1703,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TestResultState: extHostTypes.TestResultState, TestRunRequest: extHostTypes.TestRunRequest, TestMessage: extHostTypes.TestMessage, + TestMessageStackFrame: extHostTypes.TestMessageStackFrame, TestTag: extHostTypes.TestTag, TestRunProfileKind: extHostTypes.TestRunProfileKind, TextSearchCompleteMessageType: TextSearchCompleteMessageType, @@ -1722,27 +1749,36 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseProgressPart2: extHostTypes.ChatResponseProgressPart2, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, + ChatResponseReferencePart2: extHostTypes.ChatResponseReferencePart, + ChatResponseCodeCitationPart: extHostTypes.ChatResponseCodeCitationPart, ChatResponseWarningPart: extHostTypes.ChatResponseWarningPart, ChatResponseTextEditPart: extHostTypes.ChatResponseTextEditPart, ChatResponseMarkdownWithVulnerabilitiesPart: extHostTypes.ChatResponseMarkdownWithVulnerabilitiesPart, ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, ChatResponseDetectedParticipantPart: extHostTypes.ChatResponseDetectedParticipantPart, ChatResponseConfirmationPart: extHostTypes.ChatResponseConfirmationPart, + ChatResponseMovePart: extHostTypes.ChatResponseMovePart, + ChatResponseReferencePartStatusKind: extHostTypes.ChatResponseReferencePartStatusKind, ChatRequestTurn: extHostTypes.ChatRequestTurn, ChatResponseTurn: extHostTypes.ChatResponseTurn, ChatLocation: extHostTypes.ChatLocation, + ChatRequestEditorData: extHostTypes.ChatRequestEditorData, + ChatRequestNotebookData: extHostTypes.ChatRequestNotebookData, LanguageModelChatMessageRole: extHostTypes.LanguageModelChatMessageRole, LanguageModelChatMessage: extHostTypes.LanguageModelChatMessage, - LanguageModelChatMessage2: extHostTypes.LanguageModelChatMessage, // TODO@jrieken REMOVE - LanguageModelChatSystemMessage: extHostTypes.LanguageModelChatSystemMessage,// TODO@jrieken REMOVE - LanguageModelChatUserMessage: extHostTypes.LanguageModelChatUserMessage,// TODO@jrieken REMOVE - LanguageModelChatAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage,// TODO@jrieken REMOVE + LanguageModelChatMessageToolResultPart: extHostTypes.LanguageModelToolResultPart, + LanguageModelChatResponseTextPart: extHostTypes.LanguageModelTextPart, + LanguageModelChatResponseToolCallPart: extHostTypes.LanguageModelToolCallPart, LanguageModelError: extHostTypes.LanguageModelError, NewSymbolName: extHostTypes.NewSymbolName, NewSymbolNameTag: extHostTypes.NewSymbolNameTag, NewSymbolNameTriggerKind: extHostTypes.NewSymbolNameTriggerKind, InlineEdit: extHostTypes.InlineEdit, InlineEditTriggerKind: extHostTypes.InlineEditTriggerKind, + ExcludeSettingOptions: ExcludeSettingOptions, + TextSearchContextNew: TextSearchContextNew, + TextSearchMatchNew: TextSearchMatchNew, + TextSearchCompleteMessageTypeNew: TextSearchCompleteMessageType, }; }; } diff --git a/patched-vscode/src/vs/workbench/api/common/extHost.common.services.ts b/patched-vscode/src/vs/workbench/api/common/extHost.common.services.ts index d01a3219..0427ebe7 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHost.common.services.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHost.common.services.ts @@ -31,6 +31,7 @@ import { ExtHostManagedSockets, IExtHostManagedSockets } from 'vs/workbench/api/ import { ExtHostAuthentication, IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { ExtHostLanguageModels, IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; import { IExtHostTerminalShellIntegration, ExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration'; +import { ExtHostTesting, IExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; registerSingleton(IExtHostLocalizationService, ExtHostLocalizationService, InstantiationType.Delayed); registerSingleton(ILoggerService, ExtHostLoggerService, InstantiationType.Delayed); @@ -40,6 +41,7 @@ registerSingleton(IExtHostAuthentication, ExtHostAuthentication, InstantiationTy registerSingleton(IExtHostLanguageModels, ExtHostLanguageModels, InstantiationType.Eager); registerSingleton(IExtHostConfiguration, ExtHostConfiguration, InstantiationType.Eager); registerSingleton(IExtHostConsumerFileSystem, ExtHostConsumerFileSystem, InstantiationType.Eager); +registerSingleton(IExtHostTesting, ExtHostTesting, InstantiationType.Eager); registerSingleton(IExtHostDebugService, WorkerExtHostDebugService, InstantiationType.Eager); registerSingleton(IExtHostDecorations, ExtHostDecorations, InstantiationType.Eager); registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors, InstantiationType.Eager); diff --git a/patched-vscode/src/vs/workbench/api/common/extHost.protocol.ts b/patched-vscode/src/vs/workbench/api/common/extHost.protocol.ts index 2d10d6b7..6732f0ce 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHost.protocol.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHost.protocol.ts @@ -6,7 +6,6 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IRemoteConsoleLog } from 'vs/base/common/console'; -import { Location } from 'vs/editor/common/languages'; import { SerializedError } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; import { IMarkdownString } from 'vs/base/common/htmlContent'; @@ -40,6 +39,7 @@ import { IMarkerData } from 'vs/platform/markers/common/markers'; import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; import * as quickInput from 'vs/platform/quickinput/common/quickInput'; import { IRemoteConnectionData, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { AuthInfo, Credentials } from 'vs/platform/request/common/request'; import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; @@ -53,10 +53,11 @@ import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { ChatAgentLocation, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatTask, IChatTaskDto, IChatUserActionEvent, ChatAgentVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata, ILanguageModelChatSelector, ILanguageModelsChangeEvent } from 'vs/workbench/contrib/chat/common/languageModels'; -import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from 'vs/workbench/contrib/debug/common/debug'; +import { IToolData, IToolInvocation, IToolResult } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; +import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugTestRunReference, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from 'vs/workbench/contrib/debug/common/debug'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -65,11 +66,11 @@ import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkspaceSymbol, NotebookPriorityInfo } from 'vs/workbench/contrib/search/common/search'; import { IRawClosedNotebookFileMatch } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; import { IKeywordRecognitionEvent, ISpeechProviderMetadata, ISpeechToTextEvent, ITextToSpeechEvent } from 'vs/workbench/contrib/speech/common/speechService'; -import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestMessageFollowupRequest, TestMessageFollowupResponse, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; +import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestControllerCapability, TestMessageFollowupRequest, TestMessageFollowupResponse, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { RelatedInformationResult, RelatedInformationType } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation'; -import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions } from 'vs/workbench/services/authentication/common/authentication'; +import { AuthenticationSession, AuthenticationSessionAccount, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions, IAuthenticationProviderSessionOptions } from 'vs/workbench/services/authentication/common/authentication'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IExtensionDescriptionDelta, IStaticWorkspaceData } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; @@ -80,6 +81,7 @@ import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/out import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import * as search from 'vs/workbench/services/search/common/search'; +import { TextSearchCompleteMessage } from 'vs/workbench/services/search/common/searchExtTypes'; import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import type { TerminalShellExecutionCommandLineConfidence } from 'vscode'; @@ -144,10 +146,12 @@ export interface MainThreadCommentsShape extends IDisposable { $registerCommentController(handle: number, id: string, label: string, extensionId: string): void; $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; - $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, extensionId: ExtensionIdentifier, isTemplate: boolean, editorId?: string): languages.CommentThread | undefined; + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, comments: languages.Comment[], extensionId: ExtensionIdentifier, isTemplate: boolean, editorId?: string): languages.CommentThread | undefined; $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $updateCommentingRanges(handle: number, resourceHints?: languages.CommentingRangeResourceHint): void; + $revealCommentThread(handle: number, commentThreadHandle: number, commentUniqueIdInThread: number, options: languages.CommentThreadRevealOptions): Promise; + $hideCommentThread(handle: number, commentThreadHandle: number): void; } export interface AuthenticationForceNewSessionOptions { @@ -161,7 +165,7 @@ export interface MainThreadAuthenticationShape extends IDisposable { $ensureProvider(id: string): Promise; $sendDidChangeSessions(providerId: string, event: AuthenticationSessionsChangeEvent): void; $getSession(providerId: string, scopes: readonly string[], extensionId: string, extensionName: string, options: { createIfNone?: boolean; forceNewSession?: boolean | AuthenticationForceNewSessionOptions; clearSessionPreference?: boolean }): Promise; - $getSessions(providerId: string, scopes: readonly string[], extensionId: string, extensionName: string): Promise; + $getAccounts(providerId: string): Promise>; $removeSession(providerId: string, sessionId: string): Promise; } @@ -443,7 +447,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $resolvePasteFileData(handle: number, requestId: number, dataId: string): Promise; $resolveDocumentOnDropFileData(handle: number, requestId: number, dataId: string): Promise; $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; - $registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerMappedEditsProvider(handle: number, selector: IDocumentFilterDto[], displayName: string): void; } export interface MainThreadLanguagesShape extends IDisposable { @@ -1054,6 +1058,7 @@ export interface INotebookDocumentShowOptions { preserveFocus?: boolean; pinned?: boolean; selections?: ICellRange[]; + label?: string; } export type INotebookCellStatusBarEntryDto = Dto; @@ -1200,12 +1205,10 @@ export interface ExtHostSpeechShape { export interface MainThreadLanguageModelsShape extends IDisposable { $registerLanguageModelProvider(handle: number, identifier: string, metadata: ILanguageModelChatMetadata): void; $unregisterProvider(handle: number): void; - $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; - + $tryStartChatRequest(extension: ExtensionIdentifier, provider: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; + $reportResponsePart(requestId: number, chunk: IChatResponseFragment): Promise; + $reportResponseDone(requestId: number, error: SerializedError | undefined): Promise; $selectChatModels(selector: ILanguageModelChatSelector): Promise; - - $fetchResponse(extension: ExtensionIdentifier, provider: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; - $whenLanguageModelChatRequestMade(identifier: string, extension: ExtensionIdentifier, participant?: string, tokenCount?: number): void; $countTokens(provider: string, value: string | IChatMessage, token: CancellationToken): Promise; } @@ -1213,8 +1216,9 @@ export interface MainThreadLanguageModelsShape extends IDisposable { export interface ExtHostLanguageModelsShape { $acceptChatModelMetadata(data: ILanguageModelsChangeEvent): void; $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void; - $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; - $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; + $startChatRequest(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; + $acceptResponsePart(requestId: number, chunk: IChatResponseFragment): Promise; + $acceptResponseDone(requestId: number, error: SerializedError | undefined): Promise; $provideTokenLength(handle: number, value: string | IChatMessage, token: CancellationToken): Promise; } @@ -1242,6 +1246,8 @@ export interface IDynamicChatAgentProps { export interface MainThreadChatAgentsShape2 extends IDisposable { $registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: IDynamicChatAgentProps | undefined): void; + $registerChatParticipantDetectionProvider(handle: number): void; + $unregisterChatParticipantDetectionProvider(handle: number): void; $registerAgentCompletionsProvider(handle: number, id: string, triggerCharacters: string[]): void; $unregisterAgentCompletionsProvider(handle: number, id: string): void; $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; @@ -1274,14 +1280,26 @@ export type IChatAgentHistoryEntryDto = { }; export interface ExtHostChatAgentsShape2 { - $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; - $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; - $acceptFeedback(handle: number, result: IChatAgentResult, vote: ChatAgentVoteDirection, reportIssue?: boolean): void; + $invokeAgent(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; + $provideFollowups(request: Dto, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; + $acceptFeedback(handle: number, result: IChatAgentResult, voteAction: IChatVoteAction): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>; + $provideChatTitle(handle: number, context: IChatAgentHistoryEntryDto[], token: CancellationToken): Promise; $provideSampleQuestions(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise; $releaseSession(sessionId: string): void; + $detectChatParticipant(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[] }, options: { participants: IChatParticipantMetadata[]; location: ChatAgentLocation }, token: CancellationToken): Promise; +} +export interface IChatParticipantMetadata { + participant: string; + command?: string; + disambiguation: { categoryName: string; description: string; examples: string[] }[]; +} + +export interface IChatParticipantDetectionResult { + participant: string; + command?: string; } export type IChatVariableResolverProgressDto = @@ -1291,7 +1309,16 @@ export interface MainThreadChatVariablesShape extends IDisposable { $registerVariable(handle: number, data: IChatVariableData): void; $handleProgressChunk(requestId: string, progress: IChatVariableResolverProgressDto): Promise; $unregisterVariable(handle: number): void; - $attachContext(name: string, value: string | Dto | URI | unknown, location: ChatAgentLocation): void; +} + +export type IToolDataDto = Omit; + +export interface MainThreadLanguageModelToolsShape extends IDisposable { + $getTools(): Promise[]>; + $invokeTool(dto: IToolInvocation, token: CancellationToken): Promise; + $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise; + $registerTool(id: string): void; + $unregisterTool(name: string): void; } export type IChatRequestVariableValueDto = Dto; @@ -1300,6 +1327,12 @@ export interface ExtHostChatVariablesShape { $resolveVariable(handle: number, requestId: string, messageText: string, token: CancellationToken): Promise; } +export interface ExtHostLanguageModelToolsShape { + $onDidChangeTools(tools: IToolDataDto[]): void; + $invokeTool(dto: IToolInvocation, token: CancellationToken): Promise; + $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise; +} + export interface MainThreadUrlsShape extends IDisposable { $registerUriHandler(handle: number, extensionId: ExtensionIdentifier, extensionDisplayName: string): Promise; $unregisterUriHandler(handle: number): Promise; @@ -1364,6 +1397,7 @@ export interface ExtHostProfileContentHandlersShape { export interface ITextSearchComplete { limitHit?: boolean; + message?: TextSearchCompleteMessage | TextSearchCompleteMessage[]; } export interface MainThreadWorkspaceShape extends IDisposable { @@ -1374,6 +1408,8 @@ export interface MainThreadWorkspaceShape extends IDisposable { $saveAll(includeUntitled?: boolean): Promise; $updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents; name?: string }[]): Promise; $resolveProxy(url: string): Promise; + $lookupAuthorization(authInfo: AuthInfo): Promise; + $lookupKerberosAuthorization(url: string): Promise; $loadCertificates(): Promise; $requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise; $registerEditSessionIdentityProvider(handle: number, scheme: string): void; @@ -1502,7 +1538,9 @@ export type SCMRawResourceSplices = [ export interface SCMHistoryItemGroupDto { readonly id: string; readonly name: string; - readonly base?: Omit; + readonly revision?: string; + readonly base?: Omit, 'remote'>; + readonly remote?: Omit, 'remote'>; } export interface SCMHistoryItemDto { @@ -1512,6 +1550,15 @@ export interface SCMHistoryItemDto { readonly author?: string; readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; readonly timestamp?: number; + readonly statistics?: { + readonly files: number; + readonly insertions: number; + readonly deletions: number; + }; + readonly labels?: { + readonly title: string; + readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; + }[]; } export interface SCMHistoryItemChangeDto { @@ -1523,24 +1570,24 @@ export interface SCMHistoryItemChangeDto { export interface MainThreadSCMShape extends IDisposable { $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined, inputBoxDocumentUri: UriComponents): Promise; - $updateSourceControl(handle: number, features: SCMProviderFeatures): void; - $unregisterSourceControl(handle: number): void; + $updateSourceControl(handle: number, features: SCMProviderFeatures): Promise; + $unregisterSourceControl(handle: number): Promise; - $registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /* multiDiffEditorEnableViewChanges */ boolean][], splices: SCMRawResourceSplices[]): void; - $updateGroup(sourceControlHandle: number, handle: number, features: SCMGroupFeatures): void; - $updateGroupLabel(sourceControlHandle: number, handle: number, label: string): void; - $unregisterGroup(sourceControlHandle: number, handle: number): void; + $registerGroups(sourceControlHandle: number, groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /* multiDiffEditorEnableViewChanges */ boolean][], splices: SCMRawResourceSplices[]): Promise; + $updateGroup(sourceControlHandle: number, handle: number, features: SCMGroupFeatures): Promise; + $updateGroupLabel(sourceControlHandle: number, handle: number, label: string): Promise; + $unregisterGroup(sourceControlHandle: number, handle: number): Promise; - $spliceResourceStates(sourceControlHandle: number, splices: SCMRawResourceSplices[]): void; + $spliceResourceStates(sourceControlHandle: number, splices: SCMRawResourceSplices[]): Promise; - $setInputBoxValue(sourceControlHandle: number, value: string): void; - $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void; - $setInputBoxEnablement(sourceControlHandle: number, enabled: boolean): void; - $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void; - $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): void; - $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void; + $setInputBoxValue(sourceControlHandle: number, value: string): Promise; + $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): Promise; + $setInputBoxEnablement(sourceControlHandle: number, enabled: boolean): Promise; + $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): Promise; + $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): Promise; + $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): Promise; - $onDidChangeHistoryProviderCurrentHistoryItemGroup(sourceControlHandle: number, historyItemGroup: SCMHistoryItemGroupDto | undefined): void; + $onDidChangeHistoryProviderCurrentHistoryItemGroup(sourceControlHandle: number, historyItemGroup: SCMHistoryItemGroupDto | undefined): Promise; } export interface MainThreadQuickDiffShape extends IDisposable { @@ -1567,6 +1614,7 @@ export interface IStartDebuggingOptions { suppressDebugStatusbar?: boolean; suppressDebugView?: boolean; suppressSaveBeforeStart?: boolean; + testRun?: IDebugTestRunReference; } export interface MainThreadDebugServiceShape extends IDisposable { @@ -1806,7 +1854,7 @@ export interface ExtHostLabelServiceShape { } export interface ExtHostAuthenticationShape { - $getSessions(id: string, scopes?: string[]): Promise>; + $getSessions(id: string, scopes: string[] | undefined, options: IAuthenticationProviderSessionOptions): Promise>; $createSession(id: string, scopes: string[], options: IAuthenticationCreateSessionOptions): Promise; $removeSession(id: string, sessionId: string): Promise; $onDidChangeAuthenticationSessions(id: string, label: string): Promise; @@ -2166,6 +2214,7 @@ export interface ExtHostLanguageFeaturesShape { $resolveCompletionItem(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; $provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise; + $provideInlineEdits(handle: number, resource: UriComponents, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise; $handleInlineCompletionDidShow(handle: number, pid: number, idx: number, updatedInsertText: string): void; $handleInlineCompletionPartialAccept(handle: number, pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void; $freeInlineCompletionsList(handle: number, pid: number): void; @@ -2191,6 +2240,7 @@ export interface ExtHostLanguageFeaturesShape { $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseTypeHierarchy(handle: number, sessionId: string): void; $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise; + $releaseDocumentOnDropEdits(handle: number, cacheId: number): void; $provideMappedEdits(handle: number, document: UriComponents, codeBlocks: string[], context: IMappedEditsContextDto, token: CancellationToken): Promise; $provideInlineEdit(handle: number, document: UriComponents, context: languages.IInlineEditContext, token: CancellationToken): Promise; $freeInlineEdit(handle: number, pid: number): void; @@ -2302,9 +2352,11 @@ export interface ExtHostSCMShape { $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>; $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; + $provideHistoryItems2(sourceControlHandle: number, options: any, token: CancellationToken): Promise; $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; + $resolveHistoryItemGroupCommonAncestor2(sourceControlHandle: number, historyItemGroupIds: string[], token: CancellationToken): Promise; } export interface ExtHostQuickDiffShape { @@ -2700,13 +2752,13 @@ export const enum ExtHostTestingResource { export interface ExtHostTestingShape { $runControllerTests(req: IStartControllerTests[], token: CancellationToken): Promise<{ error?: string }[]>; $startContinuousRun(req: ICallProfileRunHandler[], token: CancellationToken): Promise<{ error?: string }[]>; - $cancelExtensionTestRun(runId: string | undefined): void; + $cancelExtensionTestRun(runId: string | undefined, taskId: string | undefined): void; /** Handles a diff of tests, as a result of a subscribeToDiffs() call */ $acceptDiff(diff: TestsDiffOp.Serialized[]): void; /** Expands a test item's children, by the given number of levels. */ $expandTest(testId: string, levels: number): Promise; /** Requests coverage details for a test run. Errors if not available. */ - $getCoverageDetails(coverageId: string, token: CancellationToken): Promise; + $getCoverageDetails(coverageId: string, testId: string | undefined, token: CancellationToken): Promise; /** Disposes resources associated with a test run. */ $disposeRun(runId: string): void; /** Configures a test run config. */ @@ -2717,6 +2769,8 @@ export interface ExtHostTestingShape { $syncTests(): Promise; /** Sets the active test run profiles */ $setDefaultRunProfiles(profiles: Record): void; + $getTestsRelatedToCode(uri: UriComponents, position: IPosition, token: CancellationToken): Promise; + $getCodeRelatedToTest(testId: string, token: CancellationToken): Promise; // --- test results: @@ -2745,14 +2799,14 @@ export interface IStringDetails { export interface ITestControllerPatch { label?: string; - canRefresh?: boolean; + capabilities?: TestControllerCapability; } export interface MainThreadTestingShape { // --- test lifecycle: /** Registers that there's a test controller with the given ID */ - $registerTestController(controllerId: string, label: string, canRefresh: boolean): void; + $registerTestController(controllerId: string, label: string, capability: TestControllerCapability): void; /** Updates the label of an existing test controller. */ $updateController(controllerId: string, patch: ITestControllerPatch): void; /** Diposes of the test controller with the given ID */ @@ -2763,6 +2817,8 @@ export interface MainThreadTestingShape { $unsubscribeFromDiffs(): void; /** Publishes that new tests were available on the given source. */ $publishDiff(controllerId: string, diff: TestsDiffOp.Serialized[]): void; + /** Gets coverage details from a test result. */ + $getCoverageDetails(resultId: string, taskIndex: number, uri: UriComponents, token: CancellationToken): Promise; // --- test run configurations: @@ -2812,6 +2868,7 @@ export const MainContext = { MainThreadEmbeddings: createProxyIdentifier('MainThreadEmbeddings'), MainThreadChatAgents2: createProxyIdentifier('MainThreadChatAgents2'), MainThreadChatVariables: createProxyIdentifier('MainThreadChatVariables'), + MainThreadLanguageModelTools: createProxyIdentifier('MainThreadChatSkills'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), MainThreadCommands: createProxyIdentifier('MainThreadCommands'), MainThreadComments: createProxyIdentifier('MainThreadComments'), @@ -2931,6 +2988,7 @@ export const ExtHostContext = { ExtHostInteractive: createProxyIdentifier('ExtHostInteractive'), ExtHostChatAgents2: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), + ExtHostLanguageModelTools: createProxyIdentifier('ExtHostChatSkills'), ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), ExtHostSpeech: createProxyIdentifier('ExtHostSpeech'), ExtHostEmbeddings: createProxyIdentifier('ExtHostEmbeddings'), diff --git a/patched-vscode/src/vs/workbench/api/common/extHostApiCommands.ts b/patched-vscode/src/vs/workbench/api/common/extHostApiCommands.ts index 5535cec0..6384178b 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostApiCommands.ts @@ -88,32 +88,62 @@ const newCommands: ApiCommand[] = [ [ApiCommandArgument.Uri, ApiCommandArgument.Position], new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), + new ApiCommand( + 'vscode.experimental.executeDefinitionProvider_recursive', '_executeDefinitionProvider_recursive', 'Execute all definition providers.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) + ), new ApiCommand( 'vscode.executeTypeDefinitionProvider', '_executeTypeDefinitionProvider', 'Execute all type definition providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), + new ApiCommand( + 'vscode.experimental.executeTypeDefinitionProvider_recursive', '_executeTypeDefinitionProvider_recursive', 'Execute all type definition providers.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) + ), new ApiCommand( 'vscode.executeDeclarationProvider', '_executeDeclarationProvider', 'Execute all declaration providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), + new ApiCommand( + 'vscode.experimental.executeDeclarationProvider_recursive', '_executeDeclarationProvider_recursive', 'Execute all declaration providers.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) + ), new ApiCommand( 'vscode.executeImplementationProvider', '_executeImplementationProvider', 'Execute all implementation providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), + new ApiCommand( + 'vscode.experimental.executeImplementationProvider_recursive', '_executeImplementationProvider_recursive', 'Execute all implementation providers.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult<(languages.Location | languages.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) + ), new ApiCommand( 'vscode.executeReferenceProvider', '_executeReferenceProvider', 'Execute all reference providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], new ApiCommandResult('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to)) ), + new ApiCommand( + 'vscode.experimental.executeReferenceProvider', '_executeReferenceProvider_recursive', 'Execute all reference providers.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to)) + ), // -- hover new ApiCommand( 'vscode.executeHoverProvider', '_executeHoverProvider', 'Execute all hover providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], new ApiCommandResult('A promise that resolves to an array of Hover-instances.', tryMapWith(typeConverters.Hover.to)) ), + new ApiCommand( + 'vscode.experimental.executeHoverProvider_recursive', '_executeHoverProvider_recursive', 'Execute all hover providers.', + [ApiCommandArgument.Uri, ApiCommandArgument.Position], + new ApiCommandResult('A promise that resolves to an array of Hover-instances.', tryMapWith(typeConverters.Hover.to)) + ), // -- selection range new ApiCommand( 'vscode.executeSelectionRangeProvider', '_executeSelectionRangeProvider', 'Execute selection range provider.', diff --git a/patched-vscode/src/vs/workbench/api/common/extHostAuthentication.ts b/patched-vscode/src/vs/workbench/api/common/extHostAuthentication.ts index 16b1d4a4..c5ba402e 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostAuthentication.ts @@ -32,7 +32,6 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { readonly onDidChangeSessions: Event = this._onDidChangeSessions.event; private _getSessionTaskSingler = new TaskSingler(); - private _getSessionsTaskSingler = new TaskSingler>(); constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService @@ -54,14 +53,9 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { }); } - async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: readonly string[]): Promise> { - const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); - const sortedScopes = [...scopes].sort().join(' '); - return await this._getSessionsTaskSingler.getOrCreate(`${extensionId} ${sortedScopes}`, async () => { - await this._proxy.$ensureProvider(providerId); - const extensionName = requestingExtension.displayName || requestingExtension.name; - return this._proxy.$getSessions(providerId, scopes, extensionId, extensionName); - }); + async getAccounts(providerId: string) { + await this._proxy.$ensureProvider(providerId); + return await this._proxy.$getAccounts(providerId); } async removeSession(providerId: string, sessionId: string): Promise { @@ -89,7 +83,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { }); } - async $createSession(providerId: string, scopes: string[], options: vscode.AuthenticationProviderCreateSessionOptions): Promise { + async $createSession(providerId: string, scopes: string[], options: vscode.AuthenticationProviderSessionOptions): Promise { const providerData = this._authenticationProviders.get(providerId); if (providerData) { return await providerData.provider.createSession(scopes, options); @@ -107,10 +101,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { throw new Error(`Unable to find authentication provider with handle: ${providerId}`); } - async $getSessions(providerId: string, scopes?: string[]): Promise> { + async $getSessions(providerId: string, scopes: ReadonlyArray | undefined, options: vscode.AuthenticationProviderSessionOptions): Promise> { const providerData = this._authenticationProviders.get(providerId); if (providerData) { - return await providerData.provider.getSessions(scopes); + return await providerData.provider.getSessions(scopes, options); } throw new Error(`Unable to find authentication provider with handle: ${providerId}`); diff --git a/patched-vscode/src/vs/workbench/api/common/extHostChatAgents2.ts b/patched-vscode/src/vs/workbench/api/common/extHostChatAgents2.ts index 9e8bd3e5..c7fbdfca 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -10,7 +10,8 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; -import { Disposable, DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { revive } from 'vs/base/common/marshalling'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -19,10 +20,11 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatProgressDto, IExtensionChatAgentMetadata, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatContentReference, IChatFollowup, IChatUserActionEvent, ChatAgentVoteDirection, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult, IChatAgentResultTimings } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentVoteDirection, IChatContentReference, IChatFollowup, IChatResponseErrorDetails, IChatUserActionEvent, IChatVoteAction } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; @@ -46,7 +48,7 @@ class ChatAgentResponseStream { this._isClosed = true; } - get timings() { + get timings(): IChatAgentResultTimings { return { firstProgress: this._firstProgress, totalElapsed: this._stopWatch.elapsed() @@ -70,7 +72,7 @@ class ChatAgentResponseStream { const _report = (progress: IChatProgressDto, task?: (progress: vscode.Progress) => Thenable) => { // Measure the time to the first progress update with real markdown content - if (typeof this._firstProgress === 'undefined' && 'content' in progress) { + if (typeof this._firstProgress === 'undefined' && (progress.kind === 'markdownContent' || progress.kind === 'markdownVuln')) { this._firstProgress = this._stopWatch.elapsed(); } @@ -91,7 +93,7 @@ class ChatAgentResponseStream { }; Promise.all([progressReporterPromise, task?.(progressReporter)]).then(([handle, res]) => { - if (handle !== undefined && res !== undefined) { + if (handle !== undefined) { this._proxy.$handleProgressChunk(this._request.requestId, typeConvert.ChatTaskResult.from(res), handle); } }); @@ -156,13 +158,16 @@ class ChatAgentResponseStream { return this; }, reference(value, iconPath) { + return this.reference2(value, iconPath); + }, + reference2(value, iconPath, options) { throwIfDone(this.reference); - if ('variableName' in value) { + if (typeof value === 'object' && 'variableName' in value) { checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); } - if ('variableName' in value && !value.value) { + if (typeof value === 'object' && 'variableName' in value && !value.value) { // The participant used this variable. Does that variable have any references to pull in? const matchingVarData = that._request.variables.variables.find(v => v.name === value.variableName); if (matchingVarData) { @@ -174,7 +179,7 @@ class ChatAgentResponseStream { } satisfies IChatContentReference)); } else { // Participant sent a variableName reference but the variable produced no references. Show variable reference with no value - const part = new extHostTypes.ChatResponseReferencePart(value, iconPath); + const part = new extHostTypes.ChatResponseReferencePart(value, iconPath, options); const dto = typeConvert.ChatResponseReferencePart.from(part); references = [dto]; } @@ -185,13 +190,21 @@ class ChatAgentResponseStream { // Something went wrong- that variable doesn't actually exist } } else { - const part = new extHostTypes.ChatResponseReferencePart(value, iconPath); + const part = new extHostTypes.ChatResponseReferencePart(value, iconPath, options); const dto = typeConvert.ChatResponseReferencePart.from(part); _report(dto); } return this; }, + codeCitation(value: vscode.Uri, license: string, snippet: string): void { + throwIfDone(this.codeCitation); + checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); + + const part = new extHostTypes.ChatResponseCodeCitationPart(value, license, snippet); + const dto = typeConvert.ChatResponseCodeCitationPart.from(part); + _report(dto); + }, textEdit(target, edits) { throwIfDone(this.textEdit); checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); @@ -210,11 +223,11 @@ class ChatAgentResponseStream { _report(dto); return this; }, - confirmation(title, message, data) { + confirmation(title, message, data, buttons) { throwIfDone(this.confirmation); checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); - const part = new extHostTypes.ChatResponseConfirmationPart(title, message, data); + const part = new extHostTypes.ChatResponseConfirmationPart(title, message, data, buttons); const dto = typeConvert.ChatResponseConfirmationPart.from(part); _report(dto); return this; @@ -227,14 +240,16 @@ class ChatAgentResponseStream { part instanceof extHostTypes.ChatResponseMarkdownWithVulnerabilitiesPart || part instanceof extHostTypes.ChatResponseDetectedParticipantPart || part instanceof extHostTypes.ChatResponseWarningPart || - part instanceof extHostTypes.ChatResponseConfirmationPart + part instanceof extHostTypes.ChatResponseConfirmationPart || + part instanceof extHostTypes.ChatResponseCodeCitationPart || + part instanceof extHostTypes.ChatResponseMovePart ) { checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); } if (part instanceof extHostTypes.ChatResponseReferencePart) { // Ensure variable reference values get fixed up - this.reference(part.value, part.iconPath); + this.reference2(part.value, part.iconPath, part.options); } else { const dto = typeConvert.ChatResponsePart.from(part, that._commandsConverter, that._sessionDisposables); _report(dto); @@ -256,14 +271,17 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS private readonly _agents = new Map(); private readonly _proxy: MainThreadChatAgentsShape2; + private static _participantDetectionProviderIdPool = 0; + private readonly _participantDetectionProviders = new Map(); + private readonly _sessionDisposables: DisposableMap = this._register(new DisposableMap()); private readonly _completionDisposables: DisposableMap = this._register(new DisposableMap()); constructor( mainContext: IMainContext, private readonly _logService: ILogService, - private readonly commands: ExtHostCommands, - private readonly quality: string | undefined + private readonly _commands: ExtHostCommands, + private readonly _documents: ExtHostDocuments ) { super(); this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); @@ -275,44 +293,94 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS createChatAgent(extension: IExtensionDescription, id: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { const handle = ExtHostChatAgents2._idPool++; - const agent = new ExtHostChatAgent(extension, this.quality, id, this._proxy, handle, handler); + const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler); this._agents.set(handle, agent); - if (agent.isAgentEnabled()) { - this._proxy.$registerAgent(handle, extension.identifier, id, {}, undefined); - } - + this._proxy.$registerAgent(handle, extension.identifier, id, {}, undefined); return agent.apiAgent; } createDynamicChatAgent(extension: IExtensionDescription, id: string, dynamicProps: vscode.DynamicChatParticipantProps, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { const handle = ExtHostChatAgents2._idPool++; - const agent = new ExtHostChatAgent(extension, this.quality, id, this._proxy, handle, handler); + const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler); this._agents.set(handle, agent); this._proxy.$registerAgent(handle, extension.identifier, id, { isSticky: true } satisfies IExtensionChatAgentMetadata, dynamicProps); return agent.apiAgent; } - async $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { + registerChatParticipantDetectionProvider(provider: vscode.ChatParticipantDetectionProvider): vscode.Disposable { + const handle = ExtHostChatAgents2._participantDetectionProviderIdPool++; + this._participantDetectionProviders.set(handle, provider); + this._proxy.$registerChatParticipantDetectionProvider(handle); + return toDisposable(() => { + this._participantDetectionProviders.delete(handle); + this._proxy.$unregisterChatParticipantDetectionProvider(handle); + }); + } + + async $detectChatParticipant(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[] }, options: { location: ChatAgentLocation; participants?: vscode.ChatParticipantMetadata[] }, token: CancellationToken): Promise { + const { request, location, history } = await this._createRequest(requestDto, context); + + const provider = this._participantDetectionProviders.get(handle); + if (!provider) { + return undefined; + } + + return provider.provideParticipantDetection( + typeConvert.ChatAgentRequest.to(request, location), + { history }, + { participants: options.participants, location: typeConvert.ChatLocation.to(options.location) }, + token + ); + } + + private async _createRequest(requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[] }) { + const request = revive(requestDto); + const convertedHistory = await this.prepareHistoryTurns(request.agentId, context); + + // in-place converting for location-data + let location: vscode.ChatRequestEditorData | vscode.ChatRequestNotebookData | undefined; + if (request.locationData?.type === ChatAgentLocation.Editor) { + // editor data + const document = this._documents.getDocument(request.locationData.document); + location = new extHostTypes.ChatRequestEditorData(document, typeConvert.Selection.to(request.locationData.selection), typeConvert.Range.to(request.locationData.wholeRange)); + + } else if (request.locationData?.type === ChatAgentLocation.Notebook) { + // notebook data + const cell = this._documents.getDocument(request.locationData.sessionInputUri); + location = new extHostTypes.ChatRequestNotebookData(cell); + + } else if (request.locationData?.type === ChatAgentLocation.Terminal) { + // TBD + } + + return { request, location, history: convertedHistory }; + } + + async $invokeAgent(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); } - // Init session disposables - let sessionDisposables = this._sessionDisposables.get(request.sessionId); - if (!sessionDisposables) { - sessionDisposables = new DisposableStore(); - this._sessionDisposables.set(request.sessionId, sessionDisposables); - } + let stream: ChatAgentResponseStream | undefined; - const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this.commands.converter, sessionDisposables); try { - const convertedHistory = await this.prepareHistoryTurns(request.agentId, context); + const { request, location, history } = await this._createRequest(requestDto, context); + + // Init session disposables + let sessionDisposables = this._sessionDisposables.get(request.sessionId); + if (!sessionDisposables) { + sessionDisposables = new DisposableStore(); + this._sessionDisposables.set(request.sessionId, sessionDisposables); + } + + stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._commands.converter, sessionDisposables); + const task = agent.invoke( - typeConvert.ChatAgentRequest.to(request), - { history: convertedHistory }, + typeConvert.ChatAgentRequest.to(request, location), + { history }, stream.apiObject, token ); @@ -324,7 +392,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS } catch (err) { const msg = `result.metadata MUST be JSON.stringify-able. Got error: ${err.message}`; this._logService.error(`[${agent.extension.identifier.value}] [@${agent.id}] ${msg}`, agent.extension); - return { errorDetails: { message: msg }, timings: stream.timings }; + return { errorDetails: { message: msg }, timings: stream?.timings, nextQuestion: result.nextQuestion }; } } let errorDetails: IChatResponseErrorDetails | undefined; @@ -338,7 +406,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS checkProposedApiEnabled(agent.extension, 'chatParticipantPrivate'); } - return { errorDetails, timings: stream.timings, metadata: result?.metadata } satisfies IChatAgentResult; + return { errorDetails, timings: stream?.timings, metadata: result?.metadata, nextQuestion: result?.nextQuestion } satisfies IChatAgentResult; }), token); } catch (e) { this._logService.error(e, agent.extension); @@ -350,12 +418,11 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS return { errorDetails: { message: toErrorMessage(e), responseIsIncomplete: true } }; } finally { - stream.close(); + stream?.close(); } } private async prepareHistoryTurns(agentId: string, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatRequestTurn | vscode.ChatResponseTurn)[]> { - const res: (vscode.ChatRequestTurn | vscode.ChatResponseTurn)[] = []; for (const h of context.history) { @@ -365,10 +432,18 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentValueReference.to), h.request.agentId)); + const varsWithoutTools = h.request.variables.variables + .filter(v => !v.isTool) + .map(typeConvert.ChatPromptReference.to); + const toolReferences = h.request.variables.variables + .filter(v => v.isTool) + .map(typeConvert.ChatLanguageModelToolReference.to); + const turn = new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, varsWithoutTools, h.request.agentId); + turn.toolReferences = toolReferences; + res.push(turn); // RESPONSE turn - const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.toContent(r, this.commands.converter))); + const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.toContent(r, this._commands.converter))); res.push(new extHostTypes.ChatResponseTurn(parts, result, h.request.agentId, h.request.command)); } @@ -379,12 +454,13 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS this._sessionDisposables.deleteAndDispose(sessionId); } - async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { + async $provideFollowups(requestDto: Dto, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return Promise.resolve([]); } + const request = revive(requestDto); const convertedHistory = await this.prepareHistoryTurns(agent.id, context); const ehResult = typeConvert.ChatAgentResult.to(result); @@ -402,7 +478,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS .map(f => typeConvert.ChatFollowup.from(f, request)); } - $acceptFeedback(handle: number, result: IChatAgentResult, vote: ChatAgentVoteDirection, reportIssue?: boolean): void { + $acceptFeedback(handle: number, result: IChatAgentResult, voteAction: IChatVoteAction): void { const agent = this._agents.get(handle); if (!agent) { return; @@ -410,7 +486,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS const ehResult = typeConvert.ChatAgentResult.to(result); let kind: extHostTypes.ChatResultFeedbackKind; - switch (vote) { + switch (voteAction.direction) { case ChatAgentVoteDirection.Down: kind = extHostTypes.ChatResultFeedbackKind.Unhelpful; break; @@ -418,9 +494,13 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS kind = extHostTypes.ChatResultFeedbackKind.Helpful; break; } - agent.acceptFeedback(reportIssue ? - Object.freeze({ result: ehResult, kind, reportIssue }) : - Object.freeze({ result: ehResult, kind })); + + const feedback: vscode.ChatResultFeedback = { + result: ehResult, + kind, + unhelpfulReason: isProposedApiEnabled(agent.extension, 'chatParticipantAdditions') ? voteAction.reason : undefined, + }; + agent.acceptFeedback(Object.freeze(feedback)); } $acceptAction(handle: number, result: IChatAgentResult, event: IChatUserActionEvent): void { @@ -433,7 +513,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS return; } - const ehAction = typeConvert.ChatAgentUserActionEvent.to(result, event, this.commands.converter); + const ehAction = typeConvert.ChatAgentUserActionEvent.to(result, event, this._commands.converter); if (ehAction) { agent.acceptAction(Object.freeze(ehAction)); } @@ -456,7 +536,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS const items = await agent.invokeCompletionProvider(query, token); - return items.map((i) => typeConvert.ChatAgentCompletionItem.from(i, this.commands.converter, disposables)); + return items.map((i) => typeConvert.ChatAgentCompletionItem.from(i, this._commands.converter, disposables)); } async $provideWelcomeMessage(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { @@ -468,6 +548,16 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS return await agent.provideWelcomeMessage(typeConvert.ChatLocation.to(location), token); } + async $provideChatTitle(handle: number, context: IChatAgentHistoryEntryDto[], token: CancellationToken): Promise { + const agent = this._agents.get(handle); + if (!agent) { + return; + } + + const history = await this.prepareHistoryTurns(agent.id, { history: context }); + return await agent.provideTitle({ history }, token); + } + async $provideSampleQuestions(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { @@ -483,7 +573,6 @@ class ExtHostChatAgent { private _followupProvider: vscode.ChatFollowupProvider | undefined; private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; - private _isDefault: boolean | undefined; private _helpTextPrefix: string | vscode.MarkdownString | undefined; private _helpTextVariablesPrefix: string | vscode.MarkdownString | undefined; private _helpTextPostfix: string | vscode.MarkdownString | undefined; @@ -493,12 +582,12 @@ class ExtHostChatAgent { private _supportIssueReporting: boolean | undefined; private _agentVariableProvider?: { provider: vscode.ChatParticipantCompletionItemProvider; triggerCharacters: string[] }; private _welcomeMessageProvider?: vscode.ChatWelcomeMessageProvider | undefined; + private _titleProvider?: vscode.ChatTitleProvider | undefined; private _requester: vscode.ChatRequesterInformation | undefined; private _supportsSlowReferences: boolean | undefined; constructor( public readonly extension: IExtensionDescription, - private readonly quality: string | undefined, public readonly id: string, private readonly _proxy: MainThreadChatAgentsShape2, private readonly _handle: number, @@ -521,11 +610,6 @@ class ExtHostChatAgent { return await this._agentVariableProvider.provider.provideCompletionItems(query, token) ?? []; } - public isAgentEnabled() { - // If in stable and this extension doesn't have the right proposed API, then don't register the agent - return !(this.quality === 'stable' && !isProposedApiEnabled(this.extension, 'chatParticipantPrivate')); - } - async provideFollowups(result: vscode.ChatResult, context: vscode.ChatContext, token: CancellationToken): Promise { if (!this._followupProvider) { return []; @@ -559,6 +643,14 @@ class ExtHostChatAgent { }); } + async provideTitle(context: vscode.ChatContext, token: CancellationToken): Promise { + if (!this._titleProvider) { + return; + } + + return await this._titleProvider.provideChatTitle(context, token) ?? undefined; + } + async provideSampleQuestions(location: vscode.ChatLocation, token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; @@ -583,10 +675,6 @@ class ExtHostChatAgent { } updateScheduled = true; queueMicrotask(() => { - if (!that.isAgentEnabled()) { - return; - } - this._proxy.$updateAgent(this._handle, { icon: !this._iconPath ? undefined : this._iconPath instanceof URI ? this._iconPath : @@ -635,15 +723,6 @@ class ExtHostChatAgent { that._followupProvider = v; updateMetadataSoon(); }, - get isDefault() { - checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); - return that._isDefault; - }, - set isDefault(v) { - checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); - that._isDefault = v; - updateMetadataSoon(); - }, get helpTextPrefix() { checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._helpTextPrefix; @@ -718,6 +797,15 @@ class ExtHostChatAgent { checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._welcomeMessageProvider; }, + set titleProvider(v) { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); + that._titleProvider = v; + updateMetadataSoon(); + }, + get titleProvider() { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); + return that._titleProvider; + }, onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatParticipantAdditions') ? undefined! : this._onDidPerformAction.event diff --git a/patched-vscode/src/vs/workbench/api/common/extHostChatVariables.ts b/patched-vscode/src/vs/workbench/api/common/extHostChatVariables.ts index 5f0bf7d2..dfc37201 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostChatVariables.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostChatVariables.ts @@ -64,10 +64,6 @@ export class ExtHostChatVariables implements ExtHostChatVariablesShape { this._proxy.$unregisterVariable(handle); }); } - - attachContext(name: string, value: string | vscode.Location | vscode.Uri | unknown, location: vscode.ChatLocation.Panel) { - this._proxy.$attachContext(name, extHostTypes.Location.isLocation(value) ? typeConvert.Location.from(value) : value, typeConvert.ChatLocation.from(location)); - } } class ChatVariableResolverResponseStream { diff --git a/patched-vscode/src/vs/workbench/api/common/extHostComments.ts b/patched-vscode/src/vs/workbench/api/common/extHostComments.ts index b3f54666..05761198 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostComments.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostComments.ts @@ -215,7 +215,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo } else if (rangesResult) { ranges = { ranges: rangesResult.ranges || [], - fileComments: rangesResult.fileComments || false + fileComments: rangesResult.enableFileComments || false }; } else { ranges = rangesResult ?? undefined; @@ -424,6 +424,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo this._id, this._uri, extHostTypeConverter.Range.from(this._range), + this._comments.map(cmt => convertToDTOComment(this, cmt, this._commentsMap, this.extensionDescription)), extensionDescription.identifier, this._isTemplate, editorId @@ -436,9 +437,6 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo this.eventuallyUpdateCommentThread(); })); - // set up comments after ctor to batch update events. - this.comments = _comments; - this._localDisposables.push({ dispose: () => { proxy.$deleteCommentThread( @@ -465,6 +463,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo set label(value: string | undefined) { that.label = value; }, get state(): vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined { return that.state; }, set state(value: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability }) { that.state = value; }, + reveal: (comment?: vscode.Comment | vscode.CommentThreadRevealOptions, options?: vscode.CommentThreadRevealOptions) => that.reveal(comment, options), + hide: () => that.hide(), dispose: () => { that.dispose(); } @@ -548,6 +548,31 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return; } + async reveal(commentOrOptions?: vscode.Comment | vscode.CommentThreadRevealOptions, options?: vscode.CommentThreadRevealOptions): Promise { + checkProposedApiEnabled(this.extensionDescription, 'commentReveal'); + let comment: vscode.Comment | undefined; + if (commentOrOptions && (commentOrOptions as vscode.Comment).body !== undefined) { + comment = commentOrOptions as vscode.Comment; + } else { + options = options ?? commentOrOptions as vscode.CommentThreadRevealOptions; + } + let commentToReveal = comment ? this._commentsMap.get(comment) : undefined; + commentToReveal ??= this._commentsMap.get(this._comments[0])!; + let preserveFocus: boolean = true; + let focusReply: boolean = false; + if (options?.focus === types.CommentThreadFocus.Reply) { + focusReply = true; + preserveFocus = false; + } else if (options?.focus === types.CommentThreadFocus.Comment) { + preserveFocus = false; + } + return proxy.$revealCommentThread(this._commentControllerHandle, this.handle, commentToReveal, { preserveFocus, focusReply }); + } + + async hide(): Promise { + return proxy.$hideCommentThread(this._commentControllerHandle, this.handle); + } + dispose() { this._isDiposed = true; this._acceptInputDisposables.dispose(); @@ -616,11 +641,11 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return this._activeComment; } - private _activeThread: vscode.CommentThread2 | undefined; + private _activeThread: ExtHostCommentThread | undefined; get activeCommentThread(): vscode.CommentThread2 | undefined { checkProposedApiEnabled(this._extension, 'activeComment'); - return this._activeThread; + return this._activeThread?.value; } private _localDisposables: types.Disposable[]; diff --git a/patched-vscode/src/vs/workbench/api/common/extHostDebugService.ts b/patched-vscode/src/vs/workbench/api/common/extHostDebugService.ts index d5ea2bdf..a5e22aa3 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostDebugService.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostDebugService.ts @@ -7,7 +7,8 @@ import { asPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Disposable as DisposableCls, toDisposable } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ISignService } from 'vs/platform/sign/common/sign'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -25,11 +26,11 @@ import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import { IExtHostConfiguration } from '../common/extHostConfiguration'; import { IExtHostVariableResolverProvider } from './extHostVariableResolverService'; -import { toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon as ThemeIconUtils } from 'vs/base/common/themables'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import * as Convert from 'vs/workbench/api/common/extHostTypeConverters'; import { coalesce } from 'vs/base/common/arrays'; +import { IExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; export const IExtHostDebugService = createDecorator('IExtHostDebugService'); @@ -60,7 +61,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri; } -export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDebugServiceShape { +export abstract class ExtHostDebugServiceBase extends DisposableCls implements IExtHostDebugService, ExtHostDebugServiceShape { readonly _serviceBrand: undefined; @@ -123,7 +124,10 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E @IExtHostEditorTabs protected _editorTabs: IExtHostEditorTabs, @IExtHostVariableResolverProvider private _variableResolver: IExtHostVariableResolverProvider, @IExtHostCommands private _commands: IExtHostCommands, + @IExtHostTesting private _testing: IExtHostTesting, ) { + super(); + this._configProviderHandleCounter = 0; this._configProviders = []; @@ -136,25 +140,25 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E this._debugAdapters = new Map(); this._debugAdaptersTrackers = new Map(); - this._onDidStartDebugSession = new Emitter(); - this._onDidTerminateDebugSession = new Emitter(); - this._onDidChangeActiveDebugSession = new Emitter(); - this._onDidReceiveDebugSessionCustomEvent = new Emitter(); + this._onDidStartDebugSession = this._register(new Emitter()); + this._onDidTerminateDebugSession = this._register(new Emitter()); + this._onDidChangeActiveDebugSession = this._register(new Emitter()); + this._onDidReceiveDebugSessionCustomEvent = this._register(new Emitter()); this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService); - this._onDidChangeBreakpoints = new Emitter(); + this._onDidChangeBreakpoints = this._register(new Emitter()); - this._onDidChangeActiveStackItem = new Emitter(); + this._onDidChangeActiveStackItem = this._register(new Emitter()); this._activeDebugConsole = new ExtHostDebugConsole(this._debugServiceProxy); this._breakpoints = new Map(); this._extensionService.getExtensionRegistry().then((extensionRegistry: ExtensionDescriptionRegistry) => { - extensionRegistry.onDidChange(_ => { + this._register(extensionRegistry.onDidChange(_ => { this.registerAllDebugTypes(extensionRegistry); - }); + })); this.registerAllDebugTypes(extensionRegistry); }); } @@ -169,7 +173,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return item ? this.convertVisualizerTreeItem(treeId, item) : undefined; } - public registerDebugVisualizationTree(manifest: Readonly, id: string, provider: vscode.DebugVisualizationTree): vscode.Disposable { + public registerDebugVisualizationTree(manifest: IExtensionDescription, id: string, provider: vscode.DebugVisualizationTree): vscode.Disposable { const extensionId = ExtensionIdentifier.toKey(manifest.identifier); const key = this.extensionVisKey(extensionId, id); if (this._debugVisualizationProviders.has(key)) { @@ -464,6 +468,8 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise { + const testRunMeta = options.testRun && this._testing.getMetadataForRun(options.testRun); + return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, { parentSessionID: options.parentSession ? options.parentSession.id : undefined, lifecycleManagedByParent: options.lifecycleManagedByParent, @@ -471,6 +477,10 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E noDebug: options.noDebug, compact: options.compact, suppressSaveBeforeStart: options.suppressSaveBeforeStart, + testRun: testRunMeta && { + runId: testRunMeta.runId, + taskId: testRunMeta.taskId, + }, // Check debugUI for back-compat, #147264 suppressDebugStatusbar: options.suppressDebugStatusbar ?? (options as any).debugUI?.simple, @@ -1245,8 +1255,9 @@ export class WorkerExtHostDebugService extends ExtHostDebugServiceBase { @IExtHostConfiguration configurationService: IExtHostConfiguration, @IExtHostEditorTabs editorTabs: IExtHostEditorTabs, @IExtHostVariableResolverProvider variableResolver: IExtHostVariableResolverProvider, - @IExtHostCommands commands: IExtHostCommands + @IExtHostCommands commands: IExtHostCommands, + @IExtHostTesting testing: IExtHostTesting, ) { - super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands); + super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands, testing); } } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostDiagnostics.ts b/patched-vscode/src/vs/workbench/api/common/extHostDiagnostics.ts index e23ce395..da9a087f 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -234,7 +234,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { private static _idPool: number = 0; private static readonly _maxDiagnosticsPerFile: number = 1000; - private static readonly _maxDiagnosticsTotal: number = 1.1 * ExtHostDiagnostics._maxDiagnosticsPerFile; + private static readonly _maxDiagnosticsTotal: number = 1.1 * this._maxDiagnosticsPerFile; private readonly _proxy: MainThreadDiagnosticsShape; private readonly _collections = new Map(); diff --git a/patched-vscode/src/vs/workbench/api/common/extHostDialogs.ts b/patched-vscode/src/vs/workbench/api/common/extHostDialogs.ts index c33cb070..372037aa 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostDialogs.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostDialogs.ts @@ -7,7 +7,7 @@ import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { MainContext, MainThreadDiaglogsShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class ExtHostDialogs { @@ -17,7 +17,7 @@ export class ExtHostDialogs { this._proxy = mainContext.getProxy(MainContext.MainThreadDialogs); } - showOpenDialog(extension: IRelaxedExtensionDescription, options?: vscode.OpenDialogOptions): Promise { + showOpenDialog(extension: IExtensionDescription, options?: vscode.OpenDialogOptions): Promise { if (options?.allowUIResources) { checkProposedApiEnabled(extension, 'showLocal'); } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostDocumentData.ts b/patched-vscode/src/vs/workbench/api/common/extHostDocumentData.ts index ee321b25..b3edbad9 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostDocumentData.ts @@ -76,6 +76,9 @@ export class ExtHostDocumentData extends MirrorTextModel { validateRange(ran) { return that._validateRange(ran); }, validatePosition(pos) { return that._validatePosition(pos); }, getWordRangeAtPosition(pos, regexp?) { return that._getWordRangeAtPosition(pos, regexp); }, + [Symbol.for('debug.description')]() { + return `TextDocument(${that._uri.toString()})`; + } }; } return Object.freeze(this._document); diff --git a/patched-vscode/src/vs/workbench/api/common/extHostEmbedding.ts b/patched-vscode/src/vs/workbench/api/common/extHostEmbedding.ts index f99cd387..4c712aee 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostEmbedding.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostEmbedding.ts @@ -39,6 +39,7 @@ export class ExtHostEmbeddings implements ExtHostEmbeddingsShape { this._provider.set(handle, { id: embeddingsModel, provider }); return toDisposable(() => { + this._allKnownModels.delete(embeddingsModel); this._proxy.$unregisterEmbeddingProvider(handle); this._provider.delete(handle); }); diff --git a/patched-vscode/src/vs/workbench/api/common/extHostExtensionActivator.ts b/patched-vscode/src/vs/workbench/api/common/extHostExtensionActivator.ts index 2c51f37a..d6b20d61 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -5,7 +5,7 @@ import type * as vscode from 'vscode'; import * as errors from 'vs/base/common/errors'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ExtensionIdentifier, ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions'; import { ExtensionActivationReason, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions'; @@ -119,7 +119,7 @@ export class ActivatedExtension { public readonly activationTimes: ExtensionActivationTimes; public readonly module: IExtensionModule; public readonly exports: IExtensionAPI | undefined; - public readonly subscriptions: IDisposable[]; + public readonly disposable: IDisposable; constructor( activationFailed: boolean, @@ -127,32 +127,32 @@ export class ActivatedExtension { activationTimes: ExtensionActivationTimes, module: IExtensionModule, exports: IExtensionAPI | undefined, - subscriptions: IDisposable[] + disposable: IDisposable ) { this.activationFailed = activationFailed; this.activationFailedError = activationFailedError; this.activationTimes = activationTimes; this.module = module; this.exports = exports; - this.subscriptions = subscriptions; + this.disposable = disposable; } } export class EmptyExtension extends ActivatedExtension { constructor(activationTimes: ExtensionActivationTimes) { - super(false, null, activationTimes, { activate: undefined, deactivate: undefined }, undefined, []); + super(false, null, activationTimes, { activate: undefined, deactivate: undefined }, undefined, Disposable.None); } } export class HostExtension extends ActivatedExtension { constructor() { - super(false, null, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []); + super(false, null, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, Disposable.None); } } class FailedExtension extends ActivatedExtension { constructor(activationError: Error) { - super(true, activationError, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []); + super(true, activationError, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, Disposable.None); } } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostExtensionService.ts b/patched-vscode/src/vs/workbench/api/common/extHostExtensionService.ts index 4ea250c3..97ecf09e 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostExtensionService.ts @@ -10,7 +10,7 @@ import * as path from 'vs/base/common/path'; import * as performance from 'vs/base/common/performance'; import { originalFSPath, joinPath, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { asPromise, Barrier, IntervalTimer, timeout } from 'vs/base/common/async'; -import { dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { dispose, toDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; @@ -24,7 +24,7 @@ import { MissingExtensionDependency, ActivationKind, checkProposedApiEnabled, is import { ExtensionDescriptionRegistry, IActivationEventsReader } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import * as errors from 'vs/base/common/errors'; import type * as vscode from 'vscode'; -import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { VSBuffer } from 'vs/base/common/buffer'; import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime, ManagedResolvedAuthority as ExtHostManagedResolvedAuthority } from 'vs/workbench/api/common/extHostTypes'; @@ -177,10 +177,10 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._secretState = new ExtHostSecretState(this._extHostContext); this._storagePath = storagePath; - this._instaService = instaService.createChild(new ServiceCollection( + this._instaService = this._store.add(instaService.createChild(new ServiceCollection( [IExtHostStorage, this._storage], [IExtHostSecretState, this._secretState] - )); + ))); this._activator = this._register(new ExtensionsActivator( this._myRegistry, @@ -409,9 +409,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme // clean up subscriptions try { - dispose(extension.subscriptions); + extension.disposable.dispose(); } catch (err) { - this._logService.error(`An error occurred when deactivating the subscriptions for extension '${extensionId.value}':`); + this._logService.error(`An error occurred when disposing the subscriptions for extension '${extensionId.value}':`); this._logService.error(err); } @@ -482,25 +482,26 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._logService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value}, startup: ${reason.startup}, activationEvent: '${reason.activationEvent}'${extensionDescription.identifier.value !== reason.extensionId.value ? `, root cause: ${reason.extensionId.value}` : ``}`); this._logService.flush(); + const extensionInternalStore = new DisposableStore(); // disposables that follow the extension lifecycle const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); return Promise.all([ this._loadCommonJSModule(extensionDescription, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder), - this._loadExtensionContext(extensionDescription) + this._loadExtensionContext(extensionDescription, extensionInternalStore) ]).then(values => { performance.mark(`code/extHost/willActivateExtension/${extensionDescription.identifier.value}`); - return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); + return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], extensionInternalStore, activationTimesBuilder); }).then((activatedExtension) => { performance.mark(`code/extHost/didActivateExtension/${extensionDescription.identifier.value}`); return activatedExtension; }); } - private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { + private _loadExtensionContext(extensionDescription: IExtensionDescription, extensionInternalStore: DisposableStore): Promise { - const lanuageModelAccessInformation = this._extHostLanguageModels.createLanguageModelAccessInformation(extensionDescription); - const globalState = new ExtensionGlobalMemento(extensionDescription, this._storage); - const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); - const secrets = new ExtensionSecrets(extensionDescription, this._secretState); + const languageModelAccessInformation = this._extHostLanguageModels.createLanguageModelAccessInformation(extensionDescription); + const globalState = extensionInternalStore.add(new ExtensionGlobalMemento(extensionDescription, this._storage)); + const workspaceState = extensionInternalStore.add(new ExtensionMemento(extensionDescription.identifier.value, false, this._storage)); + const secrets = extensionInternalStore.add(new ExtensionSecrets(extensionDescription, this._secretState)); const extensionMode = extensionDescription.isUnderDevelopment ? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development) : ExtensionMode.Production; @@ -526,7 +527,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme workspaceState, secrets, subscriptions: [], - get languageModelAccessInformation() { return lanuageModelAccessInformation; }, + get languageModelAccessInformation() { return languageModelAccessInformation; }, get extensionUri() { return extensionDescription.extensionLocation; }, get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, @@ -568,7 +569,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme }); } - private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: vscode.ExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: vscode.ExtensionContext, extensionInternalStore: IDisposable, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { // Make sure the extension's surface is not undefined extensionModule = extensionModule || { activate: undefined, @@ -576,7 +577,10 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme }; return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => { - return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions); + return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, toDisposable(() => { + extensionInternalStore.dispose(); + dispose(context.subscriptions); + })); }); } @@ -615,7 +619,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme }); } - private _activateAllStartupFinishedDeferred(extensions: Readonly[], start: number = 0): void { + private _activateAllStartupFinishedDeferred(extensions: IExtensionDescription[], start: number = 0): void { const timeBudget = 50; // 50 milliseconds const startTime = Date.now(); @@ -1230,7 +1234,7 @@ class SyncedActivationEventsReader implements IActivationEventsReader { this.addActivationEvents(activationEvents); } - public readActivationEvents(extensionDescription: Readonly): string[] { + public readActivationEvents(extensionDescription: IExtensionDescription): string[] { return this._map.get(extensionDescription.identifier) ?? []; } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/patched-vscode/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 215ff5fd..5bb629a6 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -33,7 +33,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import { CodeActionKind, CompletionList, Disposable, DocumentDropOrPasteEditKind, DocumentSymbol, InlineCompletionTriggerKind, InlineEditTriggerKind, InternalDataTransferItem, Location, NewSymbolNameTriggerKind, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; -import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; import { Cache } from './cache'; import * as extHostProtocol from './extHost.protocol'; @@ -501,9 +501,9 @@ class CodeActionAdapter { } else { if (codeActionContext.only) { if (!candidate.kind) { - this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`); + this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value}' requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`); } else if (!codeActionContext.only.contains(candidate.kind)) { - this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`); + this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value}' requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`); } } @@ -1287,6 +1287,10 @@ class InlineCompletionAdapterBase { return undefined; } + async provideInlineEdits(resource: URI, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise { + return undefined; + } + disposeCompletions(pid: number): void { } handleDidShowCompletionItem(pid: number, idx: number, updatedInsertText: string): void { } @@ -1392,6 +1396,82 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase { }; } + override async provideInlineEdits(resource: URI, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise { + if (!this._provider.provideInlineEdits) { + return undefined; + } + checkProposedApiEnabled(this._extension, 'inlineCompletionsAdditions'); + + const doc = this._documents.getDocument(resource); + const r = typeConvert.Range.to(range); + + const result = await this._provider.provideInlineEdits(doc, r, { + selectedCompletionInfo: + context.selectedSuggestionInfo + ? { + range: typeConvert.Range.to(context.selectedSuggestionInfo.range), + text: context.selectedSuggestionInfo.text + } + : undefined, + triggerKind: this.languageTriggerKindToVSCodeTriggerKind[context.triggerKind], + userPrompt: context.userPrompt, + }, token); + + if (!result) { + // undefined and null are valid results + return undefined; + } + + if (token.isCancellationRequested) { + // cancelled -> return without further ado, esp no caching + // of results as they will leak + return undefined; + } + + const normalizedResult = Array.isArray(result) ? result : result.items; + const commands = this._isAdditionsProposedApiEnabled ? Array.isArray(result) ? [] : result.commands || [] : []; + const enableForwardStability = this._isAdditionsProposedApiEnabled && !Array.isArray(result) ? result.enableForwardStability : undefined; + + let disposableStore: DisposableStore | undefined = undefined; + const pid = this._references.createReferenceId({ + dispose() { + disposableStore?.dispose(); + }, + items: normalizedResult + }); + + return { + pid, + items: normalizedResult.map((item, idx) => { + let command: languages.Command | undefined = undefined; + if (item.command) { + if (!disposableStore) { + disposableStore = new DisposableStore(); + } + command = this._commands.toInternal(item.command, disposableStore); + } + + const insertText = item.insertText; + return ({ + insertText: typeof insertText === 'string' ? insertText : { snippet: insertText.value }, + filterText: item.filterText, + range: item.range ? typeConvert.Range.from(item.range) : undefined, + command, + idx: idx, + completeBracketPairs: this._isAdditionsProposedApiEnabled ? item.completeBracketPairs : false, + }); + }), + commands: commands.map(c => { + if (!disposableStore) { + disposableStore = new DisposableStore(); + } + return this._commands.toInternal(c, disposableStore); + }), + suppressSuggestions: false, + enableForwardStability, + }; + } + override disposeCompletions(pid: number) { const data = this._references.disposeReferenceId(pid); data?.dispose(); @@ -2232,15 +2312,15 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF } $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token); + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token, resource.scheme === 'output'); } $resolveCodeLens(handle: number, symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined); + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined, true); } $releaseCodeLenses(handle: number, cacheId: number): void { - this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined); + this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined, true); } // --- declaration @@ -2581,6 +2661,10 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, InlineCompletionAdapterBase, adapter => adapter.provideInlineCompletions(URI.revive(resource), position, context, token), undefined, token); } + $provideInlineEdits(handle: number, resource: UriComponents, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise { + return this._withAdapter(handle, InlineCompletionAdapterBase, adapter => adapter.provideInlineEdits(URI.revive(resource), range, context, token), undefined, token); + } + $handleInlineCompletionDidShow(handle: number, pid: number, idx: number, updatedInsertText: string): void { this._withAdapter(handle, InlineCompletionAdapterBase, async adapter => { adapter.handleDidShowCompletionItem(pid, idx, updatedInsertText); @@ -2806,7 +2890,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, DocumentDropEditAdapter, adapter => adapter.resolveDropEdit(id, token), {}, undefined); } - $releaseDropEdits(handle: number, cacheId: number): void { + $releaseDocumentOnDropEdits(handle: number, cacheId: number): void { this._withAdapter(handle, DocumentDropEditAdapter, adapter => Promise.resolve(adapter.releaseDropEdits(cacheId)), undefined, undefined); } @@ -2814,7 +2898,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF registerMappedEditsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider): vscode.Disposable { const handle = this._addNewAdapter(new MappedEditsAdapter(this._documents, provider), extension); - this._proxy.$registerMappedEditsProvider(handle, this._transformDocumentSelector(selector, extension)); + this._proxy.$registerMappedEditsProvider(handle, this._transformDocumentSelector(selector, extension), extension.displayName ?? extension.name); return this._createDisposable(handle); } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/patched-vscode/src/vs/workbench/api/common/extHostLanguageModelTools.ts new file mode 100644 index 00000000..d792a7bb --- /dev/null +++ b/patched-vscode/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { raceCancellation } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationError } from 'vs/base/common/errors'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { revive } from 'vs/base/common/marshalling'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtHostLanguageModelToolsShape, IMainContext, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import { IToolInvocation, IToolResult } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; +import type * as vscode from 'vscode'; + +export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape { + /** A map of tools that were registered in this EH */ + private readonly _registeredTools = new Map(); + private readonly _proxy: MainThreadLanguageModelToolsShape; + private readonly _tokenCountFuncs = new Map Thenable>(); + + /** A map of all known tools, from other EHs or registered in vscode core */ + private readonly _allTools = new Map(); + + constructor(mainContext: IMainContext) { + this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageModelTools); + + this._proxy.$getTools().then(tools => { + for (const tool of tools) { + this._allTools.set(tool.id, revive(tool)); + } + }); + } + + async $countTokensForInvocation(callId: string, input: string, token: CancellationToken): Promise { + const fn = this._tokenCountFuncs.get(callId); + if (!fn) { + throw new Error(`Tool invocation call ${callId} not found`); + } + + return await fn(input, token); + } + + async invokeTool(toolId: string, options: vscode.LanguageModelToolInvocationOptions, token: CancellationToken): Promise { + const callId = generateUuid(); + if (options.tokenOptions) { + this._tokenCountFuncs.set(callId, options.tokenOptions.countTokens); + } + try { + // Making the round trip here because not all tools were necessarily registered in this EH + const result = await this._proxy.$invokeTool({ + toolId, + callId, + parameters: options.parameters, + tokenBudget: options.tokenOptions?.tokenBudget, + }, token); + return typeConvert.LanguageModelToolResult.to(result); + } finally { + this._tokenCountFuncs.delete(callId); + } + } + + $onDidChangeTools(tools: IToolDataDto[]): void { + this._allTools.clear(); + for (const tool of tools) { + this._allTools.set(tool.id, tool); + } + } + + get tools(): vscode.LanguageModelToolDescription[] { + return Array.from(this._allTools.values()) + .map(tool => typeConvert.LanguageModelToolDescription.to(tool)); + } + + async $invokeTool(dto: IToolInvocation, token: CancellationToken): Promise { + const item = this._registeredTools.get(dto.toolId); + if (!item) { + throw new Error(`Unknown tool ${dto.toolId}`); + } + + const options: vscode.LanguageModelToolInvocationOptions = { parameters: dto.parameters }; + if (dto.tokenBudget !== undefined) { + options.tokenOptions = { + tokenBudget: dto.tokenBudget, + countTokens: this._tokenCountFuncs.get(dto.callId) || ((value, token = CancellationToken.None) => + this._proxy.$countTokensForInvocation(dto.callId, value, token)) + }; + } + + const extensionResult = await raceCancellation(Promise.resolve(item.tool.invoke(options, token)), token); + if (!extensionResult) { + throw new CancellationError(); + } + + for (const key of Object.keys(extensionResult)) { + const value = extensionResult[key]; + if (value instanceof Promise) { + throw new Error(`Tool result for '${key}' cannot be a Promise`); + } + } + + return typeConvert.LanguageModelToolResult.from(extensionResult); + } + + registerTool(extension: IExtensionDescription, name: string, tool: vscode.LanguageModelTool): IDisposable { + this._registeredTools.set(name, { extension, tool }); + this._proxy.$registerTool(name); + + return toDisposable(() => { + this._registeredTools.delete(name); + this._proxy.$unregisterTool(name); + }); + } +} diff --git a/patched-vscode/src/vs/workbench/api/common/extHostLanguageModels.ts b/patched-vscode/src/vs/workbench/api/common/extHostLanguageModels.ts index fdc93731..dd0ccfc9 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AsyncIterableSource, Barrier } from 'vs/base/common/async'; +import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { CancellationError } from 'vs/base/common/errors'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { CancellationError, SerializedError, transformErrorForSerialization, transformErrorFromSerialization } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -19,7 +20,7 @@ import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentic import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; +import { IChatMessage, IChatResponseFragment, IChatResponsePart, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; @@ -36,13 +37,13 @@ type LanguageModelData = { class LanguageModelResponseStream { - readonly stream = new AsyncIterableSource(); + readonly stream = new AsyncIterableSource(); constructor( readonly option: number, - stream?: AsyncIterableSource + stream?: AsyncIterableSource ) { - this.stream = stream ?? new AsyncIterableSource(); + this.stream = stream ?? new AsyncIterableSource(); } } @@ -51,17 +52,26 @@ class LanguageModelResponse { readonly apiObject: vscode.LanguageModelChatResponse; private readonly _responseStreams = new Map(); - private readonly _defaultStream = new AsyncIterableSource(); + private readonly _defaultStream = new AsyncIterableSource(); private _isDone: boolean = false; - private _isStreaming: boolean = false; constructor() { const that = this; this.apiObject = { // result: promise, - text: that._defaultStream.asyncIterable, - // streams: AsyncIterable[] // FUTURE responses per N + get stream() { + return that._defaultStream.asyncIterable; + }, + get text() { + return AsyncIterableObject.map(that._defaultStream.asyncIterable, part => { + if (part instanceof extHostTypes.LanguageModelTextPart) { + return part.value; + } else { + return undefined; + } + }).coalesce(); + }, }; } @@ -79,7 +89,6 @@ class LanguageModelResponse { if (this._isDone) { return; } - this._isStreaming = true; let res = this._responseStreams.get(fragment.index); if (!res) { if (this._responseStreams.size === 0) { @@ -90,13 +99,17 @@ class LanguageModelResponse { } this._responseStreams.set(fragment.index, res); } - res.stream.emitOne(fragment.part); - } - get isStreaming(): boolean { - return this._isStreaming; + let out: vscode.LanguageModelChatResponseTextPart | vscode.LanguageModelChatResponseToolCallPart; + if (fragment.part.type === 'text') { + out = new extHostTypes.LanguageModelTextPart(fragment.part.value); + } else { + out = new extHostTypes.LanguageModelToolCallPart(fragment.part.name, fragment.part.toolCallId, fragment.part.parameters); + } + res.stream.emitOne(out); } + reject(err: Error): void { this._isDone = true; for (const stream of this._streams()) { @@ -176,28 +189,65 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { }); } - async $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { + async $startChatRequest(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: CancellationToken): Promise { const data = this._languageModels.get(handle); if (!data) { - return; + throw new Error('Provider not found'); } - const progress = new Progress(async fragment => { + const progress = new Progress(async fragment => { if (token.isCancellationRequested) { this._logService.warn(`[CHAT](${data.extension.value}) CANNOT send progress because the REQUEST IS CANCELLED`); return; } - this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); + + let part: IChatResponsePart | undefined; + if (fragment.part instanceof extHostTypes.LanguageModelToolCallPart) { + part = { type: 'tool_use', name: fragment.part.name, parameters: fragment.part.parameters, toolCallId: fragment.part.toolCallId }; + } else if (fragment.part instanceof extHostTypes.LanguageModelTextPart) { + part = { type: 'text', value: fragment.part.value }; + } + + if (!part) { + this._logService.warn(`[CHAT](${data.extension.value}) UNKNOWN part ${JSON.stringify(fragment)}`); + return; + } + + this._proxy.$reportResponsePart(requestId, { index: fragment.index, part }); }); - return data.provider.provideLanguageModelResponse( - messages.map(typeConvert.LanguageModelChatMessage.to), - options, - ExtensionIdentifier.toKey(from), - progress, - token - ); - } + let p: Promise; + + if (data.provider.provideLanguageModelResponse2) { + + p = Promise.resolve(data.provider.provideLanguageModelResponse2( + messages.map(typeConvert.LanguageModelChatMessage.to), + options, + ExtensionIdentifier.toKey(from), + progress, + token + )); + + } else { + + const progress2 = new Progress(async fragment => { + progress.report({ index: fragment.index, part: new extHostTypes.LanguageModelTextPart(fragment.part) }); + }); + + p = Promise.resolve(data.provider.provideLanguageModelResponse( + messages.map(typeConvert.LanguageModelChatMessage.to), + options?.modelOptions ?? {}, + ExtensionIdentifier.toKey(from), + progress2, + token + )); + } + p.then(() => { + this._proxy.$reportResponseDone(requestId, undefined); + }, err => { + this._proxy.$reportResponseDone(requestId, transformErrorForSerialization(err)); + }); + } //#region --- token counting @@ -311,45 +361,33 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } } - const requestId = (Math.random() * 1e6) | 0; - const requestPromise = this._proxy.$fetchResponse(from, languageModelId, requestId, internalMessages, options.modelOptions ?? {}, token); - - const barrier = new Barrier(); - - const res = new LanguageModelResponse(); - this._pendingRequest.set(requestId, { languageModelId, res }); + try { + const requestId = (Math.random() * 1e6) | 0; + const res = new LanguageModelResponse(); + this._pendingRequest.set(requestId, { languageModelId, res }); - let error: Error | undefined; + try { + await this._proxy.$tryStartChatRequest(from, languageModelId, requestId, internalMessages, options, token); - requestPromise.catch(err => { - if (barrier.isOpen()) { - // we received an error while streaming. this means we need to reject the "stream" - // because we have already returned the request object - res.reject(err); - } else { - error = err; + } catch (error) { + // error'ing here means that the request could NOT be started/made, e.g. wrong model, no access, etc, but + // later the response can fail as well. Those failures are communicated via the stream-object + this._pendingRequest.delete(requestId); + throw error; } - }).finally(() => { - this._pendingRequest.delete(requestId); - res.resolve(); - barrier.open(); - }); - await barrier.wait(); + return res.apiObject; - if (error) { + } catch (error) { if (error.name === extHostTypes.LanguageModelError.name) { throw error; } - throw new extHostTypes.LanguageModelError( - `Language model '${languageModelId}' errored, check cause for more details`, + `Language model '${languageModelId}' errored: ${toErrorMessage(error)}`, 'Unknown', error ); } - - return res.apiObject; } private _convertMessages(extension: IExtensionDescription, messages: vscode.LanguageModelChatMessage[]) { @@ -358,18 +396,36 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { if (message.role as number === extHostTypes.LanguageModelChatMessageRole.System) { checkProposedApiEnabled(extension, 'languageModelSystem'); } + if (message.content2.some(part => part instanceof extHostTypes.LanguageModelToolResultPart)) { + checkProposedApiEnabled(extension, 'lmTools'); + } internalMessages.push(typeConvert.LanguageModelChatMessage.from(message)); } return internalMessages; } - async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise { - const data = this._pendingRequest.get(requestId);//.report(chunk); + async $acceptResponsePart(requestId: number, chunk: IChatResponseFragment): Promise { + const data = this._pendingRequest.get(requestId); if (data) { data.res.handleFragment(chunk); } } + async $acceptResponseDone(requestId: number, error: SerializedError | undefined): Promise { + const data = this._pendingRequest.get(requestId); + if (!data) { + return; + } + this._pendingRequest.delete(requestId); + if (error) { + // we error the stream because that's the only way to signal + // that the request has failed + data.res.reject(transformErrorFromSerialization(error)); + } else { + data.res.resolve(); + } + } + // BIG HACK: Using AuthenticationProviders to check access to Language Models private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, justification: string | undefined, silent: boolean | undefined): Promise { // This needs to be done in both MainThread & ExtHost ChatProvider diff --git a/patched-vscode/src/vs/workbench/api/common/extHostNotebook.ts b/patched-vscode/src/vs/workbench/api/common/extHostNotebook.ts index 3a7e105c..a8c5d9f6 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostNotebook.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostNotebook.ts @@ -36,7 +36,7 @@ import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; import { CellSearchModel } from 'vs/workbench/contrib/search/common/cellSearchModel'; import { INotebookCellMatchNoModel, INotebookFileMatchNoModel, IRawClosedNotebookFileMatch, genericCellMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; import { NotebookPriorityInfo } from 'vs/workbench/contrib/search/common/search'; -import { globMatchesResource } from 'vs/workbench/services/editor/common/editorResolverService'; +import { globMatchesResource, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { ILogService } from 'vs/platform/log/common/log'; export class ExtHostNotebookController implements ExtHostNotebookShape { @@ -145,8 +145,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { return result; } - - private static _convertNotebookRegistrationData(extension: IExtensionDescription, registration: vscode.NotebookRegistrationData | undefined): INotebookContributionData | undefined { if (!registration) { return; @@ -163,7 +161,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { providerDisplayName: extension.displayName || extension.name, displayName: registration.displayName, filenamePattern: viewOptionsFilenamePattern, - exclusive: registration.exclusive || false + priority: registration.exclusive ? RegisteredEditorPriority.exclusive : undefined }; } @@ -205,28 +203,25 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { return assertIsDefined(document?.apiNotebook); } - - async showNotebookDocument(notebookOrUri: vscode.NotebookDocument | URI, options?: vscode.NotebookDocumentShowOptions): Promise { - - if (URI.isUri(notebookOrUri)) { - notebookOrUri = await this.openNotebookDocument(notebookOrUri); - } - + async showNotebookDocument(notebook: vscode.NotebookDocument, options?: vscode.NotebookDocumentShowOptions): Promise { let resolvedOptions: INotebookDocumentShowOptions; if (typeof options === 'object') { resolvedOptions = { position: typeConverters.ViewColumn.from(options.viewColumn), preserveFocus: options.preserveFocus, selections: options.selections && options.selections.map(typeConverters.NotebookRange.from), - pinned: typeof options.preview === 'boolean' ? !options.preview : undefined + pinned: typeof options.preview === 'boolean' ? !options.preview : undefined, + label: options?.label }; } else { resolvedOptions = { - preserveFocus: false + preserveFocus: false, + pinned: true }; } - const editorId = await this._notebookEditorsProxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.notebookType, resolvedOptions); + const viewType = options?.asRepl ? 'repl' : notebook.notebookType; + const editorId = await this._notebookEditorsProxy.$tryShowNotebookDocument(notebook.uri, viewType, resolvedOptions); const editor = editorId && this._editors.get(editorId)?.apiEditor; if (editor) { @@ -234,9 +229,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } if (editorId) { - throw new Error(`Could NOT open editor for "${notebookOrUri.uri.toString()}" because another editor opened in the meantime.`); + throw new Error(`Could NOT open editor for "${notebook.uri.toString()}" because another editor opened in the meantime.`); } else { - throw new Error(`Could NOT open editor for "${notebookOrUri.uri.toString()}".`); + throw new Error(`Could NOT open editor for "${notebook.uri.toString()}".`); } } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostNotebookDocument.ts b/patched-vscode/src/vs/workbench/api/common/extHostNotebookDocument.ts index 8f74a0a4..382fd39c 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -211,6 +211,9 @@ export class ExtHostNotebookDocument { }, save() { return that._save(); + }, + [Symbol.for('debug.description')]() { + return `NotebookDocument(${this.uri.toString()})`; } }; this._notebook = Object.freeze(apiObject); diff --git a/patched-vscode/src/vs/workbench/api/common/extHostNotebookEditor.ts b/patched-vscode/src/vs/workbench/api/common/extHostNotebookEditor.ts index 8472fc10..4aec54c2 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostNotebookEditor.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostNotebookEditor.ts @@ -71,6 +71,9 @@ export class ExtHostNotebookEditor { get viewColumn() { return that._viewColumn; }, + [Symbol.for('debug.description')]() { + return `NotebookEditor(${this.notebook.uri.toString()})`; + } }; ExtHostNotebookEditor.apiEditorsToExtHost.set(this._editor, this); diff --git a/patched-vscode/src/vs/workbench/api/common/extHostQuickOpen.ts b/patched-vscode/src/vs/workbench/api/common/extHostQuickOpen.ts index b850be83..3a9075f9 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -13,7 +13,7 @@ import { ExtHostQuickOpenShape, IMainContext, MainContext, TransferQuickInput, T import { URI } from 'vs/base/common/uri'; import { ThemeIcon, QuickInputButtons, QuickPickItemKind, InputBoxValidationSeverity } from 'vs/workbench/api/common/extHostTypes'; import { isCancellationError } from 'vs/base/common/errors'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { coalesce } from 'vs/base/common/arrays'; import Severity from 'vs/base/common/severity'; import { ThemeIcon as ThemeIconUtils } from 'vs/base/common/themables'; @@ -301,7 +301,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._onDidChangeValueEmitter ]; - constructor(protected _extensionId: ExtensionIdentifier, private _onDidDispose: () => void) { + constructor(protected _extension: IExtensionDescription, private _onDidDispose: () => void) { } get title() { @@ -385,6 +385,10 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx } set buttons(buttons: QuickInputButton[]) { + const allowedButtonLocation = isProposedApiEnabled(this._extension, 'quickInputButtonLocation'); + if (!allowedButtonLocation && buttons.some(button => button.location)) { + console.warn(`Extension '${this._extension.identifier.value}' uses a button location which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`); + } this._buttons = buttons.slice(); this._handlesToButtons.clear(); buttons.forEach((button, i) => { @@ -397,6 +401,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx ...getIconPathOrClass(button.iconPath), tooltip: button.tooltip, handle: button === QuickInputButtons.Back ? -1 : i, + location: allowedButtonLocation ? button.location : undefined }; }) }); @@ -546,8 +551,8 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx private readonly _onDidChangeSelectionEmitter = new Emitter(); private readonly _onDidTriggerItemButtonEmitter = new Emitter>(); - constructor(private extension: IExtensionDescription, onDispose: () => void) { - super(extension.identifier, onDispose); + constructor(extension: IExtensionDescription, onDispose: () => void) { + super(extension, onDispose); this._disposables.push( this._onDidChangeActiveEmitter, this._onDidChangeSelectionEmitter, @@ -569,7 +574,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this._itemsToHandles.set(item, i); }); - const allowedTooltips = isProposedApiEnabled(this.extension, 'quickPickItemTooltip'); + const allowedTooltips = isProposedApiEnabled(this._extension, 'quickPickItemTooltip'); const pickItems: TransferQuickPickItemOrSeparator[] = []; for (let handle = 0; handle < items.length; handle++) { @@ -578,7 +583,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx pickItems.push({ type: 'separator', label: item.label }); } else { if (item.tooltip && !allowedTooltips) { - console.warn(`Extension '${this.extension.identifier.value}' uses a tooltip which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this.extension.identifier.value}`); + console.warn(`Extension '${this._extension.identifier.value}' uses a tooltip which is proposed API that is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`); } const icon = (item.iconPath) ? getIconPathOrClass(item.iconPath) : undefined; @@ -712,7 +717,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx private _validationMessage: string | InputBoxValidationMessage | undefined; constructor(extension: IExtensionDescription, onDispose: () => void) { - super(extension.identifier, onDispose); + super(extension, onDispose); this.update({ type: 'inputBox' }); } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostSCM.ts b/patched-vscode/src/vs/workbench/api/common/extHostSCM.ts index c63a3afd..b07e8f49 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostSCM.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostSCM.ts @@ -9,7 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { debounce } from 'vs/base/common/decorators'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { asPromise, Sequencer } from 'vs/base/common/async'; +import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto } from './extHost.protocol'; import { sortedDiff, equals } from 'vs/base/common/arrays'; @@ -58,19 +58,26 @@ function getIconResource(decorations?: vscode.SourceControlResourceThemableDecor } } -function getHistoryItemIconDto(historyItem: vscode.SourceControlHistoryItem): UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined { - if (!historyItem.icon) { +function getHistoryItemIconDto(icon: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined): UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined { + if (!icon) { return undefined; - } else if (URI.isUri(historyItem.icon)) { - return historyItem.icon; - } else if (ThemeIcon.isThemeIcon(historyItem.icon)) { - return historyItem.icon; + } else if (URI.isUri(icon)) { + return icon; + } else if (ThemeIcon.isThemeIcon(icon)) { + return icon; } else { - const icon = historyItem.icon as { light: URI; dark: URI }; - return { light: icon.light, dark: icon.dark }; + const iconDto = icon as { light: URI; dark: URI }; + return { light: iconDto.light, dark: iconDto.dark }; } } +function toSCMHistoryItemDto(historyItem: vscode.SourceControlHistoryItem): SCMHistoryItemDto { + const icon = getHistoryItemIconDto(historyItem.icon); + const labels = historyItem.labels?.map(l => ({ title: l.title, icon: getHistoryItemIconDto(l.icon) })); + + return { ...historyItem, icon, labels }; +} + function compareResourceThemableDecorations(a: vscode.SourceControlResourceThemableDecorations, b: vscode.SourceControlResourceThemableDecorations): number { if (!a.iconPath && !b.iconPath) { return 0; @@ -259,7 +266,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { set value(value: string) { value = value ?? ''; - this._sequencer.queue(async () => this.#proxy.$setInputBoxValue(this._sourceControlHandle, value)); + this.#proxy.$setInputBoxValue(this._sourceControlHandle, value); this.updateValue(value); } @@ -276,7 +283,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { } set placeholder(placeholder: string) { - this._sequencer.queue(async () => this.#proxy.$setInputBoxPlaceholder(this._sourceControlHandle, placeholder)); + this.#proxy.$setInputBoxPlaceholder(this._sourceControlHandle, placeholder); this._placeholder = placeholder; } @@ -296,7 +303,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { } this._validateInput = fn; - this._sequencer.queue(async () => this.#proxy.$setValidationProviderIsEnabled(this._sourceControlHandle, !!fn)); + this.#proxy.$setValidationProviderIsEnabled(this._sourceControlHandle, !!fn); } private _enabled: boolean = true; @@ -313,7 +320,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { } this._enabled = enabled; - this._sequencer.queue(async () => this.#proxy.$setInputBoxEnablement(this._sourceControlHandle, enabled)); + this.#proxy.$setInputBoxEnablement(this._sourceControlHandle, enabled); } private _visible: boolean = true; @@ -330,7 +337,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { } this._visible = visible; - this._sequencer.queue(async () => this.#proxy.$setInputBoxVisibility(this._sourceControlHandle, visible)); + this.#proxy.$setInputBoxVisibility(this._sourceControlHandle, visible); } get document(): vscode.TextDocument { @@ -339,7 +346,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { return this.#extHostDocuments.getDocument(this._documentUri); } - constructor(private _extension: IExtensionDescription, _extHostDocuments: ExtHostDocuments, proxy: MainThreadSCMShape, private _sequencer: Sequencer, private _sourceControlHandle: number, private _documentUri: URI) { + constructor(private _extension: IExtensionDescription, _extHostDocuments: ExtHostDocuments, proxy: MainThreadSCMShape, private _sourceControlHandle: number, private _documentUri: URI) { this.#extHostDocuments = _extHostDocuments; this.#proxy = proxy; } @@ -347,7 +354,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { showValidationMessage(message: string | vscode.MarkdownString, type: vscode.SourceControlInputBoxValidationType) { checkProposedApiEnabled(this._extension, 'scmValidation'); - this._sequencer.queue(async () => this.#proxy.$showValidationMessage(this._sourceControlHandle, message, type as any)); + this.#proxy.$showValidationMessage(this._sourceControlHandle, message, type as any); } $onInputBoxValueChange(value: string): void { @@ -386,14 +393,14 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG get label(): string { return this._label; } set label(label: string) { this._label = label; - this._sequencer.queue(async () => this._proxy.$updateGroupLabel(this._sourceControlHandle, this.handle, label)); + this._proxy.$updateGroupLabel(this._sourceControlHandle, this.handle, label); } private _hideWhenEmpty: boolean | undefined = undefined; get hideWhenEmpty(): boolean | undefined { return this._hideWhenEmpty; } set hideWhenEmpty(hideWhenEmpty: boolean | undefined) { this._hideWhenEmpty = hideWhenEmpty; - this._sequencer.queue(async () => this._proxy.$updateGroup(this._sourceControlHandle, this.handle, this.features)); + this._proxy.$updateGroup(this._sourceControlHandle, this.handle, this.features); } get features(): SCMGroupFeatures { @@ -413,7 +420,6 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG constructor( private _proxy: MainThreadSCMShape, private _commands: ExtHostCommands, - private _sequencer: Sequencer, private _sourceControlHandle: number, private _id: string, private _label: string, @@ -512,7 +518,6 @@ class ExtHostSourceControl implements vscode.SourceControl { #proxy: MainThreadSCMShape; - private readonly _sequencer = new Sequencer(); private _groups: Map = new Map(); get id(): string { @@ -542,7 +547,7 @@ class ExtHostSourceControl implements vscode.SourceControl { } this._count = count; - this._sequencer.queue(async () => this.#proxy.$updateSourceControl(this.handle, { count })); + this.#proxy.$updateSourceControl(this.handle, { count }); } private _quickDiffProvider: vscode.QuickDiffProvider | undefined = undefined; @@ -557,7 +562,7 @@ class ExtHostSourceControl implements vscode.SourceControl { if (isProposedApiEnabled(this._extension, 'quickDiffProvider')) { quickDiffLabel = quickDiffProvider?.label; } - this._sequencer.queue(async () => this.#proxy.$updateSourceControl(this.handle, { hasQuickDiffProvider: !!quickDiffProvider, quickDiffLabel })); + this.#proxy.$updateSourceControl(this.handle, { hasQuickDiffProvider: !!quickDiffProvider, quickDiffLabel }); } private _historyProvider: vscode.SourceControlHistoryProvider | undefined; @@ -575,12 +580,12 @@ class ExtHostSourceControl implements vscode.SourceControl { this._historyProvider = historyProvider; this._historyProviderDisposable.value = new DisposableStore(); - this._sequencer.queue(async () => this.#proxy.$updateSourceControl(this.handle, { hasHistoryProvider: !!historyProvider })); + this.#proxy.$updateSourceControl(this.handle, { hasHistoryProvider: !!historyProvider }); if (historyProvider) { this._historyProviderDisposable.value.add(historyProvider.onDidChangeCurrentHistoryItemGroup(() => { this._historyProviderCurrentHistoryItemGroup = historyProvider?.currentHistoryItemGroup; - this._sequencer.queue(async () => this.#proxy.$onDidChangeHistoryProviderCurrentHistoryItemGroup(this.handle, this._historyProviderCurrentHistoryItemGroup)); + this.#proxy.$onDidChangeHistoryProviderCurrentHistoryItemGroup(this.handle, this._historyProviderCurrentHistoryItemGroup); })); } } @@ -597,7 +602,7 @@ class ExtHostSourceControl implements vscode.SourceControl { } this._commitTemplate = commitTemplate; - this._sequencer.queue(async () => this.#proxy.$updateSourceControl(this.handle, { commitTemplate })); + this.#proxy.$updateSourceControl(this.handle, { commitTemplate }); } private readonly _acceptInputDisposables = new MutableDisposable(); @@ -613,7 +618,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._acceptInputCommand = acceptInputCommand; const internal = this._commands.converter.toInternal(acceptInputCommand, this._acceptInputDisposables.value); - this._sequencer.queue(async () => this.#proxy.$updateSourceControl(this.handle, { acceptInputCommand: internal })); + this.#proxy.$updateSourceControl(this.handle, { acceptInputCommand: internal }); } private readonly _actionButtonDisposables = new MutableDisposable(); @@ -637,7 +642,7 @@ class ExtHostSourceControl implements vscode.SourceControl { description: actionButton.description, enabled: actionButton.enabled } : undefined; - this._sequencer.queue(async () => this.#proxy.$updateSourceControl(this.handle, { actionButton: internal ?? null })); + this.#proxy.$updateSourceControl(this.handle, { actionButton: internal ?? null }); } @@ -658,7 +663,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._statusBarCommands = statusBarCommands; const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c, this._statusBarDisposables.value!)) as ICommandDto[]; - this._sequencer.queue(async () => this.#proxy.$updateSourceControl(this.handle, { statusBarCommands: internal })); + this.#proxy.$updateSourceControl(this.handle, { statusBarCommands: internal }); } private _selected: boolean = false; @@ -689,8 +694,8 @@ class ExtHostSourceControl implements vscode.SourceControl { query: _rootUri ? `rootUri=${encodeURIComponent(_rootUri.toString())}` : undefined }); - this._sequencer.queue(() => this.#proxy.$registerSourceControl(this.handle, _id, _label, _rootUri, inputBoxDocumentUri)); - this._inputBox = new ExtHostSCMInputBox(_extension, _extHostDocuments, this.#proxy, this._sequencer, this.handle, inputBoxDocumentUri); + this._inputBox = new ExtHostSCMInputBox(_extension, _extHostDocuments, this.#proxy, this.handle, inputBoxDocumentUri); + this.#proxy.$registerSourceControl(this.handle, _id, _label, _rootUri, inputBoxDocumentUri); } private createdResourceGroups = new Map(); @@ -698,7 +703,7 @@ class ExtHostSourceControl implements vscode.SourceControl { createResourceGroup(id: string, label: string, options?: { multiDiffEditorEnableViewChanges?: boolean }): ExtHostSourceControlResourceGroup { const multiDiffEditorEnableViewChanges = isProposedApiEnabled(this._extension, 'scmMultiDiffEditor') && options?.multiDiffEditorEnableViewChanges === true; - const group = new ExtHostSourceControlResourceGroup(this.#proxy, this._commands, this._sequencer, this.handle, id, label, multiDiffEditorEnableViewChanges, this._extension); + const group = new ExtHostSourceControlResourceGroup(this.#proxy, this._commands, this.handle, id, label, multiDiffEditorEnableViewChanges, this._extension); const disposable = Event.once(group.onDidDispose)(() => this.createdResourceGroups.delete(group)); this.createdResourceGroups.set(group, disposable); this.eventuallyAddResourceGroups(); @@ -722,7 +727,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this.updatedResourceGroups.delete(group); updateListener.dispose(); this._groups.delete(group.handle); - this._sequencer.queue(async () => this.#proxy.$unregisterGroup(this.handle, group.handle)); + this.#proxy.$unregisterGroup(this.handle, group.handle); }); groups.push([group.handle, group.id, group.label, group.features, group.multiDiffEditorEnableViewChanges]); @@ -736,7 +741,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._groups.set(group.handle, group); } - this._sequencer.queue(async () => this.#proxy.$registerGroups(this.handle, groups, splices)); + this.#proxy.$registerGroups(this.handle, groups, splices); this.createdResourceGroups.clear(); } @@ -755,7 +760,7 @@ class ExtHostSourceControl implements vscode.SourceControl { }); if (splices.length > 0) { - this._sequencer.queue(async () => this.#proxy.$spliceResourceStates(this.handle, splices)); + this.#proxy.$spliceResourceStates(this.handle, splices); } this.updatedResourceGroups.clear(); @@ -776,7 +781,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._statusBarDisposables.dispose(); this._groups.forEach(group => group.dispose()); - this._sequencer.queue(async () => this.#proxy.$unregisterSourceControl(this.handle)); + this.#proxy.$unregisterSourceControl(this.handle); } } @@ -967,11 +972,23 @@ export class ExtHostSCM implements ExtHostSCMShape { return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2, token) ?? undefined; } + async $resolveHistoryItemGroupCommonAncestor2(sourceControlHandle: number, historyItemGroupIds: string[], token: CancellationToken): Promise { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + return await historyProvider?.resolveHistoryItemGroupCommonAncestor2(historyItemGroupIds, token) ?? undefined; + } + async $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; const historyItems = await historyProvider?.provideHistoryItems(historyItemGroupId, options, token); - return historyItems?.map(item => ({ ...item, icon: getHistoryItemIconDto(item) })) ?? undefined; + return historyItems?.map(item => toSCMHistoryItemDto(item)) ?? undefined; + } + + async $provideHistoryItems2(sourceControlHandle: number, options: any, token: CancellationToken): Promise { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + const historyItems = await historyProvider?.provideHistoryItems2(options, token); + + return historyItems?.map(item => toSCMHistoryItemDto(item)) ?? undefined; } async $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise { @@ -981,7 +998,7 @@ export class ExtHostSCM implements ExtHostSCMShape { } const historyItem = await historyProvider.provideHistoryItemSummary(historyItemId, historyItemParentId, token); - return historyItem ? { ...historyItem, icon: getHistoryItemIconDto(historyItem) } : undefined; + return historyItem ? toSCMHistoryItemDto(historyItem) : undefined; } async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise { diff --git a/patched-vscode/src/vs/workbench/api/common/extHostSearch.ts b/patched-vscode/src/vs/workbench/api/common/extHostSearch.ts index c2e0b93f..bf7f4049 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostSearch.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostSearch.ts @@ -15,28 +15,33 @@ import { IRawFileQuery, ISearchCompleteStats, IFileQuery, IRawTextQuery, IRawQue import { URI, UriComponents } from 'vs/base/common/uri'; import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { revive } from 'vs/base/common/marshalling'; +import { OldAITextSearchProviderConverter, OldFileSearchProviderConverter, OldTextSearchProviderConverter } from 'vs/workbench/services/search/common/searchExtConversionTypes'; export interface IExtHostSearch extends ExtHostSearchShape { - registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable; - registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; - registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable; + registerTextSearchProviderOld(scheme: string, provider: vscode.TextSearchProvider): IDisposable; + registerAITextSearchProviderOld(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; + registerFileSearchProviderOld(scheme: string, provider: vscode.FileSearchProvider): IDisposable; + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProviderNew): IDisposable; + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProviderNew): IDisposable; + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProviderNew): IDisposable; doInternalFileSearchWithCustomCallback(query: IFileQuery, token: CancellationToken, handleFileMatch: (data: URI[]) => void): Promise; } export const IExtHostSearch = createDecorator('IExtHostSearch'); -export class ExtHostSearch implements ExtHostSearchShape { +export class ExtHostSearch implements IExtHostSearch { protected readonly _proxy: MainThreadSearchShape = this.extHostRpc.getProxy(MainContext.MainThreadSearch); protected _handlePool: number = 0; - private readonly _textSearchProvider = new Map(); + private readonly _textSearchProvider = new Map(); private readonly _textSearchUsedSchemes = new Set(); - private readonly _aiTextSearchProvider = new Map(); + private readonly _aiTextSearchProvider = new Map(); private readonly _aiTextSearchUsedSchemes = new Set(); - private readonly _fileSearchProvider = new Map(); + private readonly _fileSearchProvider = new Map(); private readonly _fileSearchUsedSchemes = new Set(); private readonly _fileSearchManager = new FileSearchManager(); @@ -44,14 +49,30 @@ export class ExtHostSearch implements ExtHostSearchShape { constructor( @IExtHostRpcService private extHostRpc: IExtHostRpcService, @IURITransformerService protected _uriTransformer: IURITransformerService, - @ILogService protected _logService: ILogService + @ILogService protected _logService: ILogService, ) { } protected _transformScheme(scheme: string): string { return this._uriTransformer.transformOutgoingScheme(scheme); } - registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable { + registerTextSearchProviderOld(scheme: string, provider: vscode.TextSearchProvider): IDisposable { + if (this._textSearchUsedSchemes.has(scheme)) { + throw new Error(`a text search provider for the scheme '${scheme}' is already registered`); + } + + this._textSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._textSearchProvider.set(handle, new OldTextSearchProviderConverter(provider)); + this._proxy.$registerTextSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._textSearchUsedSchemes.delete(scheme); + this._textSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProviderNew): IDisposable { if (this._textSearchUsedSchemes.has(scheme)) { throw new Error(`a text search provider for the scheme '${scheme}' is already registered`); } @@ -67,7 +88,23 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } - registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable { + registerAITextSearchProviderOld(scheme: string, provider: vscode.AITextSearchProvider): IDisposable { + if (this._aiTextSearchUsedSchemes.has(scheme)) { + throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); + } + + this._aiTextSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._aiTextSearchProvider.set(handle, new OldAITextSearchProviderConverter(provider)); + this._proxy.$registerAITextSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._aiTextSearchUsedSchemes.delete(scheme); + this._aiTextSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProviderNew): IDisposable { if (this._aiTextSearchUsedSchemes.has(scheme)) { throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); } @@ -83,7 +120,23 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } - registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable { + registerFileSearchProviderOld(scheme: string, provider: vscode.FileSearchProvider): IDisposable { + if (this._fileSearchUsedSchemes.has(scheme)) { + throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); + } + + this._fileSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._fileSearchProvider.set(handle, new OldFileSearchProviderConverter(provider)); + this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._fileSearchUsedSchemes.delete(scheme); + this._fileSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProviderNew): IDisposable { if (this._fileSearchUsedSchemes.has(scheme)) { throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); } @@ -107,7 +160,7 @@ export class ExtHostSearch implements ExtHostSearchShape { this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); }, token); } else { - throw new Error('3 unknown provider: ' + handle); + throw new Error('unknown provider: ' + handle); } } @@ -124,7 +177,7 @@ export class ExtHostSearch implements ExtHostSearchShape { $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: vscode.CancellationToken): Promise { const provider = this._textSearchProvider.get(handle); if (!provider || !provider.provideTextSearchResults) { - throw new Error(`2 Unknown provider ${handle}`); + throw new Error(`Unknown Text Search Provider ${handle}`); } const query = reviveQuery(rawQuery); @@ -135,7 +188,7 @@ export class ExtHostSearch implements ExtHostSearchShape { $provideAITextSearchResults(handle: number, session: number, rawQuery: IRawAITextQuery, token: vscode.CancellationToken): Promise { const provider = this._aiTextSearchProvider.get(handle); if (!provider || !provider.provideAITextSearchResults) { - throw new Error(`1 Unknown provider ${handle}`); + throw new Error(`Unknown AI Text Search Provider ${handle}`); } const query = reviveQuery(rawQuery); @@ -145,14 +198,14 @@ export class ExtHostSearch implements ExtHostSearchShape { $enableExtensionHostSearch(): void { } - protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { return new TextSearchManager({ query, provider }, { readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding }, 'textSearchProvider'); } - protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProvider): TextSearchManager { + protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProviderNew): TextSearchManager { return new TextSearchManager({ query, provider }, { readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding @@ -171,8 +224,6 @@ export function reviveQuery(rawQuery: U): U extends IRawTex } function reviveFolderQuery(rawFolderQuery: IFolderQuery): IFolderQuery { - return { - ...rawFolderQuery, - folder: URI.revive(rawFolderQuery.folder) - }; + return revive(rawFolderQuery); } + diff --git a/patched-vscode/src/vs/workbench/api/common/extHostSecrets.ts b/patched-vscode/src/vs/workbench/api/common/extHostSecrets.ts index d1af02ed..13fb3293 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostSecrets.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostSecrets.ts @@ -9,26 +9,30 @@ import type * as vscode from 'vscode'; import { ExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export class ExtensionSecrets implements vscode.SecretStorage { protected readonly _id: string; readonly #secretState: ExtHostSecretState; - private _onDidChange = new Emitter(); - readonly onDidChange: Event = this._onDidChange.event; - + readonly onDidChange: Event; + readonly disposables = new DisposableStore(); constructor(extensionDescription: IExtensionDescription, secretState: ExtHostSecretState) { this._id = ExtensionIdentifier.toKey(extensionDescription.identifier); this.#secretState = secretState; - this.#secretState.onDidChangePassword(e => { - if (e.extensionId === this._id) { - this._onDidChange.fire({ key: e.key }); - } - }); + this.onDidChange = Event.map( + Event.filter(this.#secretState.onDidChangePassword, e => e.extensionId === this._id), + e => ({ key: e.key }), + this.disposables + ); + } + + dispose() { + this.disposables.dispose(); } get(key: string): Promise { diff --git a/patched-vscode/src/vs/workbench/api/common/extHostTask.ts b/patched-vscode/src/vs/workbench/api/common/extHostTask.ts index b5829c09..99968d5d 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostTask.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostTask.ts @@ -28,6 +28,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/tasks'; import { ErrorNoTelemetry, NotSupportedError } from 'vs/base/common/errors'; +import { asArray } from 'vs/base/common/arrays'; export interface IExtHostTask extends ExtHostTaskShape { @@ -279,7 +280,7 @@ export namespace TaskDTO { isBackground: value.isBackground, group: TaskGroupDTO.from(value.group as vscode.TaskGroup), presentationOptions: TaskPresentationOptionsDTO.from(value.presentationOptions), - problemMatchers: value.problemMatchers, + problemMatchers: asArray(value.problemMatchers), hasDefinedMatchers: (value as types.Task).hasDefinedMatchers, runOptions: value.runOptions ? value.runOptions : { reevaluateOnRerun: true }, detail: value.detail diff --git a/patched-vscode/src/vs/workbench/api/common/extHostTerminalService.ts b/patched-vscode/src/vs/workbench/api/common/extHostTerminalService.ts index b870c3ab..9ce31e71 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostTerminalService.ts @@ -921,7 +921,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public getEnvironmentVariableCollection(extension: IExtensionDescription): IEnvironmentVariableCollection { let collection = this._environmentVariableCollections.get(extension.identifier.value); if (!collection) { - collection = new UnifiedEnvironmentVariableCollection(); + collection = this._register(new UnifiedEnvironmentVariableCollection()); this._setEnvironmentVariableCollection(extension.identifier.value, collection); } return collection.getScopedEnvironmentVariableCollection(undefined); @@ -936,7 +936,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { collections.forEach(entry => { const extensionIdentifier = entry[0]; - const collection = new UnifiedEnvironmentVariableCollection(entry[1]); + const collection = this._register(new UnifiedEnvironmentVariableCollection(entry[1])); this._setEnvironmentVariableCollection(extensionIdentifier, collection); }); } @@ -952,20 +952,20 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I private _setEnvironmentVariableCollection(extensionIdentifier: string, collection: UnifiedEnvironmentVariableCollection): void { this._environmentVariableCollections.set(extensionIdentifier, collection); - collection.onDidChangeCollection(() => { + this._register(collection.onDidChangeCollection(() => { // When any collection value changes send this immediately, this is done to ensure // following calls to createTerminal will be created with the new environment. It will // result in more noise by sending multiple updates when called but collections are // expected to be small. this._syncEnvironmentVariableCollection(extensionIdentifier, collection); - }); + })); } } /** * Unified environment variable collection carrying information for all scopes, for a specific extension. */ -class UnifiedEnvironmentVariableCollection { +class UnifiedEnvironmentVariableCollection extends Disposable { readonly map: Map = new Map(); private readonly scopedCollections: Map = new Map(); readonly descriptionMap: Map = new Map(); @@ -983,6 +983,7 @@ class UnifiedEnvironmentVariableCollection { constructor( serialized?: ISerializableEnvironmentVariableCollection ) { + super(); this.map = new Map(serialized); } @@ -992,7 +993,7 @@ class UnifiedEnvironmentVariableCollection { if (!scopedCollection) { scopedCollection = new ScopedEnvironmentVariableCollection(this, scope); this.scopedCollections.set(scopedCollectionKey, scopedCollection); - scopedCollection.onDidChangeCollection(() => this._onDidChangeCollection.fire()); + this._register(scopedCollection.onDidChangeCollection(() => this._onDidChangeCollection.fire())); } return scopedCollection; } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts b/patched-vscode/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts index 59f391f8..06417a27 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts @@ -11,7 +11,7 @@ import { MainContext, type ExtHostTerminalShellIntegrationShape, type MainThread import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { Emitter, type Event } from 'vs/base/common/event'; -import { isUriComponents, URI } from 'vs/base/common/uri'; +import { URI, type UriComponents } from 'vs/base/common/uri'; import { AsyncIterableObject, Barrier, type AsyncIterableEmitter } from 'vs/base/common/async'; export interface IExtHostTerminalShellIntegration extends ExtHostTerminalShellIntegrationShape { @@ -104,7 +104,7 @@ export class ExtHostTerminalShellIntegration extends Disposable implements IExtH }); } - public $shellExecutionStart(instanceId: number, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, cwd: URI | undefined): void { + public $shellExecutionStart(instanceId: number, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, cwd: UriComponents | undefined): void { // Force shellIntegration creation if it hasn't been created yet, this could when events // don't come through on startup if (!this._activeShellIntegrations.has(instanceId)) { @@ -115,7 +115,7 @@ export class ExtHostTerminalShellIntegration extends Disposable implements IExtH confidence: commandLineConfidence, isTrusted }; - this._activeShellIntegrations.get(instanceId)?.startShellExecution(commandLine, cwd); + this._activeShellIntegrations.get(instanceId)?.startShellExecution(commandLine, URI.revive(cwd)); } public $shellExecutionEnd(instanceId: number, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, exitCode: number | undefined): void { @@ -131,8 +131,8 @@ export class ExtHostTerminalShellIntegration extends Disposable implements IExtH this._activeShellIntegrations.get(instanceId)?.emitData(data); } - public $cwdChange(instanceId: number, cwd: URI | undefined): void { - this._activeShellIntegrations.get(instanceId)?.setCwd(isUriComponents(cwd) ? URI.revive(cwd) : cwd); + public $cwdChange(instanceId: number, cwd: UriComponents | undefined): void { + this._activeShellIntegrations.get(instanceId)?.setCwd(URI.revive(cwd)); } public $closeTerminal(instanceId: number): void { @@ -202,7 +202,8 @@ class InternalTerminalShellIntegration extends Disposable { this._currentExecution.endExecution(undefined); this._onDidRequestEndExecution.fire({ terminal: this._terminal, shellIntegration: this.value, execution: this._currentExecution.value, exitCode: undefined }); } - const currentExecution = this._currentExecution = new InternalTerminalShellExecution(commandLine, cwd); + // Fallback to the shell integration's cwd as the cwd may not have been restored after a reload + const currentExecution = this._currentExecution = new InternalTerminalShellExecution(commandLine, cwd ?? this._cwd); if (fireEventInMicrotask) { queueMicrotask(() => this._onDidStartTerminalShellExecution.fire({ terminal: this._terminal, shellIntegration: this.value, execution: currentExecution.value })); } else { diff --git a/patched-vscode/src/vs/workbench/api/common/extHostTesting.ts b/patched-vscode/src/vs/workbench/api/common/extHostTesting.ts index 64458c0b..456816da 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostTesting.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostTesting.ts @@ -14,20 +14,23 @@ import { hash } from 'vs/base/common/hash'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { isDefined } from 'vs/base/common/types'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IPosition } from 'vs/editor/common/core/position'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostTestingShape, ILocationDto, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostTestItemCollection, TestItemImpl, TestItemRootImpl, toItemFromContext } from 'vs/workbench/api/common/extHostTestItem'; import * as Convert from 'vs/workbench/api/common/extHostTypeConverters'; -import { TestRunProfileKind, TestRunRequest, FileCoverage } from 'vs/workbench/api/common/extHostTypes'; +import { FileCoverage, TestRunProfileKind, TestRunRequest } from 'vs/workbench/api/common/extHostTypes'; import { TestCommandId } from 'vs/workbench/contrib/testing/common/constants'; -import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId'; +import { TestId, TestPosition } from 'vs/workbench/contrib/testing/common/testId'; import { InvalidTestItemError } from 'vs/workbench/contrib/testing/common/testItemCollection'; -import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, ITestRunProfile, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestMessageFollowupRequest, TestMessageFollowupResponse, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes'; +import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, ITestRunProfile, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestControllerCapability, TestMessageFollowupRequest, TestMessageFollowupResponse, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; @@ -35,7 +38,8 @@ interface ControllerInfo { controller: vscode.TestController; profiles: Map; collection: ExtHostTestItemCollection; - extension: Readonly; + extension: IExtensionDescription; + relatedCodeProvider?: vscode.TestRelatedCodeProvider; activeProfiles: Set; } @@ -45,7 +49,14 @@ let followupCounter = 0; const testResultInternalIDs = new WeakMap(); +export const IExtHostTesting = createDecorator('IExtHostTesting'); +export interface IExtHostTesting extends ExtHostTesting { + readonly _serviceBrand: undefined; +} + export class ExtHostTesting extends Disposable implements ExtHostTestingShape { + declare readonly _serviceBrand: undefined; + private readonly resultsChangedEmitter = this._register(new Emitter()); protected readonly controllers = new Map(); private readonly proxy: MainThreadTestingShape; @@ -61,8 +72,8 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { constructor( @IExtHostRpcService rpc: IExtHostRpcService, @ILogService private readonly logService: ILogService, - private readonly commands: ExtHostCommands, - private readonly editors: ExtHostDocumentsAndEditors, + @IExtHostCommands private readonly commands: IExtHostCommands, + @IExtHostDocumentsAndEditors private readonly editors: IExtHostDocumentsAndEditors, ) { super(); this.proxy = rpc.getProxy(MainContext.MainThreadTesting); @@ -111,6 +122,8 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { }); } + //#region public API + /** * Implements vscode.test.registerTestProvider */ @@ -127,6 +140,23 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { const activeProfiles = new Set(); const proxy = this.proxy; + const getCapability = () => { + let cap = 0; + if (refreshHandler) { + cap |= TestControllerCapability.Refresh; + } + const rcp = info.relatedCodeProvider; + if (rcp) { + if (rcp?.provideRelatedTests) { + cap |= TestControllerCapability.TestRelatedToCode; + } + if (rcp?.provideRelatedCode) { + cap |= TestControllerCapability.CodeRelatedToTest; + } + } + return cap as TestControllerCapability; + }; + const controller: vscode.TestController = { items: collection.root.children, get label() { @@ -142,11 +172,19 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { }, set refreshHandler(value: ((token: CancellationToken) => Thenable | void) | undefined) { refreshHandler = value; - proxy.$updateController(controllerId, { canRefresh: !!value }); + proxy.$updateController(controllerId, { capabilities: getCapability() }); }, get id() { return controllerId; }, + get relatedCodeProvider() { + return info.relatedCodeProvider; + }, + set relatedCodeProvider(value: vscode.TestRelatedCodeProvider | undefined) { + checkProposedApiEnabled(extension, 'testRelatedCode'); + info.relatedCodeProvider = value; + proxy.$updateController(controllerId, { capabilities: getCapability() }); + }, createRunProfile: (label, group, runHandler, isDefault, tag?: vscode.TestTag | undefined, supportsContinuousRun?: boolean) => { // Derive the profile ID from a hash so that the same profile will tend // to have the same hashes, allowing re-run requests to work across reloads. @@ -182,10 +220,10 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { }, }; - proxy.$registerTestController(controllerId, label, !!refreshHandler); + const info: ControllerInfo = { controller, collection, profiles, extension, activeProfiles }; + proxy.$registerTestController(controllerId, label, getCapability()); disposable.add(toDisposable(() => proxy.$unregisterTestController(controllerId))); - const info: ControllerInfo = { controller, collection, profiles, extension, activeProfiles }; this.controllers.set(controllerId, info); disposable.add(toDisposable(() => this.controllers.delete(controllerId))); @@ -218,9 +256,9 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { await this.proxy.$runTests({ preserveFocus: req.preserveFocus ?? true, + group: profileGroupToBitset[profile.kind], targets: [{ testIds: req.include?.map(t => TestId.fromExtHostTestItem(t, controller.collection.root.id).toString()) ?? [controller.collection.root.id], - profileGroup: profileGroupToBitset[profile.kind], profileId: profile.profileId, controllerId: profile.controllerId, }], @@ -236,6 +274,59 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { return { dispose: () => { this.followupProviders.delete(provider); } }; } + //#endregion + + //#region RPC methods + /** + * @inheritdoc + */ + async $getTestsRelatedToCode(uri: UriComponents, _position: IPosition, token: CancellationToken): Promise { + const doc = this.editors.getDocument(URI.revive(uri)); + if (!doc) { + return []; + } + + const position = Convert.Position.to(_position); + const related: string[] = []; + await Promise.all([...this.controllers.values()].map(async (c) => { + let tests: vscode.TestItem[] | undefined | null; + try { + tests = await c.relatedCodeProvider?.provideRelatedTests?.(doc.document, position, token); + } catch (e) { + if (!token.isCancellationRequested) { + this.logService.warn(`Error thrown while providing related tests for ${c.controller.label}`, e); + } + } + + if (tests) { + for (const test of tests) { + related.push(TestId.fromExtHostTestItem(test, c.controller.id).toString()); + } + c.collection.flushDiff(); + } + })); + + return related; + } + + /** + * @inheritdoc + */ + async $getCodeRelatedToTest(testId: string, token: CancellationToken): Promise { + const controller = this.controllers.get(TestId.root(testId)); + if (!controller) { + return []; + } + + const test = controller.collection.tree.get(testId); + if (!test) { + return []; + } + + const locations = await controller.relatedCodeProvider?.provideRelatedCode?.(test.actual, token); + return locations?.map(Convert.location.from) ?? []; + } + /** * @inheritdoc */ @@ -250,8 +341,8 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { /** * @inheritdoc */ - async $getCoverageDetails(coverageId: string, token: CancellationToken): Promise { - const details = await this.runTracker.getCoverageDetails(coverageId, token); + async $getCoverageDetails(coverageId: string, testId: string | undefined, token: CancellationToken): Promise { + const details = await this.runTracker.getCoverageDetails(coverageId, testId, token); return details?.map(Convert.TestCoverage.fromDetails); } @@ -308,6 +399,12 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { results .map(r => { const o = Convert.TestResults.to(r); + const taskWithCoverage = r.tasks.findIndex(t => t.hasCoverage); + if (taskWithCoverage !== -1) { + o.getDetailedCoverage = (uri, token = CancellationToken.None) => + this.proxy.$getCoverageDetails(r.id, taskWithCoverage, uri, token).then(r => r.map(Convert.TestCoverage.to)); + } + testResultInternalIDs.set(o, r.id); return o; }) @@ -412,6 +509,30 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { return this.commands.executeCommand(command.command, ...(command.arguments || [])); } + /** + * Cancels an ongoing test run. + */ + public $cancelExtensionTestRun(runId: string | undefined, taskId: string | undefined) { + if (runId === undefined) { + this.runTracker.cancelAllRuns(); + } else { + this.runTracker.cancelRunById(runId, taskId); + } + } + + //#endregion + + public getMetadataForRun(run: vscode.TestRun) { + for (const tracker of this.runTracker.trackers) { + const taskId = tracker.getTaskIdForRun(run); + if (taskId) { + return { taskId, runId: tracker.id }; + } + } + + return undefined; + } + private async runControllerTestRequest(req: ICallProfileRunHandler | ICallProfileRunHandler, isContinuous: boolean, token: CancellationToken): Promise { const lookup = this.controllers.get(req.controllerId); if (!lookup) { @@ -467,17 +588,6 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape { } } } - - /** - * Cancels an ongoing test run. - */ - public $cancelExtensionTestRun(runId: string | undefined) { - if (runId === undefined) { - this.runTracker.cancelAllRuns(); - } else { - this.runTracker.cancelRunById(runId); - } - } } // Deadline after being requested by a user that a test run is forcibly cancelled. @@ -495,12 +605,12 @@ const enum TestRunTrackerState { class TestRunTracker extends Disposable { private state = TestRunTrackerState.Running; private running = 0; - private readonly tasks = new Map(); + private readonly tasks = new Map(); private readonly sharedTestIds = new Set(); private readonly cts: CancellationTokenSource; private readonly endEmitter = this._register(new Emitter()); private readonly onDidDispose: Event; - private readonly publishedCoverage = new Map(); + private readonly publishedCoverage = new Map(); /** * Fires when a test ends, and no more tests are left running. @@ -526,7 +636,7 @@ class TestRunTracker extends Disposable { private readonly proxy: MainThreadTestingShape, private readonly logService: ILogService, private readonly profile: vscode.TestRunProfile | undefined, - private readonly extension: IRelaxedExtensionDescription, + private readonly extension: IExtensionDescription, parentToken?: CancellationToken, ) { super(); @@ -543,9 +653,22 @@ class TestRunTracker extends Disposable { })); } + /** Gets the task ID from a test run object. */ + public getTaskIdForRun(run: vscode.TestRun) { + for (const [taskId, { run: r }] of this.tasks) { + if (r === run) { + return taskId; + } + } + + return undefined; + } + /** Requests cancellation of the run. On the second call, forces cancellation. */ - public cancel() { - if (this.state === TestRunTrackerState.Running) { + public cancel(taskId?: string) { + if (taskId) { + this.tasks.get(taskId)?.cts.cancel(); + } else if (this.state === TestRunTrackerState.Running) { this.cts.cancel(); this.state = TestRunTrackerState.Cancelling; } else if (this.state === TestRunTrackerState.Cancelling) { @@ -554,19 +677,33 @@ class TestRunTracker extends Disposable { } /** Gets details for a previously-emitted coverage object. */ - public getCoverageDetails(id: string, token: CancellationToken) { + public async getCoverageDetails(id: string, testId: string | undefined, token: CancellationToken): Promise { const [, taskId] = TestId.fromString(id).path; /** runId, taskId, URI */ const coverage = this.publishedCoverage.get(id); if (!coverage) { return []; } + const { report, extIds } = coverage; const task = this.tasks.get(taskId); if (!task) { throw new Error('unreachable: run task was not found'); } - return this.profile?.loadDetailedCoverage?.(task.run, coverage, token) ?? []; + let testItem: vscode.TestItem | undefined; + if (testId && report instanceof FileCoverage) { + const index = extIds.indexOf(testId); + if (index === -1) { + return []; // ?? + } + testItem = report.fromTests[index]; + } + + const details = testItem + ? this.profile?.loadDetailedCoverageForTest?.(task.run, report, testItem, token) + : this.profile?.loadDetailedCoverage?.(task.run, report, token); + + return (await details) ?? []; } /** Creates the public test run interface to give to extensions. */ @@ -582,10 +719,6 @@ class TestRunTracker extends Disposable { return; } - if (!this.dto.isIncluded(test)) { - return; - } - this.ensureTestIsKnown(test); fn(test, ...args); }; @@ -606,14 +739,15 @@ class TestRunTracker extends Disposable { }; let ended = false; + // tasks are alive for as long as the tracker is alive, so simple this._register is fine: + const cts = this._register(new CancellationTokenSource(this.cts.token)); // one-off map used to associate test items with incrementing IDs in `addCoverage`. // There's no need to include their entire ID, we just want to make sure they're // stable and unique. Normal map is okay since TestRun lifetimes are limited. - const testItemCoverageId = new Map(); const run: vscode.TestRun = { isPersisted: this.dto.isPersisted, - token: this.cts.token, + token: cts.token, name, onDidDispose: this.onDidDispose, addCoverage: (coverage) => { @@ -621,28 +755,21 @@ class TestRunTracker extends Disposable { return; } - const testItem = coverage instanceof FileCoverage ? coverage.testItem : undefined; - let testItemIdPart: undefined | number; - if (testItem) { + const fromTests = coverage instanceof FileCoverage ? coverage.fromTests : []; + if (fromTests.length) { checkProposedApiEnabled(this.extension, 'attributableCoverage'); - if (!this.dto.isIncluded(testItem)) { - throw new Error('Attempted to `addCoverage` for a test item not included in the run'); - } - - this.ensureTestIsKnown(testItem); - testItemIdPart = testItemCoverageId.get(testItem); - if (testItemIdPart === undefined) { - testItemIdPart = testItemCoverageId.size; - testItemCoverageId.set(testItem, testItemIdPart); + for (const test of fromTests) { + this.ensureTestIsKnown(test); } } const uriStr = coverage.uri.toString(); - const id = new TestId(testItemIdPart !== undefined - ? [runId, taskId, uriStr, String(testItemIdPart)] - : [runId, taskId, uriStr], - ).toString(); - this.publishedCoverage.set(id, coverage); + const id = new TestId([runId, taskId, uriStr]).toString(); + // it's a lil funky, but it's possible for a test item's ID to change after + // it's been reported if it's rehomed under a different parent. Record its + // ID at the time when the coverage report is generated so we can reference + // it later if needeed. + this.publishedCoverage.set(id, { report: coverage, extIds: fromTests.map(t => TestId.fromExtHostTestItem(t, ctrlId).toString()) }); this.proxy.$appendCoverage(runId, taskId, Convert.TestCoverage.fromFile(ctrlId, id, coverage)); }, //#region state mutation @@ -673,11 +800,7 @@ class TestRunTracker extends Disposable { } if (test) { - if (this.dto.isIncluded(test)) { - this.ensureTestIsKnown(test); - } else { - test = undefined; - } + this.ensureTestIsKnown(test); } this.proxy.$appendOutputToRun( @@ -694,7 +817,6 @@ class TestRunTracker extends Disposable { } ended = true; - testItemCoverageId.clear(); this.proxy.$finishedTestRunTask(runId, taskId); if (!--this.running) { this.markEnded(); @@ -703,8 +825,13 @@ class TestRunTracker extends Disposable { }; this.running++; - this.tasks.set(taskId, { run }); - this.proxy.$startedTestRunTask(runId, { id: taskId, name, running: true }); + this.tasks.set(taskId, { run, cts }); + this.proxy.$startedTestRunTask(runId, { + id: taskId, + ctrlId: this.dto.controllerId, + name: name || this.extension.displayName || this.extension.identifier.value, + running: true, + }); return run; } @@ -778,9 +905,9 @@ export class TestRunCoordinator { /** * Gets a coverage report for a given run and task ID. */ - public getCoverageDetails(id: string, token: vscode.CancellationToken) { + public getCoverageDetails(id: string, testId: string | undefined, token: vscode.CancellationToken) { const runId = TestId.root(id); - return this.trackedById.get(runId)?.getCoverageDetails(id, token) || []; + return this.trackedById.get(runId)?.getCoverageDetails(id, testId, token) || []; } /** @@ -802,15 +929,15 @@ export class TestRunCoordinator { * `$startedExtensionTestRun` is not invoked. The run must eventually * be cancelled manually. */ - public prepareForMainThreadTestRun(extension: IRelaxedExtensionDescription, req: vscode.TestRunRequest, dto: TestRunDto, profile: vscode.TestRunProfile, token: CancellationToken) { + public prepareForMainThreadTestRun(extension: IExtensionDescription, req: vscode.TestRunRequest, dto: TestRunDto, profile: vscode.TestRunProfile, token: CancellationToken) { return this.getTracker(req, dto, profile, extension, token); } /** * Cancels an existing test run via its cancellation token. */ - public cancelRunById(runId: string) { - this.trackedById.get(runId)?.cancel(); + public cancelRunById(runId: string, taskId?: string) { + this.trackedById.get(runId)?.cancel(taskId); } /** @@ -825,7 +952,7 @@ export class TestRunCoordinator { /** * Implements the public `createTestRun` API. */ - public createTestRun(extension: IRelaxedExtensionDescription, controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun { + public createTestRun(extension: IExtensionDescription, controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun { const existing = this.tracked.get(request); if (existing) { return existing.createRun(name); @@ -854,7 +981,7 @@ export class TestRunCoordinator { return tracker.createRun(name); } - private getTracker(req: vscode.TestRunRequest, dto: TestRunDto, profile: vscode.TestRunProfile | undefined, extension: IRelaxedExtensionDescription, token?: CancellationToken) { + private getTracker(req: vscode.TestRunRequest, dto: TestRunDto, profile: vscode.TestRunProfile | undefined, extension: IExtensionDescription, token?: CancellationToken) { const tracker = new TestRunTracker(dto, this.proxy, this.logService, profile, extension, token); this.tracked.set(req, tracker); this.trackedById.set(tracker.id, tracker); @@ -875,15 +1002,10 @@ const tryGetProfileFromTestRunReq = (request: vscode.TestRunRequest) => { }; export class TestRunDto { - private readonly includePrefix: string[]; - private readonly excludePrefix: string[]; - public static fromPublic(controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest, persist: boolean) { return new TestRunDto( controllerId, generateUuid(), - request.include?.map(t => TestId.fromExtHostTestItem(t, controllerId).toString()) ?? [controllerId], - request.exclude?.map(t => TestId.fromExtHostTestItem(t, controllerId).toString()) ?? [], persist, collection, ); @@ -893,8 +1015,6 @@ export class TestRunDto { return new TestRunDto( request.controllerId, request.runId, - request.testIds, - request.excludeExtIds, true, collection, ); @@ -903,30 +1023,9 @@ export class TestRunDto { constructor( public readonly controllerId: string, public readonly id: string, - include: string[], - exclude: string[], public readonly isPersisted: boolean, public readonly colllection: ExtHostTestItemCollection, ) { - this.includePrefix = include.map(id => id + TestIdPathParts.Delimiter); - this.excludePrefix = exclude.map(id => id + TestIdPathParts.Delimiter); - } - - public isIncluded(test: vscode.TestItem) { - const id = TestId.fromExtHostTestItem(test, this.controllerId).toString() + TestIdPathParts.Delimiter; - for (const prefix of this.excludePrefix) { - if (id === prefix || id.startsWith(prefix)) { - return false; - } - } - - for (const prefix of this.includePrefix) { - if (id === prefix || id.startsWith(prefix)) { - return true; - } - } - - return false; } } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostTextEditor.ts b/patched-vscode/src/vs/workbench/api/common/extHostTextEditor.ts index 44ed8f38..73334e5a 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostTextEditor.ts @@ -566,6 +566,9 @@ export class ExtHostTextEditor { }, hide() { _proxy.$tryHideEditor(id); + }, + [Symbol.for('debug.description')]() { + return `TextEditor(${this.document.uri.toString()})`; } }); } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostTextEditors.ts b/patched-vscode/src/vs/workbench/api/common/extHostTextEditors.ts index 7ab8d65d..277422f9 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostTextEditors.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostTextEditors.ts @@ -5,6 +5,7 @@ import * as arrays from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostEditorsShape, IEditorPropertiesChangeData, IMainContext, ITextDocumentShowOptions, ITextEditorPositionData, MainContext, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -13,7 +14,7 @@ import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { TextEditorSelectionChangeKind } from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; -export class ExtHostEditors implements ExtHostEditorsShape { +export class ExtHostEditors extends Disposable implements ExtHostEditorsShape { private readonly _onDidChangeTextEditorSelection = new Emitter(); private readonly _onDidChangeTextEditorOptions = new Emitter(); @@ -35,11 +36,11 @@ export class ExtHostEditors implements ExtHostEditorsShape { mainContext: IMainContext, private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, ) { + super(); this._proxy = mainContext.getProxy(MainContext.MainThreadTextEditors); - - this._extHostDocumentsAndEditors.onDidChangeVisibleTextEditors(e => this._onDidChangeVisibleTextEditors.fire(e)); - this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e)); + this._register(this._extHostDocumentsAndEditors.onDidChangeVisibleTextEditors(e => this._onDidChangeVisibleTextEditors.fire(e))); + this._register(this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e))); } getActiveTextEditor(): vscode.TextEditor | undefined { diff --git a/patched-vscode/src/vs/workbench/api/common/extHostTypeConverters.ts b/patched-vscode/src/vs/workbench/api/common/extHostTypeConverters.ts index 6525d0f2..b0e7f785 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -10,7 +10,7 @@ import { createSingleCallFunction } from 'vs/base/common/functional'; import * as htmlContent from 'vs/base/common/htmlContent'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ResourceMap, ResourceSet } from 'vs/base/common/map'; -import { marked } from 'vs/base/common/marked/marked'; +import * as marked from 'vs/base/common/marked/marked'; import { parse, revive } from 'vs/base/common/marshalling'; import { Mimes } from 'vs/base/common/mime'; import { cloneAndChange } from 'vs/base/common/objects'; @@ -40,7 +40,8 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatRequestVariableEntry } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatAgentDetection, IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatAgentDetection, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatProgressMessage, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from 'vs/workbench/contrib/chat/common/chatService'; +import { IToolData, IToolResult } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import * as chatProvider from 'vs/workbench/contrib/chat/common/languageModels'; import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -366,7 +367,7 @@ export namespace MarkdownString { const resUris: { [href: string]: UriComponents } = Object.create(null); res.uris = resUris; - const collectUri = (href: string): string => { + const collectUri = ({ href }: { href: string }): string => { try { let uri = URI.parse(href, true); uri = uri.with({ query: _uriMassage(uri.query, resUris) }); @@ -376,11 +377,16 @@ export namespace MarkdownString { } return ''; }; - const renderer = new marked.Renderer(); - renderer.link = collectUri; - renderer.image = href => typeof href === 'string' ? collectUri(htmlContent.parseHrefAndDimensions(href).href) : ''; - marked(res.value, { renderer }); + marked.marked.walkTokens(marked.marked.lexer(res.value), token => { + if (token.type === 'link') { + collectUri({ href: token.href }); + } else if (token.type === 'image') { + if (typeof token.href === 'string') { + collectUri(htmlContent.parseHrefAndDimensions(token.href)); + } + } + }); return res; } @@ -786,7 +792,7 @@ export namespace SymbolTag { export namespace WorkspaceSymbol { export function from(info: vscode.SymbolInformation): search.IWorkspaceSymbol { - return { + return { name: info.name, kind: SymbolKind.from(info.kind), tags: info.tags && info.tags.map(SymbolTag.from), @@ -965,7 +971,7 @@ export namespace Hover { export namespace EvaluatableExpression { export function from(expression: vscode.EvaluatableExpression): languages.EvaluatableExpression { - return { + return { range: Range.from(expression.range), expression: expression.expression }; @@ -979,24 +985,24 @@ export namespace EvaluatableExpression { export namespace InlineValue { export function from(inlineValue: vscode.InlineValue): languages.InlineValue { if (inlineValue instanceof types.InlineValueText) { - return { + return { type: 'text', range: Range.from(inlineValue.range), text: inlineValue.text - }; + } satisfies languages.InlineValueText; } else if (inlineValue instanceof types.InlineValueVariableLookup) { - return { + return { type: 'variable', range: Range.from(inlineValue.range), variableName: inlineValue.variableName, caseSensitiveLookup: inlineValue.caseSensitiveLookup - }; + } satisfies languages.InlineValueVariableLookup; } else if (inlineValue instanceof types.InlineValueEvaluatableExpression) { - return { + return { type: 'expression', range: Range.from(inlineValue.range), expression: inlineValue.expression - }; + } satisfies languages.InlineValueExpression; } else { throw new Error(`Unknown 'InlineValue' type`); } @@ -1005,28 +1011,28 @@ export namespace InlineValue { export function to(inlineValue: languages.InlineValue): vscode.InlineValue { switch (inlineValue.type) { case 'text': - return { + return { range: Range.to(inlineValue.range), text: inlineValue.text - }; + } satisfies vscode.InlineValueText; case 'variable': - return { + return { range: Range.to(inlineValue.range), variableName: inlineValue.variableName, caseSensitiveLookup: inlineValue.caseSensitiveLookup - }; + } satisfies vscode.InlineValueVariableLookup; case 'expression': - return { + return { range: Range.to(inlineValue.range), expression: inlineValue.expression - }; + } satisfies vscode.InlineValueEvaluatableExpression; } } } export namespace InlineValueContext { export function from(inlineValueContext: vscode.InlineValueContext): extHostProtocol.IInlineValueContextDto { - return { + return { frameId: inlineValueContext.frameId, stoppedLocation: Range.from(inlineValueContext.stoppedLocation) }; @@ -1330,7 +1336,9 @@ export namespace DocumentLink { // ignore } } - return new types.DocumentLink(Range.to(link.range), target); + const result = new types.DocumentLink(Range.to(link.range), target); + result.tooltip = link.tooltip; + return result; } } @@ -1885,6 +1893,11 @@ export namespace TestMessage { actual: message.actualOutput, contextValue: message.contextValue, location: message.location && ({ range: Range.from(message.location.range), uri: message.location.uri }), + stackTrace: message.stackTrace?.map(s => ({ + label: s.label, + position: s.position && Position.from(s.position), + uri: s.uri && URI.revive(s.uri).toJSON(), + })), }; } @@ -2028,6 +2041,43 @@ export namespace TestCoverage { return 'line' in location ? Position.from(location) : Range.from(location); } + function toLocation(location: IPosition | editorRange.IRange): types.Position | types.Range; + function toLocation(location: IPosition | editorRange.IRange | undefined): types.Position | types.Range | undefined; + function toLocation(location: IPosition | editorRange.IRange | undefined): types.Position | types.Range | undefined { + if (!location) { return undefined; } + return 'endLineNumber' in location ? Range.to(location) : Position.to(location); + } + + export function to(serialized: CoverageDetails.Serialized): vscode.FileCoverageDetail { + if (serialized.type === DetailType.Statement) { + const branches: vscode.BranchCoverage[] = []; + if (serialized.branches) { + for (const branch of serialized.branches) { + branches.push({ + executed: branch.count, + location: toLocation(branch.location), + label: branch.label + }); + } + } + return new types.StatementCoverage( + serialized.count, + toLocation(serialized.location), + serialized.branches?.map(b => new types.BranchCoverage( + b.count, + toLocation(b.location)!, + b.label, + )) + ); + } else { + return new types.DeclarationCoverage( + serialized.name, + serialized.count, + toLocation(serialized.location), + ); + } + } + export function fromDetails(coverage: vscode.FileCoverageDetail): CoverageDetails.Serialized { if (typeof coverage.executed === 'number' && coverage.executed < 0) { throw new Error(`Invalid coverage count ${coverage.executed}`); @@ -2063,8 +2113,8 @@ export namespace TestCoverage { statement: fromCoverageCount(coverage.statementCoverage), branch: coverage.branchCoverage && fromCoverageCount(coverage.branchCoverage), declaration: coverage.declarationCoverage && fromCoverageCount(coverage.declarationCoverage), - testId: coverage instanceof types.FileCoverage && coverage.testItem ? - TestId.fromExtHostTestItem(coverage.testItem, controllerId).toString() : undefined, + testIds: coverage instanceof types.FileCoverage && coverage.fromTests.length ? + coverage.fromTests.map(t => TestId.fromExtHostTestItem(t, controllerId).toString()) : undefined, }; } } @@ -2241,23 +2291,81 @@ export namespace ChatFollowup { } } +export namespace LanguageModelChatMessageRole { + export function to(role: chatProvider.ChatMessageRole): vscode.LanguageModelChatMessageRole { + switch (role) { + case chatProvider.ChatMessageRole.System: return types.LanguageModelChatMessageRole.System; + case chatProvider.ChatMessageRole.User: return types.LanguageModelChatMessageRole.User; + case chatProvider.ChatMessageRole.Assistant: return types.LanguageModelChatMessageRole.Assistant; + } + } + + export function from(role: vscode.LanguageModelChatMessageRole): chatProvider.ChatMessageRole { + switch (role) { + case types.LanguageModelChatMessageRole.System: return chatProvider.ChatMessageRole.System; + case types.LanguageModelChatMessageRole.User: return chatProvider.ChatMessageRole.User; + case types.LanguageModelChatMessageRole.Assistant: return chatProvider.ChatMessageRole.Assistant; + } + return chatProvider.ChatMessageRole.User; + } +} export namespace LanguageModelChatMessage { export function to(message: chatProvider.IChatMessage): vscode.LanguageModelChatMessage { - switch (message.role) { - case chatProvider.ChatMessageRole.System: return new types.LanguageModelChatMessage(types.LanguageModelChatMessageRole.System, message.content); - case chatProvider.ChatMessageRole.User: return new types.LanguageModelChatMessage(types.LanguageModelChatMessageRole.User, message.content); - case chatProvider.ChatMessageRole.Assistant: return new types.LanguageModelChatMessage(types.LanguageModelChatMessageRole.Assistant, message.content); - } + const content2 = message.content.map(c => { + if (c.type === 'text') { + return c.value; + } else if (c.type === 'tool_result') { + return new types.LanguageModelToolResultPart(c.toolCallId, c.value, c.isError); + } else { + return new types.LanguageModelToolCallPart(c.name, c.toolCallId, c.parameters); + } + }); + const content = content2.find(c => typeof c === 'string') ?? ''; + const role = LanguageModelChatMessageRole.to(message.role); + const result = new types.LanguageModelChatMessage(role, content, message.name); + result.content2 = content2; + return result; } export function from(message: vscode.LanguageModelChatMessage): chatProvider.IChatMessage { - switch (message.role as types.LanguageModelChatMessageRole) { - case types.LanguageModelChatMessageRole.System: return { role: chatProvider.ChatMessageRole.System, content: message.content }; - case types.LanguageModelChatMessageRole.User: return { role: chatProvider.ChatMessageRole.User, content: message.content }; - case types.LanguageModelChatMessageRole.Assistant: return { role: chatProvider.ChatMessageRole.Assistant, content: message.content }; - } + + const role = LanguageModelChatMessageRole.from(message.role); + const name = message.name; + + const content = message.content2.map((c): chatProvider.IChatMessagePart => { + if (c instanceof types.LanguageModelToolResultPart) { + return { + type: 'tool_result', + toolCallId: c.toolCallId, + value: c.content, + isError: c.isError + }; + } else if (c instanceof types.LanguageModelToolCallPart) { + return { + type: 'tool_use', + toolCallId: c.toolCallId, + name: c.name, + parameters: c.parameters + }; + } else { + if (typeof c !== 'string') { + throw new Error('Unexpected chat message content type'); + } + + return { + type: 'text', + value: c + }; + } + }); + + return { + role, + name, + content + }; } } @@ -2305,7 +2413,8 @@ export namespace ChatResponseConfirmationPart { kind: 'confirmation', title: part.title, message: part.message, - data: part.data + data: part.data, + buttons: part.buttons }; } } @@ -2394,6 +2503,19 @@ export namespace ChatResponseWarningPart { } } +export namespace ChatResponseMovePart { + export function from(part: vscode.ChatResponseMovePart): Dto { + return { + kind: 'move', + uri: part.uri, + range: Range.from(part.range), + }; + } + export function to(part: Dto): vscode.ChatResponseMovePart { + return new types.ChatResponseMovePart(URI.revive(part.uri), Range.to(part.range)); + } +} + export namespace ChatTask { export function from(part: vscode.ChatResponseProgressPart2): IChatTaskDto { return { @@ -2447,7 +2569,8 @@ export namespace ChatResponseReferencePart { : URI.isUri(part.iconPath) ? { light: URI.revive(part.iconPath) } : (part.iconPath && 'light' in part.iconPath && 'dark' in part.iconPath && URI.isUri(part.iconPath.light) && URI.isUri(part.iconPath.dark) ? { light: URI.revive(part.iconPath.light), dark: URI.revive(part.iconPath.dark) } : undefined); - if ('variableName' in part.value) { + + if (typeof part.value === 'object' && 'variableName' in part.value) { return { kind: 'reference', reference: { @@ -2456,16 +2579,18 @@ export namespace ChatResponseReferencePart { part.value.value : Location.from(part.value.value as vscode.Location) }, - iconPath + iconPath, + options: part.options }; } return { kind: 'reference', - reference: URI.isUri(part.value) ? + reference: URI.isUri(part.value) || typeof part.value === 'string' ? part.value : Location.from(part.value), - iconPath + iconPath, + options: part.options }; } export function to(part: Dto): vscode.ChatResponseReferencePart { @@ -2476,7 +2601,7 @@ export namespace ChatResponseReferencePart { Location.to(value); return new types.ChatResponseReferencePart( - 'variableName' in value.reference ? { + typeof value.reference === 'string' ? value.reference : 'variableName' in value.reference ? { variableName: value.reference.variableName, value: value.reference.value && mapValue(value.reference.value) } : @@ -2485,9 +2610,20 @@ export namespace ChatResponseReferencePart { } } +export namespace ChatResponseCodeCitationPart { + export function from(part: vscode.ChatResponseCodeCitationPart): Dto { + return { + kind: 'codeCitation', + value: part.value, + license: part.license, + snippet: part.snippet + }; + } +} + export namespace ChatResponsePart { - export function from(part: vscode.ChatResponsePart | vscode.ChatResponseTextEditPart | vscode.ChatResponseMarkdownWithVulnerabilitiesPart | vscode.ChatResponseDetectedParticipantPart | vscode.ChatResponseWarningPart | vscode.ChatResponseConfirmationPart | vscode.ChatResponseWarningPart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto { + export function from(part: vscode.ChatResponsePart | vscode.ChatResponseTextEditPart | vscode.ChatResponseMarkdownWithVulnerabilitiesPart | vscode.ChatResponseDetectedParticipantPart | vscode.ChatResponseWarningPart | vscode.ChatResponseConfirmationPart | vscode.ChatResponseReferencePart2 | vscode.ChatResponseMovePart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto { if (part instanceof types.ChatResponseMarkdownPart) { return ChatResponseMarkdownPart.from(part); } else if (part instanceof types.ChatResponseAnchorPart) { @@ -2508,6 +2644,12 @@ export namespace ChatResponsePart { return ChatResponseDetectedParticipantPart.from(part); } else if (part instanceof types.ChatResponseWarningPart) { return ChatResponseWarningPart.from(part); + } else if (part instanceof types.ChatResponseConfirmationPart) { + return ChatResponseConfirmationPart.from(part); + } else if (part instanceof types.ChatResponseCodeCitationPart) { + return ChatResponseCodeCitationPart.from(part); + } else if (part instanceof types.ChatResponseMovePart) { + return ChatResponseMovePart.from(part); } return { @@ -2543,16 +2685,21 @@ export namespace ChatResponsePart { } export namespace ChatAgentRequest { - export function to(request: IChatAgentRequest): vscode.ChatRequest { + export function to(request: IChatAgentRequest, location2: vscode.ChatRequestEditorData | vscode.ChatRequestNotebookData | undefined): vscode.ChatRequest { + const toolReferences = request.variables.variables.filter(v => v.isTool); + const variableReferences = request.variables.variables.filter(v => !v.isTool); return { prompt: request.message, command: request.command, attempt: request.attempt ?? 0, enableCommandDetection: request.enableCommandDetection ?? true, - references: request.variables.variables.map(ChatAgentValueReference.to), + isParticipantDetected: request.isParticipantDetected ?? false, + references: variableReferences.map(ChatPromptReference.to), + toolReferences: toolReferences.map(ChatLanguageModelToolReference.to), location: ChatLocation.to(request.location), acceptedConfirmationData: request.acceptedConfirmationData, - rejectedConfirmationData: request.rejectedConfirmationData + rejectedConfirmationData: request.rejectedConfirmationData, + location2, }; } } @@ -2577,7 +2724,7 @@ export namespace ChatLocation { } } -export namespace ChatAgentValueReference { +export namespace ChatPromptReference { export function to(variable: IChatRequestVariableEntry): vscode.ChatPromptReference { const value = variable.value; if (!value) { @@ -2596,6 +2743,20 @@ export namespace ChatAgentValueReference { } } +export namespace ChatLanguageModelToolReference { + export function to(variable: IChatRequestVariableEntry): vscode.ChatLanguageModelToolReference { + const value = variable.value; + if (value) { + throw new Error('Invalid tool reference'); + } + + return { + id: variable.id, + range: variable.range && [variable.range.start, variable.range.endExclusive], + }; + } +} + export namespace ChatAgentCompletionItem { export function from(item: vscode.ChatCompletionItem, commandsConverter: CommandsConverter, disposables: DisposableStore): extHostProtocol.IChatAgentCompletionItem { return { @@ -2617,6 +2778,7 @@ export namespace ChatAgentResult { return { errorDetails: result.errorDetails, metadata: result.metadata, + nextQuestion: result.nextQuestion, }; } } @@ -2647,6 +2809,24 @@ export namespace ChatAgentUserActionEvent { } } +export namespace LanguageModelToolResult { + export function from(result: vscode.LanguageModelToolResult): IToolResult { + return { + ...result, + string: result.toString(), + }; + } + + export function to(result: IToolResult): vscode.LanguageModelToolResult { + const copy: vscode.LanguageModelToolResult = { + ...result, + toString: () => result.string, + }; + delete copy.string; + + return copy; + } +} export namespace TerminalQuickFix { export function from(quickFix: vscode.TerminalQuickFixTerminalCommand | vscode.TerminalQuickFixOpener | vscode.Command, converter: Command.ICommandsConverter, disposables: DisposableStore): extHostProtocol.ITerminalQuickFixTerminalCommandDto | extHostProtocol.ITerminalQuickFixOpenerDto | extHostProtocol.ICommandDto | undefined { @@ -2695,3 +2875,14 @@ export namespace DebugTreeItem { }; } } + +export namespace LanguageModelToolDescription { + export function to(item: IToolData): vscode.LanguageModelToolDescription { + return { + id: item.id, + modelDescription: item.modelDescription, + parametersSchema: item.parametersSchema, + displayName: item.displayName, + }; + } +} diff --git a/patched-vscode/src/vs/workbench/api/common/extHostTypes.ts b/patched-vscode/src/vs/workbench/api/common/extHostTypes.ts index 189c90b8..e8d47239 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostTypes.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostTypes.ts @@ -271,6 +271,10 @@ export class Position { toJSON(): any { return { line: this.line, character: this.character }; } + + [Symbol.for('debug.description')]() { + return `(${this.line}:${this.character})`; + } } @es5ClassCompat @@ -417,6 +421,10 @@ export class Range { toJSON(): any { return [this.start, this.end]; } + + [Symbol.for('debug.description')]() { + return getDebugDescriptionOfRange(this); + } } @es5ClassCompat @@ -483,6 +491,29 @@ export class Selection extends Range { anchor: this.anchor }; } + + + [Symbol.for('debug.description')]() { + return getDebugDescriptionOfSelection(this); + } +} + +export function getDebugDescriptionOfRange(range: vscode.Range): string { + return range.isEmpty + ? `[${range.start.line}:${range.start.character})` + : `[${range.start.line}:${range.start.character} -> ${range.end.line}:${range.end.character})`; +} + +export function getDebugDescriptionOfSelection(selection: vscode.Selection): string { + let rangeStr = getDebugDescriptionOfRange(selection); + if (!selection.isEmpty) { + if (selection.active.isEqual(selection.start)) { + rangeStr = `|${rangeStr}`; + } else { + rangeStr = `${rangeStr}|`; + } + } + return rangeStr; } const validateConnectionToken = (connectionToken: string) => { @@ -1205,18 +1236,18 @@ export class Hover { @es5ClassCompat export class VerboseHover extends Hover { - public canIncreaseHover: boolean | undefined; - public canDecreaseHover: boolean | undefined; + public canIncreaseVerbosity: boolean | undefined; + public canDecreaseVerbosity: boolean | undefined; constructor( contents: vscode.MarkdownString | vscode.MarkedString | (vscode.MarkdownString | vscode.MarkedString)[], range?: Range, - canIncreaseHover?: boolean, - canDecreaseHover?: boolean, + canIncreaseVerbosity?: boolean, + canDecreaseVerbosity?: boolean, ) { super(contents, range); - this.canIncreaseHover = canIncreaseHover; - this.canDecreaseHover = canDecreaseHover; + this.canIncreaseVerbosity = canIncreaseVerbosity; + this.canDecreaseVerbosity = canDecreaseVerbosity; } } @@ -3310,6 +3341,11 @@ export enum CommentThreadApplicability { Outdated = 1 } +export enum CommentThreadFocus { + Reply = 1, + Comment = 2 +} + //#endregion //#region Semantic Coloring @@ -3561,6 +3597,11 @@ export class DebugVisualization { //#endregion +export enum QuickInputButtonLocation { + Title = 1, + Inline = 2 +} + @es5ClassCompat export class QuickInputButtons { @@ -4038,9 +4079,11 @@ export class TestMessage implements vscode.TestMessage { public expectedOutput?: string; public actualOutput?: string; public location?: vscode.Location; - /** proposed: */ public contextValue?: string; + /** proposed: */ + public stackTrace?: TestMessageStackFrame[]; + public static diff(message: string | vscode.MarkdownString, expected: string, actual: string) { const msg = new TestMessage(message); msg.expectedOutput = expected; @@ -4056,6 +4099,19 @@ export class TestTag implements vscode.TestTag { constructor(public readonly id: string) { } } +export class TestMessageStackFrame { + /** + * @param label The name of the stack frame + * @param file The file URI of the stack frame + * @param position The position of the stack frame within the file + */ + constructor( + public label: string, + public uri?: vscode.Uri, + public position?: Position, + ) { } +} + //#endregion //#region Test Coverage @@ -4119,7 +4175,7 @@ export class FileCoverage implements vscode.FileCoverage { public statementCoverage: vscode.TestCoverageCount, public branchCoverage?: vscode.TestCoverageCount, public declarationCoverage?: vscode.TestCoverageCount, - public testItem?: vscode.TestItem, + public fromTests: vscode.TestItem[] = [], ) { } } @@ -4346,10 +4402,13 @@ export class ChatResponseConfirmationPart { title: string; message: string; data: any; - constructor(title: string, message: string, data: any) { + buttons?: string[]; + + constructor(title: string, message: string, data: any, buttons?: string[]) { this.title = title; this.message = message; this.data = data; + this.buttons = buttons; } } @@ -4406,11 +4465,32 @@ export class ChatResponseCommandButtonPart { } export class ChatResponseReferencePart { - value: vscode.Uri | vscode.Location | { variableName: string; value?: vscode.Uri | vscode.Location }; + value: vscode.Uri | vscode.Location | { variableName: string; value?: vscode.Uri | vscode.Location } | string; iconPath?: vscode.Uri | vscode.ThemeIcon | { light: vscode.Uri; dark: vscode.Uri }; - constructor(value: vscode.Uri | vscode.Location | { variableName: string; value?: vscode.Uri | vscode.Location }, iconPath?: vscode.Uri | vscode.ThemeIcon | { light: vscode.Uri; dark: vscode.Uri }) { + options?: { status?: { description: string; kind: vscode.ChatResponseReferencePartStatusKind } }; + constructor(value: vscode.Uri | vscode.Location | { variableName: string; value?: vscode.Uri | vscode.Location } | string, iconPath?: vscode.Uri | vscode.ThemeIcon | { light: vscode.Uri; dark: vscode.Uri }, options?: { status?: { description: string; kind: vscode.ChatResponseReferencePartStatusKind } }) { this.value = value; this.iconPath = iconPath; + this.options = options; + } +} + +export class ChatResponseCodeCitationPart { + value: vscode.Uri; + license: string; + snippet: string; + constructor(value: vscode.Uri, license: string, snippet: string) { + this.value = value; + this.license = license; + this.snippet = snippet; + } +} + +export class ChatResponseMovePart { + constructor( + public readonly uri: vscode.Uri, + public readonly range: vscode.Range, + ) { } } @@ -4424,6 +4504,8 @@ export class ChatResponseTextEditPart { } export class ChatRequestTurn implements vscode.ChatRequestTurn { + toolReferences?: vscode.ChatLanguageModelToolReference[]; + constructor( readonly prompt: string, readonly command: string | undefined, @@ -4449,16 +4531,51 @@ export enum ChatLocation { Editor = 4, } +export enum ChatResponseReferencePartStatusKind { + Complete = 1, + Partial = 2, + Omitted = 3 +} + +export class ChatRequestEditorData implements vscode.ChatRequestEditorData { + constructor( + readonly document: vscode.TextDocument, + readonly selection: vscode.Selection, + readonly wholeRange: vscode.Range, + ) { } +} + +export class ChatRequestNotebookData implements vscode.ChatRequestNotebookData { + constructor( + readonly cell: vscode.TextDocument + ) { } +} + export enum LanguageModelChatMessageRole { User = 1, Assistant = 2, System = 3 } +export class LanguageModelToolResultPart implements vscode.LanguageModelChatMessageToolResultPart { + + toolCallId: string; + content: string; + isError: boolean; + + constructor(toolCallId: string, content: string, isError?: boolean) { + this.toolCallId = toolCallId; + this.content = content; + this.isError = isError ?? false; + } +} + export class LanguageModelChatMessage implements vscode.LanguageModelChatMessage { - static User(content: string, name?: string): LanguageModelChatMessage { - return new LanguageModelChatMessage(LanguageModelChatMessageRole.User, content, name); + static User(content: string | LanguageModelToolResultPart, name?: string): LanguageModelChatMessage { + const value = new LanguageModelChatMessage(LanguageModelChatMessageRole.User, typeof content === 'string' ? content : '', name); + value.content2 = [content]; + return value; } static Assistant(content: string, name?: string): LanguageModelChatMessage { @@ -4467,12 +4584,35 @@ export class LanguageModelChatMessage implements vscode.LanguageModelChatMessage role: vscode.LanguageModelChatMessageRole; content: string; + content2: (string | vscode.LanguageModelChatMessageToolResultPart | vscode.LanguageModelChatResponseToolCallPart)[]; name: string | undefined; constructor(role: vscode.LanguageModelChatMessageRole, content: string, name?: string) { this.role = role; this.content = content; + this.content2 = [content]; + this.name = name; + } +} + +export class LanguageModelToolCallPart implements vscode.LanguageModelChatResponseToolCallPart { + name: string; + toolCallId: string; + parameters: any; + + constructor(name: string, toolCallId: string, parameters: any) { this.name = name; + this.toolCallId = toolCallId; + this.parameters = parameters; + } +} + +export class LanguageModelTextPart implements vscode.LanguageModelChatResponseTextPart { + value: string; + + constructor(value: string) { + this.value = value; + } } diff --git a/patched-vscode/src/vs/workbench/api/common/extHostWorkspace.ts b/patched-vscode/src/vs/workbench/api/common/extHostWorkspace.ts index 08b842e8..951c87d9 100644 --- a/patched-vscode/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/patched-vscode/src/vs/workbench/api/common/extHostWorkspace.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { delta as arrayDelta, mapArrayOrNot } from 'vs/base/common/arrays'; -import { Barrier } from 'vs/base/common/async'; +import { AsyncIterableObject, Barrier } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event'; import { toDisposable } from 'vs/base/common/lifecycle'; @@ -28,17 +28,21 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { GlobPattern } from 'vs/workbench/api/common/extHostTypeConverters'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; -import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, ISearchPatternBuilder, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import { IRawFileMatch2, ITextSearchResult, resultIsMatch } from 'vs/workbench/services/search/common/search'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; import { revive } from 'vs/base/common/marshalling'; +import { AuthInfo, Credentials } from 'vs/platform/request/common/request'; +import { ExcludeSettingOptions, TextSearchContextNew, TextSearchMatchNew } from 'vs/workbench/services/search/common/searchExtTypes'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; resolveWorkspaceFolder(uri: vscode.Uri): Promise; getWorkspaceFolders2(): Promise; resolveProxy(url: string): Promise; + lookupAuthorization(authInfo: AuthInfo): Promise; + lookupKerberosAuthorization(url: string): Promise; loadCertificates(): Promise; } @@ -75,6 +79,11 @@ interface MutableWorkspaceFolder extends vscode.WorkspaceFolder { index: number; } +interface QueryOptions { + options: T; + folder: URI | undefined; +} + class ExtHostWorkspaceImpl extends Workspace { static toExtHostWorkspace(data: IWorkspaceData | null, previousConfirmedWorkspace: ExtHostWorkspaceImpl | undefined, previousUnconfirmedWorkspace: ExtHostWorkspaceImpl | undefined, extHostFileSystemInfo: IExtHostFileSystemInfo): { workspace: ExtHostWorkspaceImpl | null; added: vscode.WorkspaceFolder[]; removed: vscode.WorkspaceFolder[] } { @@ -132,7 +141,7 @@ class ExtHostWorkspaceImpl extends Workspace { constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[], transient: boolean, configuration: URI | null, private _isUntitled: boolean, ignorePathCasing: (key: URI) => boolean) { super(id, folders.map(f => new WorkspaceFolder(f)), transient, configuration, ignorePathCasing); - this._structure = TernarySearchTree.forUris(ignorePathCasing); + this._structure = TernarySearchTree.forUris(ignorePathCasing, () => true); // setup the workspace folder data structure folders.forEach(folder => { @@ -457,12 +466,15 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac excludeString = exclude.pattern; } } + + // todo: consider exclude baseURI if available return this._findFilesImpl(include, undefined, { - exclude: excludeString, + exclude: [excludeString], maxResults, - useDefaultExcludes: useFileExcludes, - useDefaultSearchExcludes: false, - useIgnoreFiles: false + useExcludeSettings: useFileExcludes ? ExcludeSettingOptions.FilesExclude : ExcludeSettingOptions.None, + useIgnoreFiles: { + local: false + } }, token); } @@ -471,86 +483,191 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles2: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2`); - return this._findFilesImpl(undefined, filePattern, options, token); + + + const useDefaultExcludes = options.useDefaultExcludes ?? true; + const useDefaultSearchExcludes = options.useDefaultSearchExcludes ?? true; + const excludeSetting = useDefaultExcludes ? + (useDefaultSearchExcludes ? ExcludeSettingOptions.SearchAndFilesExclude : ExcludeSettingOptions.FilesExclude) : + ExcludeSettingOptions.None; + const newOptions: vscode.FindFiles2OptionsNew = { + exclude: options.exclude ? [options.exclude] : undefined, + useIgnoreFiles: { + local: options.useIgnoreFiles, + global: options.useGlobalIgnoreFiles, + parent: options.useParentIgnoreFiles + }, + useExcludeSettings: excludeSetting, + followSymlinks: options.followSymlinks, + maxResults: options.maxResults, + }; + return this._findFilesImpl(undefined, filePattern !== undefined ? [filePattern] : [], newOptions, token); + } + + findFiles2New(filePatterns: vscode.GlobPattern[], + options: vscode.FindFiles2OptionsNew = {}, + extensionId: ExtensionIdentifier, + token: vscode.CancellationToken = CancellationToken.None): Promise { + this._logService.trace(`extHostWorkspace#findFiles2New: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2New`); + return this._findFilesImpl(undefined, filePatterns, options, token); } private async _findFilesImpl( // the old `findFiles` used `include` to query, but the new `findFiles2` uses `filePattern` to query. // `filePattern` is the proper way to handle this, since it takes less precedence than the ignore files. include: vscode.GlobPattern | undefined, - filePattern: vscode.GlobPattern | undefined, - options: vscode.FindFiles2Options, + filePatterns: vscode.GlobPattern[] | undefined, + options: vscode.FindFiles2OptionsNew, token: vscode.CancellationToken = CancellationToken.None): Promise { if (token && token.isCancellationRequested) { return Promise.resolve([]); } - const excludePattern = (typeof options.exclude === 'string') ? options.exclude : - options.exclude ? options.exclude.pattern : undefined; - const fileQueries: IFileQueryBuilderOptions = { - ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, - disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, - disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, - disregardParentIgnoreFiles: typeof options.useParentIgnoreFiles === 'boolean' ? !options.useParentIgnoreFiles : undefined, - disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : false, - disregardSearchExcludeSettings: typeof options.useDefaultSearchExcludes === 'boolean' ? !options.useDefaultSearchExcludes : false, - maxResults: options.maxResults, - excludePattern: excludePattern, - shouldGlobSearch: typeof options.fuzzy === 'boolean' ? !options.fuzzy : true, - _reason: 'startFileSearch' - }; - let folderToUse: URI | undefined; - if (include) { - const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include)); - folderToUse = folder; - fileQueries.includePattern = includePattern; - } else { - const { includePattern, folder } = parseSearchInclude(GlobPattern.from(filePattern)); - folderToUse = folder; - fileQueries.filePattern = includePattern; - } + const filePatternsToUse = include !== undefined ? [include] : filePatterns; + const queryOptions: QueryOptions[] = filePatternsToUse?.map(filePattern => { + + const excludePatterns = globsToISearchPatternBuilder(options.exclude); - return this._proxy.$startFileSearch( - folderToUse ?? null, - fileQueries, - token - ) - .then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []); + const fileQueries: IFileQueryBuilderOptions = { + ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, + disregardIgnoreFiles: typeof options.useIgnoreFiles?.local === 'boolean' ? !options.useIgnoreFiles.local : undefined, + disregardGlobalIgnoreFiles: typeof options.useIgnoreFiles?.global === 'boolean' ? !options.useIgnoreFiles.global : undefined, + disregardParentIgnoreFiles: typeof options.useIgnoreFiles?.parent === 'boolean' ? !options.useIgnoreFiles.parent : undefined, + disregardExcludeSettings: options.useExcludeSettings !== undefined && options.useExcludeSettings === ExcludeSettingOptions.None, + disregardSearchExcludeSettings: options.useExcludeSettings !== undefined && (options.useExcludeSettings !== ExcludeSettingOptions.SearchAndFilesExclude), + maxResults: options.maxResults, + excludePattern: excludePatterns.length > 0 ? excludePatterns : undefined, + _reason: 'startFileSearch', + shouldGlobSearch: include ? undefined : true, + }; + + const parseInclude = parseSearchExcludeInclude(GlobPattern.from(filePattern)); + const folderToUse = parseInclude?.folder; + if (include) { + fileQueries.includePattern = parseInclude?.pattern; + } else { + fileQueries.filePattern = parseInclude?.pattern; + } + return { + folder: folderToUse, + options: fileQueries + }; + }) ?? []; + + return this._findFilesBase(queryOptions, token); } - async findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { - this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles`); + private async _findFilesBase( + queryOptions: QueryOptions[] | undefined, + token: CancellationToken + ): Promise { + const result = await Promise.all(queryOptions?.map(option => this._proxy.$startFileSearch( + option.folder ?? null, + option.options, + token).then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []) + ) ?? []); - const requestId = this._requestIdProvider.getNext(); + return result.flat(); + } - const previewOptions: vscode.TextSearchPreviewOptions = typeof options.previewOptions === 'undefined' ? - { - matchLines: 100, - charsPerLine: 10000 - } : - options.previewOptions; + findTextInFilesNew(query: vscode.TextSearchQueryNew, extensionId: ExtensionIdentifier, options?: vscode.FindTextInFilesOptionsNew, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse { + this._logService.trace(`extHostWorkspace#findTextInFilesNew: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFilesNew`); - const { includePattern, folder } = parseSearchInclude(GlobPattern.from(options.include)); - const excludePattern = (typeof options.exclude === 'string') ? options.exclude : - options.exclude ? options.exclude.pattern : undefined; - const queryOptions: ITextQueryBuilderOptions = { - ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, - disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, - disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, - disregardParentIgnoreFiles: typeof options.useParentIgnoreFiles === 'boolean' ? !options.useParentIgnoreFiles : undefined, - disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : true, - fileEncoding: options.encoding, - maxResults: options.maxResults, - previewOptions, - afterContext: options.afterContext, - beforeContext: options.beforeContext, - includePattern: includePattern, - excludePattern: excludePattern + const getOptions = (include: vscode.GlobPattern | undefined): QueryOptions => { + if (!options) { + return { + folder: undefined, + options: {} + }; + } + const parsedInclude = include ? parseSearchExcludeInclude(GlobPattern.from(include)) : undefined; + + const excludePatterns = globsToISearchPatternBuilder(options.exclude); + + return { + options: { + + ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, + disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, + disregardGlobalIgnoreFiles: typeof options.useIgnoreFiles?.global === 'boolean' ? !options.useIgnoreFiles?.global : undefined, + disregardParentIgnoreFiles: typeof options.useIgnoreFiles?.parent === 'boolean' ? !options.useIgnoreFiles?.parent : undefined, + disregardExcludeSettings: options.useExcludeSettings !== undefined && options.useExcludeSettings === ExcludeSettingOptions.None, + disregardSearchExcludeSettings: options.useExcludeSettings !== undefined && (options.useExcludeSettings !== ExcludeSettingOptions.SearchAndFilesExclude), + fileEncoding: options.encoding, + maxResults: options.maxResults, + previewOptions: options.previewOptions ? { + matchLines: options.previewOptions?.numMatchLines ?? 100, + charsPerLine: options.previewOptions?.charsPerLine ?? 10000, + } : undefined, + surroundingContext: options.surroundingContext, + + includePattern: parsedInclude?.pattern, + excludePattern: excludePatterns + } satisfies ITextQueryBuilderOptions, + folder: parsedInclude?.folder + } satisfies QueryOptions; }; - const isCanceled = false; + const queryOptionsRaw: (QueryOptions | undefined)[] = ((options?.include?.map((include) => + getOptions(include)))) ?? [getOptions(undefined)]; + + const queryOptions = queryOptionsRaw.filter((queryOps): queryOps is QueryOptions => !!queryOps); + + const complete: Promise = Promise.resolve(undefined); + + const asyncIterable = new AsyncIterableObject(async emitter => { + const progress = (result: ITextSearchResult, uri: URI) => { + if (resultIsMatch(result)) { + emitter.emitOne(new TextSearchMatchNew( + uri, + result.rangeLocations.map((range) => ({ + previewRange: new Range(range.preview.startLineNumber, range.preview.startColumn, range.preview.endLineNumber, range.preview.endColumn), + sourceRange: new Range(range.source.startLineNumber, range.source.startColumn, range.source.endLineNumber, range.source.endColumn) + })), + result.previewText + + )); + } else { + emitter.emitOne(new TextSearchContextNew( + uri, + result.text, + result.lineNumber + )); + + } + return result; + }; + + await complete.then(e => { + return this.findTextInFilesBase( + query, + queryOptions, + progress, + token + ); + }); + }); + + return { + results: asyncIterable, + complete: complete.then((e) => { + return { + limitHit: e?.limitHit ?? false + }; + }), + }; + } + + + async findTextInFilesBase(query: vscode.TextSearchQuery, queryOptions: QueryOptions[] | undefined, callback: (result: ITextSearchResult, uri: URI) => void, token: vscode.CancellationToken = CancellationToken.None): Promise { + const requestId = this._requestIdProvider.getNext(); + + let isCanceled = false; + token.onCancellationRequested(_ => { + isCanceled = true; + }); this._activeSearchCallbacks[requestId] = p => { if (isCanceled) { @@ -560,26 +677,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac const uri = URI.revive(p.resource); p.results!.forEach(rawResult => { const result: ITextSearchResult = revive(rawResult); - if (resultIsMatch(result)) { - callback({ - uri, - preview: { - text: result.preview.text, - matches: mapArrayOrNot( - result.preview.matches, - m => new Range(m.startLineNumber, m.startColumn, m.endLineNumber, m.endColumn)) - }, - ranges: mapArrayOrNot( - result.ranges, - r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn)) - } satisfies vscode.TextSearchMatch); - } else { - callback({ - uri, - text: result.text, - lineNumber: result.lineNumber - } satisfies vscode.TextSearchContext); - } + callback(result, uri); }); }; @@ -588,20 +686,83 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } try { - const result = await this._proxy.$startTextSearch( + const result = await Promise.all(queryOptions?.map(option => this._proxy.$startTextSearch( query, - folder ?? null, - queryOptions, + option.folder ?? null, + option.options, requestId, - token); + token) || {} + ) ?? []); delete this._activeSearchCallbacks[requestId]; - return result || {}; + return result.reduce((acc, val) => { + return { + limitHit: acc?.limitHit || (val?.limitHit ?? false), + message: [acc?.message ?? [], val?.message ?? []].flat(), + }; + }, {}) ?? { limitHit: false }; + } catch (err) { delete this._activeSearchCallbacks[requestId]; throw err; } } + async findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions & { useSearchExclude?: boolean }, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { + this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles`); + + const previewOptions: vscode.TextSearchPreviewOptions = typeof options.previewOptions === 'undefined' ? + { + matchLines: 100, + charsPerLine: 10000 + } : + options.previewOptions; + + const parsedInclude = parseSearchExcludeInclude(GlobPattern.from(options.include)); + + const excludePattern = (typeof options.exclude === 'string') ? options.exclude : + options.exclude ? options.exclude.pattern : undefined; + const queryOptions: ITextQueryBuilderOptions = { + ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, + disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, + disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, + disregardParentIgnoreFiles: typeof options.useParentIgnoreFiles === 'boolean' ? !options.useParentIgnoreFiles : undefined, + disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : true, + disregardSearchExcludeSettings: typeof options.useSearchExclude === 'boolean' ? !options.useSearchExclude : true, + fileEncoding: options.encoding, + maxResults: options.maxResults, + previewOptions, + surroundingContext: options.afterContext, // TODO: remove ability to have before/after context separately + + includePattern: parsedInclude?.pattern, + excludePattern: excludePattern ? [{ pattern: excludePattern }] : undefined, + }; + + const progress = (result: ITextSearchResult, uri: URI) => { + if (resultIsMatch(result)) { + callback({ + uri, + preview: { + text: result.previewText, + matches: mapArrayOrNot( + result.rangeLocations, + m => new Range(m.preview.startLineNumber, m.preview.startColumn, m.preview.endLineNumber, m.preview.endColumn)) + }, + ranges: mapArrayOrNot( + result.rangeLocations, + r => new Range(r.source.startLineNumber, r.source.startColumn, r.source.endLineNumber, r.source.endColumn)) + } satisfies vscode.TextSearchMatch); + } else { + callback({ + uri, + text: result.text, + lineNumber: result.lineNumber + } satisfies vscode.TextSearchContext); + } + }; + + return this.findTextInFilesBase(query, [{ options: queryOptions, folder: parsedInclude?.folder }], progress, token); + } + $handleTextSearchResult(result: IRawFileMatch2, requestId: number): void { this._activeSearchCallbacks[requestId]?.(result); } @@ -626,6 +787,14 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac return this._proxy.$resolveProxy(url); } + lookupAuthorization(authInfo: AuthInfo): Promise { + return this._proxy.$lookupAuthorization(authInfo); + } + + lookupKerberosAuthorization(url: string): Promise { + return this._proxy.$lookupKerberosAuthorization(url); + } + loadCertificates(): Promise { return this._proxy.$loadCertificates(); } @@ -794,22 +963,23 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac export const IExtHostWorkspace = createDecorator('IExtHostWorkspace'); export interface IExtHostWorkspace extends ExtHostWorkspace, ExtHostWorkspaceShape, IExtHostWorkspaceProvider { } -function parseSearchInclude(include: string | IRelativePatternDto | undefined | null): { includePattern?: string; folder?: URI } { - let includePattern: string | undefined; +function parseSearchExcludeInclude(include: string | IRelativePatternDto | undefined | null): { pattern: string; folder?: URI } | undefined { + let pattern: string | undefined; let includeFolder: URI | undefined; if (include) { if (typeof include === 'string') { - includePattern = include; + pattern = include; } else { - includePattern = include.pattern; + pattern = include.pattern; includeFolder = URI.revive(include.baseUri); } - } - return { - includePattern, - folder: includeFolder - }; + return { + pattern, + folder: includeFolder + }; + } + return undefined; } interface IExtensionListener { @@ -817,3 +987,27 @@ interface IExtensionListener { (e: E): any; } +function globsToISearchPatternBuilder(excludes: vscode.GlobPattern[] | undefined): ISearchPatternBuilder[] { + return ( + excludes?.map((exclude): ISearchPatternBuilder | undefined => { + if (typeof exclude === 'string') { + if (exclude === '') { + return undefined; + } + return { + pattern: exclude, + uri: undefined + } satisfies ISearchPatternBuilder; + } else { + const parsedExclude = parseSearchExcludeInclude(exclude); + if (!parsedExclude) { + return undefined; + } + return { + pattern: parsedExclude.pattern, + uri: parsedExclude.folder + } satisfies ISearchPatternBuilder; + } + }) ?? [] + ).filter((e): e is ISearchPatternBuilder => !!e); +} diff --git a/patched-vscode/src/vs/workbench/api/common/extensionHostMain.ts b/patched-vscode/src/vs/workbench/api/common/extensionHostMain.ts index 2bd275cb..50c47ba9 100644 --- a/patched-vscode/src/vs/workbench/api/common/extensionHostMain.ts +++ b/patched-vscode/src/vs/workbench/api/common/extensionHostMain.ts @@ -11,7 +11,7 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; import { IExtensionHostInitData } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -195,7 +195,7 @@ export class ExtensionHostMain { private static _transform(initData: IExtensionHostInitData, rpcProtocol: RPCProtocol): IExtensionHostInitData { initData.extensions.allExtensions.forEach((ext) => { - (>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)); + (>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)); }); initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); const extDevLocs = initData.environment.extensionDevelopmentLocationURI; diff --git a/patched-vscode/src/vs/workbench/api/node/extHostDebugService.ts b/patched-vscode/src/vs/workbench/api/node/extHostDebugService.ts index fd03fd9e..dc48354e 100644 --- a/patched-vscode/src/vs/workbench/api/node/extHostDebugService.ts +++ b/patched-vscode/src/vs/workbench/api/node/extHostDebugService.ts @@ -27,6 +27,7 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c import type * as vscode from 'vscode'; import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; export class ExtHostDebugService extends ExtHostDebugServiceBase { @@ -44,8 +45,9 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { @IExtHostEditorTabs editorTabs: IExtHostEditorTabs, @IExtHostVariableResolverProvider variableResolver: IExtHostVariableResolverProvider, @IExtHostCommands commands: IExtHostCommands, + @IExtHostTesting testing: IExtHostTesting, ) { - super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands); + super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands, testing); } protected override createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined { @@ -78,9 +80,9 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { if (!this._terminalDisposedListener) { // React on terminal disposed and check if that is the debug terminal #12956 - this._terminalDisposedListener = this._terminalService.onDidCloseTerminal(terminal => { + this._terminalDisposedListener = this._register(this._terminalService.onDidCloseTerminal(terminal => { this._integratedTerminalInstances.onTerminalClosed(terminal); - }); + })); } const configProvider = await this._configurationService.getConfigProvider(); diff --git a/patched-vscode/src/vs/workbench/api/node/extHostExtensionService.ts b/patched-vscode/src/vs/workbench/api/node/extHostExtensionService.ts index 5ac0ddc7..4d17df38 100644 --- a/patched-vscode/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/patched-vscode/src/vs/workbench/api/node/extHostExtensionService.ts @@ -18,6 +18,10 @@ import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { realpathSync } from 'vs/base/node/extpath'; import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder'; import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider'; +// ESM-uncomment-begin +// import { createRequire } from 'node:module'; +// const require = createRequire(import.meta.url); +// ESM-uncomment-end class NodeModuleRequireInterceptor extends RequireInterceptor { @@ -109,7 +113,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { if (extensionId) { performance.mark(`code/extHost/willLoadExtensionCode/${extensionId}`); } - r = require.__$__nodeRequire(module.fsPath); + r = (require.__$__nodeRequire ?? require /* TODO@esm drop the first */)(module.fsPath); } finally { if (extensionId) { performance.mark(`code/extHost/didLoadExtensionCode/${extensionId}`); diff --git a/patched-vscode/src/vs/workbench/api/node/extHostSearch.ts b/patched-vscode/src/vs/workbench/api/node/extHostSearch.ts index 99f5d761..573aeb08 100644 --- a/patched-vscode/src/vs/workbench/api/node/extHostSearch.ts +++ b/patched-vscode/src/vs/workbench/api/node/extHostSearch.ts @@ -8,6 +8,7 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { ILogService } from 'vs/platform/log/common/log'; +import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostSearch, reviveQuery } from 'vs/workbench/api/common/extHostSearch'; @@ -29,24 +30,59 @@ export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { private _registeredEHSearchProvider = false; + private _numThreadsPromise: Promise | undefined; + private readonly _disposables = new DisposableStore(); + private isDisposed = false; + constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, @IURITransformerService _uriTransformer: IURITransformerService, + @IExtHostConfiguration private readonly configurationService: IExtHostConfiguration, @ILogService _logService: ILogService, ) { super(extHostRpc, _uriTransformer, _logService); - + this.getNumThreads = this.getNumThreads.bind(this); + this.getNumThreadsCached = this.getNumThreadsCached.bind(this); + this.handleConfigurationChanged = this.handleConfigurationChanged.bind(this); const outputChannel = new OutputChannel('RipgrepSearchUD', this._logService); - this._disposables.add(this.registerTextSearchProvider(Schemas.vscodeUserData, new RipgrepSearchProvider(outputChannel))); + this._disposables.add(this.registerTextSearchProvider(Schemas.vscodeUserData, new RipgrepSearchProvider(outputChannel, this.getNumThreadsCached))); if (initData.remote.isRemote && initData.remote.authority) { this._registerEHSearchProviders(); } + + configurationService.getConfigProvider().then(provider => { + if (this.isDisposed) { + return; + } + this._disposables.add(provider.onDidChangeConfiguration(this.handleConfigurationChanged)); + }); + } + + private handleConfigurationChanged(event: vscode.ConfigurationChangeEvent) { + if (!event.affectsConfiguration('search')) { + return; + } + this._numThreadsPromise = undefined; + } + + async getNumThreads(): Promise { + const configProvider = await this.configurationService.getConfigProvider(); + const numThreads = configProvider.getConfiguration('search').get('ripgrep.maxThreads'); + return numThreads; + } + + async getNumThreadsCached(): Promise { + if (!this._numThreadsPromise) { + this._numThreadsPromise = this.getNumThreads(); + } + return this._numThreadsPromise; } dispose(): void { + this.isDisposed = true; this._disposables.dispose(); } @@ -61,8 +97,8 @@ export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { this._registeredEHSearchProvider = true; const outputChannel = new OutputChannel('RipgrepSearchEH', this._logService); - this._disposables.add(this.registerTextSearchProvider(Schemas.file, new RipgrepSearchProvider(outputChannel))); - this._disposables.add(this.registerInternalFileSearchProvider(Schemas.file, new SearchService('fileSearchProvider'))); + this._disposables.add(this.registerTextSearchProvider(Schemas.file, new RipgrepSearchProvider(outputChannel, this.getNumThreadsCached))); + this._disposables.add(this.registerInternalFileSearchProvider(Schemas.file, new SearchService('fileSearchProvider', this.getNumThreadsCached))); } private registerInternalFileSearchProvider(scheme: string, provider: SearchService): IDisposable { @@ -90,7 +126,7 @@ export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { return super.$provideFileSearchResults(handle, session, rawQuery, token); } - override doInternalFileSearchWithCustomCallback(rawQuery: IFileQuery, token: vscode.CancellationToken, handleFileMatch: (data: URI[]) => void): Promise { + override async doInternalFileSearchWithCustomCallback(rawQuery: IFileQuery, token: vscode.CancellationToken, handleFileMatch: (data: URI[]) => void): Promise { const onResult = (ev: ISerializedSearchProgressItem) => { if (isSerializedFileMatch(ev)) { ev = [ev]; @@ -109,8 +145,8 @@ export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { if (!this._internalFileSearchProvider) { throw new Error('No internal file search handler'); } - - return >this._internalFileSearchProvider.doFileSearch(rawQuery, onResult, token); + const numThreads = await this.getNumThreadsCached(); + return >this._internalFileSearchProvider.doFileSearch(rawQuery, numThreads, onResult, token); } private async doInternalFileSearch(handle: number, session: number, rawQuery: IFileQuery, token: vscode.CancellationToken): Promise { @@ -125,7 +161,7 @@ export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { return super.$clearCache(cacheKey); } - protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { return new NativeTextSearchManager(query, provider, undefined, 'textSearchProvider'); } } diff --git a/patched-vscode/src/vs/workbench/api/node/extHostStoragePaths.ts b/patched-vscode/src/vs/workbench/api/node/extHostStoragePaths.ts index 8348d949..e259b3b0 100644 --- a/patched-vscode/src/vs/workbench/api/node/extHostStoragePaths.ts +++ b/patched-vscode/src/vs/workbench/api/node/extHostStoragePaths.ts @@ -69,14 +69,14 @@ export class ExtensionStoragePaths extends CommonExtensionStoragePaths { async function mkdir(dir: string): Promise { try { - await Promises.stat(dir); + await fs.promises.stat(dir); return; } catch { // doesn't exist, that's OK } try { - await Promises.mkdir(dir, { recursive: true }); + await fs.promises.mkdir(dir, { recursive: true }); } catch { } } @@ -103,7 +103,7 @@ class Lock extends Disposable { this._timer.cancel(); } try { - await Promises.utimes(filename, new Date(), new Date()); + await fs.promises.utimes(filename, new Date(), new Date()); } catch (err) { logService.error(err); logService.info(`Lock '${filename}': Could not update mtime.`); @@ -174,7 +174,7 @@ interface ILockfileContents { async function readLockfileContents(logService: ILogService, filename: string): Promise { let contents: Buffer; try { - contents = await Promises.readFile(filename); + contents = await fs.promises.readFile(filename); } catch (err) { // cannot read the file logService.error(err); @@ -196,7 +196,7 @@ async function readLockfileContents(logService: ILogService, filename: string): async function readmtime(logService: ILogService, filename: string): Promise { let stats: fs.Stats; try { - stats = await Promises.stat(filename); + stats = await fs.promises.stat(filename); } catch (err) { // cannot read the file stats to check if it is stale or not logService.error(err); @@ -279,7 +279,7 @@ async function checkStaleAndTryAcquireLock(logService: ILogService, filename: st async function tryDeleteAndAcquireLock(logService: ILogService, filename: string): Promise { logService.info(`Lock '${filename}': Deleting a stale lock.`); try { - await Promises.unlink(filename); + await fs.promises.unlink(filename); } catch (err) { // cannot delete the file // maybe the file is already deleted diff --git a/patched-vscode/src/vs/workbench/api/node/extHostTunnelService.ts b/patched-vscode/src/vs/workbench/api/node/extHostTunnelService.ts index 13498806..54b83109 100644 --- a/patched-vscode/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/patched-vscode/src/vs/workbench/api/node/extHostTunnelService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import { exec } from 'child_process'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; @@ -243,8 +244,8 @@ export class NodeExtHostTunnelService extends ExtHostTunnelService { let tcp: string = ''; let tcp6: string = ''; try { - tcp = await pfs.Promises.readFile('/proc/net/tcp', 'utf8'); - tcp6 = await pfs.Promises.readFile('/proc/net/tcp6', 'utf8'); + tcp = await fs.promises.readFile('/proc/net/tcp', 'utf8'); + tcp6 = await fs.promises.readFile('/proc/net/tcp6', 'utf8'); } catch (e) { // File reading error. No additional handling needed. } @@ -265,10 +266,10 @@ export class NodeExtHostTunnelService extends ExtHostTunnelService { try { const pid: number = Number(childName); const childUri = resources.joinPath(URI.file('/proc'), childName); - const childStat = await pfs.Promises.stat(childUri.fsPath); + const childStat = await fs.promises.stat(childUri.fsPath); if (childStat.isDirectory() && !isNaN(pid)) { - const cwd = await pfs.Promises.readlink(resources.joinPath(childUri, 'cwd').fsPath); - const cmd = await pfs.Promises.readFile(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8'); + const cwd = await fs.promises.readlink(resources.joinPath(childUri, 'cwd').fsPath); + const cmd = await fs.promises.readFile(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8'); processes.push({ pid, cwd, cmd }); } } catch (e) { diff --git a/patched-vscode/src/vs/workbench/api/node/extensionHostProcess.ts b/patched-vscode/src/vs/workbench/api/node/extensionHostProcess.ts index 785db7ed..80a60c1d 100644 --- a/patched-vscode/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/patched-vscode/src/vs/workbench/api/node/extensionHostProcess.ts @@ -3,29 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import minimist from 'minimist'; import * as nativeWatchdog from 'native-watchdog'; import * as net from 'net'; -import * as minimist from 'minimist'; -import * as performance from 'vs/base/common/performance'; -import type { MessagePortMain } from 'vs/base/parts/sandbox/node/electronTypes'; +import { ProcessTimeRunOnceScheduler } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; import { isCancellationError, isSigPipeError, onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; +import * as performance from 'vs/base/common/performance'; +import { IURITransformer } from 'vs/base/common/uriIpc'; +import { realpath } from 'vs/base/node/extpath'; +import { Promises } from 'vs/base/node/pfs'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { PersistentProtocol, ProtocolConstants, BufferedEmitter } from 'vs/base/parts/ipc/common/ipc.net'; +import { BufferedEmitter, PersistentProtocol, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import type { MessagePortMain } from 'vs/base/parts/sandbox/node/electronTypes'; +import { boolean } from 'vs/editor/common/config/editorOptions'; import product from 'vs/platform/product/common/product'; -import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage, IExtHostReduceGraceTimeMessage, ExtensionHostExitCode, IExtensionHostInitData } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionHostMain, IExitFn } from 'vs/workbench/api/common/extensionHostMain'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { IURITransformer } from 'vs/base/common/uriIpc'; -import { Promises } from 'vs/base/node/pfs'; -import { realpath } from 'vs/base/node/extpath'; import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; -import { ProcessTimeRunOnceScheduler } from 'vs/base/common/async'; -import { boolean } from 'vs/editor/common/config/editorOptions'; import { createURITransformer } from 'vs/workbench/api/node/uriTransformer'; import { ExtHostConnectionType, readExtHostConnection } from 'vs/workbench/services/extensions/common/extensionHostEnv'; +import { ExtensionHostExitCode, IExtHostReadyMessage, IExtHostReduceGraceTimeMessage, IExtHostSocketMessage, IExtensionHostInitData, MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { IDisposable } from 'vs/base/common/lifecycle'; import 'vs/workbench/api/common/extHost.common.services'; import 'vs/workbench/api/node/extHost.node.services'; @@ -251,12 +252,14 @@ async function createExtHostProtocol(): Promise { readonly onMessage: Event = this._onMessage.event; private _terminating: boolean; + private _protocolListener: IDisposable; constructor() { this._terminating = false; - protocol.onMessage((msg) => { + this._protocolListener = protocol.onMessage((msg) => { if (isMessageOfType(msg, MessageType.Terminate)) { this._terminating = true; + this._protocolListener.dispose(); onTerminate('received terminate message from renderer'); } else { this._onMessage.fire(msg); diff --git a/patched-vscode/src/vs/workbench/api/node/proxyResolver.ts b/patched-vscode/src/vs/workbench/api/node/proxyResolver.ts index 519924ee..e6f9cf77 100644 --- a/patched-vscode/src/vs/workbench/api/node/proxyResolver.ts +++ b/patched-vscode/src/vs/workbench/api/node/proxyResolver.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// ESM-comment-begin import * as http from 'http'; import * as https from 'https'; import * as tls from 'tls'; import * as net from 'net'; +// ESM-comment-end import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; @@ -17,6 +19,16 @@ import { URI } from 'vs/base/common/uri'; import { ILogService, LogLevel as LogServiceLevel } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { LogLevel, createHttpPatch, createProxyResolver, createTlsPatch, ProxySupportSetting, ProxyAgentParams, createNetPatch, loadSystemCertificates } from '@vscode/proxy-agent'; +import { AuthInfo } from 'vs/platform/request/common/request'; + +// ESM-uncomment-begin +// import { createRequire } from 'node:module'; +// const require = createRequire(import.meta.url); +// const http = require('http'); +// const https = require('https'); +// const tls = require('tls'); +// const net = require('net'); +// ESM-uncomment-end const systemCertificatesV2Default = false; @@ -32,9 +44,10 @@ export function connectProxyResolver( const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; const params: ProxyAgentParams = { resolveProxy: url => extHostWorkspace.resolveProxy(url), - lookupProxyAuthorization: lookupProxyAuthorization.bind(undefined, extHostLogService, mainThreadTelemetry, configProvider, {}, initData.remote.isRemote), + lookupProxyAuthorization: lookupProxyAuthorization.bind(undefined, extHostWorkspace, extHostLogService, mainThreadTelemetry, configProvider, {}, {}, initData.remote.isRemote, doUseHostProxy), getProxyURL: () => configProvider.getConfiguration('http').get('proxy'), getProxySupport: () => configProvider.getConfiguration('http').get('proxySupport') || 'off', + getNoProxyConfig: () => configProvider.getConfiguration('http').get('noProxy') || [], addCertificatesV1: () => certSettingV1(configProvider), addCertificatesV2: () => certSettingV2(configProvider), log: extHostLogService, @@ -67,6 +80,11 @@ export function connectProxyResolver( certs.then(certs => extHostLogService.trace('ProxyResolver#loadAdditionalCertificates: Loaded certificates from main process', certs.length)); promises.push(certs); } + // Using https.globalAgent because it is shared with proxy.test.ts and mutable. + if (initData.environment.extensionTestsLocationURI && (https.globalAgent as any).testCertificates?.length) { + extHostLogService.trace('ProxyResolver#loadAdditionalCertificates: Loading test certificates'); + promises.push(Promise.resolve((https.globalAgent as any).testCertificates as string[])); + } return (await Promise.all(promises)).flat(); }, env: process.env, @@ -77,11 +95,16 @@ export function connectProxyResolver( } function createPatchedModules(params: ProxyAgentParams, resolveProxy: ReturnType) { + + function mergeModules(module: any, patch: any) { + return Object.assign(module.default || module, patch); + } + return { - http: Object.assign(http, createHttpPatch(params, http, resolveProxy)), - https: Object.assign(https, createHttpPatch(params, https, resolveProxy)), - net: Object.assign(net, createNetPatch(params, net)), - tls: Object.assign(tls, createTlsPatch(params, tls)) + http: mergeModules(http, createHttpPatch(params, http, resolveProxy)), + https: mergeModules(https, createHttpPatch(params, https, resolveProxy)), + net: mergeModules(net, createNetPatch(params, net)), + tls: mergeModules(tls, createTlsPatch(params, tls)) }; } @@ -129,14 +152,17 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku } async function lookupProxyAuthorization( + extHostWorkspace: IExtHostWorkspaceProvider, extHostLogService: ILogService, mainThreadTelemetry: MainThreadTelemetryShape, configProvider: ExtHostConfigProvider, proxyAuthenticateCache: Record, + basicAuthCache: Record, isRemote: boolean, + useHostProxy: boolean, proxyURL: string, proxyAuthenticate: string | string[] | undefined, - state: { kerberosRequested?: boolean } + state: { kerberosRequested?: boolean; basicAuthCacheUsed?: boolean; basicAuthAttempt?: number } ): Promise { const cached = proxyAuthenticateCache[proxyURL]; if (proxyAuthenticate) { @@ -147,8 +173,9 @@ async function lookupProxyAuthorization( const authenticate = Array.isArray(header) ? header : typeof header === 'string' ? [header] : []; sendTelemetry(mainThreadTelemetry, authenticate, isRemote); if (authenticate.some(a => /^(Negotiate|Kerberos)( |$)/i.test(a)) && !state.kerberosRequested) { + state.kerberosRequested = true; + try { - state.kerberosRequested = true; const kerberos = await import('kerberos'); const url = new URL(proxyURL); const spn = configProvider.getConfiguration('http').get('proxyKerberosServicePrincipal') @@ -158,7 +185,54 @@ async function lookupProxyAuthorization( const response = await client.step(''); return 'Negotiate ' + response; } catch (err) { - extHostLogService.error('ProxyResolver#lookupProxyAuthorization Kerberos authentication failed', err); + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Kerberos authentication failed', err); + } + + if (isRemote && useHostProxy) { + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Kerberos authentication lookup on host', `proxyURL:${proxyURL}`); + const auth = await extHostWorkspace.lookupKerberosAuthorization(proxyURL); + if (auth) { + return 'Negotiate ' + auth; + } + } + } + const basicAuthHeader = authenticate.find(a => /^Basic( |$)/i.test(a)); + if (basicAuthHeader) { + try { + const cachedAuth = basicAuthCache[proxyURL]; + if (cachedAuth) { + if (state.basicAuthCacheUsed) { + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Basic authentication deleting cached credentials', `proxyURL:${proxyURL}`); + delete basicAuthCache[proxyURL]; + } else { + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Basic authentication using cached credentials', `proxyURL:${proxyURL}`); + state.basicAuthCacheUsed = true; + return cachedAuth; + } + } + state.basicAuthAttempt = (state.basicAuthAttempt || 0) + 1; + const realm = / realm="([^"]+)"/i.exec(basicAuthHeader)?.[1]; + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Basic authentication lookup', `proxyURL:${proxyURL}`, `realm:${realm}`); + const url = new URL(proxyURL); + const authInfo: AuthInfo = { + scheme: 'basic', + host: url.hostname, + port: Number(url.port), + realm: realm || '', + isProxy: true, + attempt: state.basicAuthAttempt, + }; + const credentials = await extHostWorkspace.lookupAuthorization(authInfo); + if (credentials) { + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Basic authentication received credentials', `proxyURL:${proxyURL}`, `realm:${realm}`); + const auth = 'Basic ' + Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64'); + basicAuthCache[proxyURL] = auth; + return auth; + } else { + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Basic authentication received no credentials', `proxyURL:${proxyURL}`, `realm:${realm}`); + } + } catch (err) { + extHostLogService.error('ProxyResolver#lookupProxyAuthorization Basic authentication failed', err); } } return undefined; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHost.api.impl.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHost.api.impl.test.ts index 35e7ff35..d5db3ca4 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHost.api.impl.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHost.api.impl.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { originalFSPath } from 'vs/base/common/resources'; import { isWindows } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index b49d05ed..da5e8bb9 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -17,7 +17,7 @@ import 'vs/editor/contrib/suggest/browser/suggest'; import 'vs/editor/contrib/rename/browser/rename'; import 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; -import * as assert from 'assert'; +import assert from 'assert'; import { setUnexpectedErrorHandler, errorHandler } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; @@ -1275,6 +1275,22 @@ suite('ExtHostLanguageFeatureCommands', function () { }); + testApiCmd('DocumentLink[] vscode.executeLinkProvider returns lack tooltip #213970', async function () { + disposables.push(extHost.registerDocumentLinkProvider(nullExtensionDescription, defaultSelector, { + provideDocumentLinks(): any { + const link = new types.DocumentLink(new types.Range(0, 0, 0, 20), URI.parse('foo:bar')); + link.tooltip = 'Link Tooltip'; + return [link]; + } + })); + + await rpcProtocol.sync(); + + const links1 = await commands.executeCommand('vscode.executeLinkProvider', model.uri); + assert.strictEqual(links1.length, 1); + assert.strictEqual(links1[0].tooltip, 'Link Tooltip'); + }); + test('Color provider', function () { diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts index dd77886b..de02ffff 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts index 045f3d77..be11d3a7 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostBulkEdits.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { MainContext, IWorkspaceEditDto, MainThreadBulkEditsShape, IWorkspaceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostCommands.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostCommands.test.ts index 5697ffe7..5353ca9c 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostCommands.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostCommands.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { MainThreadCommandsShape } from 'vs/workbench/api/common/extHost.protocol'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts index ef43b937..298299b3 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostDecorations.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostDecorations.test.ts index 26b419f6..8dca84bc 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostDecorations.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostDecorations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts index 6c6de8c7..2c866dae 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI, UriComponents } from 'vs/base/common/uri'; import { DiagnosticCollection, ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { Diagnostic, DiagnosticSeverity, Range, DiagnosticRelatedInformation, Location } from 'vs/workbench/api/common/extHostTypes'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentContentProvider.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentContentProvider.test.ts index 2132482c..f4795ade 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentContentProvider.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentContentProvider.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts index 3f60f5ef..298a2811 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { Position } from 'vs/workbench/api/common/extHostTypes'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts index 632487d4..9f0a28ec 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.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 * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts index 3f4255c4..eda97538 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts index cedfaa5e..75f2ccce 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type * as vscode from 'vscode'; -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { IEditorTabDto, IEditorTabGroupDto, MainThreadEditorTabsShape, TabInputKind, TabModelOperationKind, TextInputDto } from 'vs/workbench/api/common/extHost.protocol'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts index ba5e260f..953aff7e 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.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 * as assert from 'assert'; +import assert from 'assert'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { NullLogService } from 'vs/platform/log/common/log'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts index 585e3745..ec934301 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { setUnexpectedErrorHandler, errorHandler } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; @@ -301,7 +301,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, new EditorPosition(1, 1), CancellationToken.None); + const value = await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, new EditorPosition(1, 1), false, CancellationToken.None); assert.strictEqual(value.length, 1); const [entry] = value; assert.deepStrictEqual(entry.range, { startLineNumber: 2, startColumn: 3, endLineNumber: 4, endColumn: 5 }); @@ -322,7 +322,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, new EditorPosition(1, 1), CancellationToken.None); + const value = await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, new EditorPosition(1, 1), false, CancellationToken.None); assert.strictEqual(value.length, 2); }); @@ -341,7 +341,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, new EditorPosition(1, 1), CancellationToken.None); + const value = await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, new EditorPosition(1, 1), false, CancellationToken.None); assert.strictEqual(value.length, 2); // let [first, second] = value; assert.strictEqual(value[0].uri.authority, 'second'); @@ -362,7 +362,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, new EditorPosition(1, 1), CancellationToken.None); + const value = await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, new EditorPosition(1, 1), false, CancellationToken.None); assert.strictEqual(value.length, 1); }); @@ -377,7 +377,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, new EditorPosition(1, 1), CancellationToken.None); + const value = await getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, new EditorPosition(1, 1), false, CancellationToken.None); assert.strictEqual(value.length, 1); const [entry] = value; assert.deepStrictEqual(entry.range, { startLineNumber: 2, startColumn: 3, endLineNumber: 4, endColumn: 5 }); @@ -395,7 +395,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, new EditorPosition(1, 1), CancellationToken.None); + const value = await getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, new EditorPosition(1, 1), false, CancellationToken.None); assert.strictEqual(value.length, 1); const [entry] = value; assert.deepStrictEqual(entry.range, { startLineNumber: 2, startColumn: 3, endLineNumber: 4, endColumn: 5 }); @@ -413,7 +413,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, new EditorPosition(1, 1), CancellationToken.None); + const value = await getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, new EditorPosition(1, 1), false, CancellationToken.None); assert.strictEqual(value.length, 1); const [entry] = value; assert.deepStrictEqual(entry.range, { startLineNumber: 2, startColumn: 3, endLineNumber: 4, endColumn: 5 }); @@ -517,7 +517,7 @@ suite('ExtHostLanguageFeatures', function () { disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { - return []; + return undefined; } })); disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, '*', new class implements vscode.DocumentHighlightProvider { @@ -591,7 +591,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, new EditorPosition(1, 2), false, CancellationToken.None); + const value = await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, new EditorPosition(1, 2), false, false, CancellationToken.None); assert.strictEqual(value.length, 2); const [first, second] = value; assert.strictEqual(first.uri.path, '/second'); @@ -607,7 +607,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, new EditorPosition(1, 2), false, CancellationToken.None); + const value = await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, new EditorPosition(1, 2), false, false, CancellationToken.None); assert.strictEqual(value.length, 1); const [item] = value; assert.deepStrictEqual(item.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); @@ -628,7 +628,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, new EditorPosition(1, 2), false, CancellationToken.None); + const value = await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, new EditorPosition(1, 2), false, false, CancellationToken.None); assert.strictEqual(value.length, 1); }); diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts index 465663c8..a88e5a79 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { MainThreadMessageService } from 'vs/workbench/api/browser/mainThreadMessageService'; import { IDialogService, IPrompt, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions, INotificationSource, INotificationSourceFilter, NotificationsFilter } from 'vs/platform/notification/common/notification'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostNotebook.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostNotebook.test.ts index 5a7ed7e4..49a2f011 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostNotebook.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostNotebook.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as vscode from 'vscode'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts index 5a7e6f43..a1341bad 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Barrier } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -344,4 +344,3 @@ suite('NotebookKernel', function () { assert.ok(found); }); }); - diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts index 97bfb308..54878ddb 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostTelemetry.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -64,7 +64,8 @@ suite('ExtHostTelemetry', function () { publisher: 'vscode', version: '1.0.0', engines: { vscode: '*' }, - extensionLocation: URI.parse('fake') + extensionLocation: URI.parse('fake'), + enabledApiProposals: undefined, }; const createExtHostTelemetry = () => { diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostTesting.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostTesting.test.ts index b82376cd..4a49c803 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostTesting.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostTesting.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { mock, mockObject, MockObject } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import * as editorRange from 'vs/editor/common/core/range'; -import { ExtensionIdentifier, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { NullLogService } from 'vs/platform/log/common/log'; import { MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -23,7 +23,7 @@ import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import { ExtHostTesting, TestRunCoordinator, TestRunDto, TestRunProfileImpl } from 'vs/workbench/api/common/extHostTesting'; import { ExtHostTestItemCollection, TestItemImpl } from 'vs/workbench/api/common/extHostTestItem'; import * as convert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Location, Position, Range, TestMessage, TestResultState, TestRunProfileKind, TestRunRequest as TestRunRequestImpl, TestTag } from 'vs/workbench/api/common/extHostTypes'; +import { Location, Position, Range, TestMessage, TestRunProfileKind, TestRunRequest as TestRunRequestImpl, TestTag } from 'vs/workbench/api/common/extHostTypes'; import { AnyCallRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; import { TestDiffOpType, TestItemExpandState, TestMessageType, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes'; @@ -637,7 +637,7 @@ suite('ExtHost Testing', () => { let req: TestRunRequest; let dto: TestRunDto; - const ext: IRelaxedExtensionDescription = {} as any; + const ext: IExtensionDescription = {} as any; teardown(() => { for (const { id } of c.trackers) { @@ -829,7 +829,8 @@ suite('ExtHost Testing', () => { expected: undefined, contextValue: undefined, actual: undefined, - location: convert.location.from(message1.location) + location: convert.location.from(message1.location), + stackTrace: undefined, }] ]); @@ -846,6 +847,7 @@ suite('ExtHost Testing', () => { expected: undefined, actual: undefined, location: convert.location.from({ uri: test2.uri!, range: test2.range }), + stackTrace: undefined, }] ]); @@ -864,29 +866,6 @@ suite('ExtHost Testing', () => { assert.strictEqual(proxy.$appendTestMessagesInRun.called, false); }); - test('excludes tests outside tree or explicitly excluded', () => { - const task = c.createTestRun(ext, 'ctrlId', single, { - profile: configuration, - include: [single.root.children.get('id-a')!], - exclude: [single.root.children.get('id-a')!.children.get('id-aa')!], - preserveFocus: false, - }, 'hello world', false); - - task.passed(single.root.children.get('id-a')!.children.get('id-aa')!); - task.passed(single.root.children.get('id-a')!.children.get('id-ab')!); - - assert.deepStrictEqual(proxy.$updateTestStateInRun.args.length, 1); - const args = proxy.$updateTestStateInRun.args[0]; - assert.deepStrictEqual(proxy.$updateTestStateInRun.args, [[ - args[0], - args[1], - new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), - TestResultState.Passed, - undefined, - ]]); - task.end(); - }); - test('sets state of test with identical local IDs (#131827)', () => { const testA = single.root.children.get('id-a'); const testB = single.root.children.get('id-b'); diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts index 106a30df..98bc7766 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostTextEditor.test.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 * as assert from 'assert'; +import assert from 'assert'; import { Lazy } from 'vs/base/common/lazy'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts index 3f780a03..1e442c1e 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { Emitter } from 'vs/base/common/event'; import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts index ab38b202..40a14804 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { MarkdownString, NotebookCellOutputItem, NotebookData, LanguageSelector, WorkspaceEdit } from 'vs/workbench/api/common/extHostTypeConverters'; import { isEmptyObject } from 'vs/base/common/types'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostTypes.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostTypes.test.ts index 6cef861c..3d1ff69b 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostTypes.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostTypes.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import * as types from 'vs/workbench/api/common/extHostTypes'; import { isWindows } from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostWebview.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostWebview.test.ts index b741292b..e87300ae 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostWebview.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostWebview.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index 6abca46d..d1224f44 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { basename } from 'vs/base/common/path'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -587,7 +587,7 @@ suite('ExtHostWorkspace', function () { mainThreadCalled = true; assert.strictEqual(options.includePattern, 'foo'); assert.strictEqual(_includeFolder, null); - assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.excludePattern, undefined); assert.strictEqual(options.disregardExcludeSettings, false); assert.strictEqual(options.maxResults, 10); return Promise.resolve(null); @@ -610,7 +610,7 @@ suite('ExtHostWorkspace', function () { mainThreadCalled = true; assert.strictEqual(options.includePattern, 'glob/**'); assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON()); - assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.excludePattern, undefined); assert.strictEqual(options.disregardExcludeSettings, false); return Promise.resolve(null); } @@ -640,7 +640,7 @@ suite('ExtHostWorkspace', function () { mainThreadCalled = true; assert.strictEqual(options.includePattern, 'glob/**'); assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString()); - assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.excludePattern, undefined); assert.strictEqual(options.disregardExcludeSettings, true); return Promise.resolve(null); } @@ -681,7 +681,8 @@ suite('ExtHostWorkspace', function () { override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(options.disregardExcludeSettings, false); - assert.strictEqual(options.excludePattern, 'glob/**'); // Note that the base portion is ignored, see #52651 + assert.strictEqual(options.excludePattern?.length, 1); + assert.strictEqual(options.excludePattern[0].pattern, 'glob/**'); // Note that the base portion is ignored, see #52651 return Promise.resolve(null); } }); @@ -798,7 +799,8 @@ suite('ExtHostWorkspace', function () { override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; assert.strictEqual(options.disregardExcludeSettings, false); - assert.strictEqual(options.excludePattern, 'glob/**'); // Note that the base portion is ignored, see #52651 + assert.strictEqual(options.excludePattern?.length, 1); + assert.strictEqual(options.excludePattern[0].pattern, 'glob/**'); // Note that the base portion is ignored, see #52651 return Promise.resolve(null); } }); @@ -941,7 +943,8 @@ suite('ExtHostWorkspace', function () { assert.strictEqual(query.pattern, 'foo'); assert.deepStrictEqual(folder, null); assert.strictEqual(options.includePattern, undefined); - assert.strictEqual(options.excludePattern, 'glob/**'); // exclude folder is ignored... + assert.strictEqual(options.excludePattern?.length, 1); + assert.strictEqual(options.excludePattern[0].pattern, 'glob/**'); // exclude folder is ignored... return null; } }); diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadBulkEdits.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadBulkEdits.test.ts index 959705f2..c6ef6939 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadBulkEdits.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadBulkEdits.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IWorkspaceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { mock } from 'vs/base/test/common/mock'; import { Event } from 'vs/base/common/event'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadCommands.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadCommands.test.ts index 0c5d4754..d7503ca0 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadCommands.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadCommands.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadConfiguration.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadConfiguration.test.ts index a3a8e477..0c5a02f6 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadConfiguration.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadConfiguration.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDiagnostics.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDiagnostics.test.ts index bf67789f..2f89e98e 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDiagnostics.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDiagnostics.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { URI, UriComponents } from 'vs/base/common/uri'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocumentContentProviders.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocumentContentProviders.test.ts index 8eaa5879..949c2a78 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocumentContentProviders.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocumentContentProviders.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { MainThreadDocumentContentProviders } from 'vs/workbench/api/browser/mainThreadDocumentContentProviders'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts index 8331e51c..fee65f21 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocuments.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; import { timeout } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts index d7190f1f..17a9049d 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -27,11 +27,15 @@ import { TestTextResourcePropertiesService, TestWorkingCopyFileService } from 'v import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { LanguageService } from 'vs/editor/common/services/languageService'; 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 { ILanguageService } from 'vs/editor/common/languages/language'; +import { LanguageService } from 'vs/editor/common/services/languageService'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; suite('MainThreadDocumentsAndEditors', () => { @@ -61,12 +65,15 @@ suite('MainThreadDocumentsAndEditors', () => { const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); const themeService = new TestThemeService(); + const instantiationService = new TestInstantiationService(); + instantiationService.set(ILanguageService, disposables.add(new LanguageService())); + instantiationService.set(ILanguageConfigurationService, new TestLanguageConfigurationService()); + instantiationService.set(IUndoRedoService, undoRedoService); modelService = new ModelService( configService, new TestTextResourcePropertiesService(configService), undoRedoService, - disposables.add(new LanguageService()), - new TestLanguageConfigurationService(), + instantiationService ); codeEditorService = new TestCodeEditorService(themeService); textFileService = new class extends mock() { diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts index 9809814a..d83a5ebc 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { DisposableStore, IReference, ImmortalReference } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -16,12 +16,10 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ITextSnapshot } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { LanguageService } from 'vs/editor/common/services/languageService'; import { IModelService } from 'vs/editor/common/services/model'; import { ModelService } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; -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 { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -57,6 +55,10 @@ import { ICopyOperation, ICreateFileOperation, ICreateOperation, IDeleteOperatio import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestFileService, TestLifecycleService, TestWorkingCopyService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { LanguageService } from 'vs/editor/common/services/languageService'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; suite('MainThreadEditors', () => { @@ -80,19 +82,11 @@ suite('MainThreadEditors', () => { createdResources.clear(); deletedResources.clear(); - const configService = new TestConfigurationService(); const dialogService = new TestDialogService(); const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); const themeService = new TestThemeService(); - modelService = new ModelService( - configService, - new TestTextResourcePropertiesService(configService), - undoRedoService, - disposables.add(new LanguageService()), - new TestLanguageConfigurationService(), - ); const services = new ServiceCollection(); services.set(IBulkEditService, new SyncDescriptor(BulkEditService)); @@ -178,8 +172,18 @@ suite('MainThreadEditors', () => { } }); + services.set(ILanguageService, disposables.add(new LanguageService())); + services.set(ILanguageConfigurationService, new TestLanguageConfigurationService()); + const instaService = new InstantiationService(services); + modelService = new ModelService( + configService, + new TestTextResourcePropertiesService(configService), + undoRedoService, + instaService + ); + bulkEdits = instaService.createInstance(MainThreadBulkEdits, SingleProxyRPCProtocol(null)); }); diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts index 8a714d81..dd20e3cd 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadManagedSockets.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { disposableTimeout, timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts index 57b58ab6..f4ead183 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as assert from 'assert'; +import assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; diff --git a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts index 5234d7ab..aa9a770a 100644 --- a/patched-vscode/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -56,7 +56,8 @@ suite('MainThreadWorkspace', () => { fileSearch(query: IFileQuery) { assert.strictEqual(query.folderQueries.length, 1); assert.strictEqual(query.folderQueries[0].disregardIgnoreFiles, true); - assert.deepStrictEqual(query.folderQueries[0].excludePattern, { 'filesExclude': true }); + assert.strictEqual(query.folderQueries[0].excludePattern?.length, 1); + assert.deepStrictEqual(query.folderQueries[0].excludePattern[0].pattern, { 'filesExclude': true }); return Promise.resolve({ results: [], messages: [] }); } @@ -76,7 +77,7 @@ suite('MainThreadWorkspace', () => { instantiationService.stub(ISearchService, { fileSearch(query: IFileQuery) { - assert.deepStrictEqual(query.folderQueries[0].excludePattern, undefined); + assert.deepStrictEqual(query.folderQueries[0].excludePattern, []); assert.deepStrictEqual(query.excludePattern, undefined); return Promise.resolve({ results: [], messages: [] }); @@ -99,7 +100,7 @@ suite('MainThreadWorkspace', () => { fileSearch(query: IFileQuery) { assert.strictEqual(query.folderQueries.length, 1); assert.strictEqual(query.folderQueries[0].disregardIgnoreFiles, true); - assert.deepStrictEqual(query.folderQueries[0].excludePattern, undefined); + assert.deepStrictEqual(query.folderQueries[0].excludePattern, []); return Promise.resolve({ results: [], messages: [] }); } @@ -112,7 +113,7 @@ suite('MainThreadWorkspace', () => { test('exclude string', () => { instantiationService.stub(ISearchService, { fileSearch(query: IFileQuery) { - assert.strictEqual(query.folderQueries[0].excludePattern, undefined); + assert.deepStrictEqual(query.folderQueries[0].excludePattern, []); assert.deepStrictEqual({ ...query.excludePattern }, { 'exclude/**': true }); return Promise.resolve({ results: [], messages: [] }); @@ -120,6 +121,6 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: 'exclude/**', disregardSearchExcludeSettings: true }, CancellationToken.None); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: [{ pattern: 'exclude/**' }], disregardSearchExcludeSettings: true }, CancellationToken.None); }); }); diff --git a/patched-vscode/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts b/patched-vscode/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts index 3a566566..bfd874ac 100644 --- a/patched-vscode/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/common/extHostExtensionActivator.test.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { promiseWithResolvers, timeout } from 'vs/base/common/async'; +import { Mutable } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { NullLogService } from 'vs/platform/log/common/log'; import { ActivatedExtension, EmptyExtension, ExtensionActivationTimes, ExtensionsActivator, IExtensionsActivatorHost } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ExtensionDescriptionRegistry, IActivationEventsReader } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; @@ -85,8 +86,8 @@ suite('ExtensionsActivator', () => { test('Supports having resolved extensions', async () => { const host = new SimpleExtensionsActivatorHost(); const bExt = desc(idB); - delete (bExt).main; - delete (bExt).browser; + delete (>bExt).main; + delete (>bExt).browser; const activator = createActivator(host, [ desc(idA, [idB]) ], [bExt]); @@ -103,7 +104,7 @@ suite('ExtensionsActivator', () => { [idB, extActivationB] ]); const bExt = desc(idB); - (bExt).api = 'none'; + (>bExt).api = 'none'; const activator = createActivator(host, [ desc(idA, [idB]) ], [bExt]); @@ -274,7 +275,8 @@ suite('ExtensionsActivator', () => { activationEvents, main: 'index.js', targetPlatform: TargetPlatform.UNDEFINED, - extensionDependencies: deps.map(d => d.value) + extensionDependencies: deps.map(d => d.value), + enabledApiProposals: undefined, }; } diff --git a/patched-vscode/src/vs/workbench/api/test/common/extensionHostMain.test.ts b/patched-vscode/src/vs/workbench/api/test/common/extensionHostMain.test.ts index 928c06a1..1608511b 100644 --- a/patched-vscode/src/vs/workbench/api/test/common/extensionHostMain.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/common/extensionHostMain.test.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { SerializedError, errorHandler, onUnexpectedError } from 'vs/base/common/errors'; import { isFirefox, isSafari } from 'vs/base/common/platform'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -48,7 +48,7 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo declare readonly _serviceBrand: undefined; getExtensionPathIndex() { return new class extends ExtensionPaths { - override findSubstr(key: URI): Readonly | undefined { + override findSubstr(key: URI): IExtensionDescription | undefined { findSubstrCount++; return nullExtensionDescription; } diff --git a/patched-vscode/src/vs/workbench/api/test/node/extHostSearch.test.ts b/patched-vscode/src/vs/workbench/api/test/node/extHostSearch.test.ts index 1502ef3f..458beed2 100644 --- a/patched-vscode/src/vs/workbench/api/test/node/extHostSearch.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/node/extHostSearch.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { mapArrayOrNot } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -16,6 +16,7 @@ import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; @@ -75,12 +76,12 @@ suite('ExtHostSearch', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); async function registerTestTextSearchProvider(provider: vscode.TextSearchProvider, scheme = 'file'): Promise { - disposables.add(extHostSearch.registerTextSearchProvider(scheme, provider)); + disposables.add(extHostSearch.registerTextSearchProviderOld(scheme, provider)); await rpcProtocol.sync(); } async function registerTestFileSearchProvider(provider: vscode.FileSearchProvider, scheme = 'file'): Promise { - disposables.add(extHostSearch.registerFileSearchProvider(scheme, provider)); + disposables.add(extHostSearch.registerFileSearchProviderOld(scheme, provider)); await rpcProtocol.sync(); } @@ -144,12 +145,32 @@ suite('ExtHostSearch', () => { rpcProtocol, new class extends mock() { override remote = { isRemote: false, authority: undefined, connectionData: null }; }, new URITransformerService(null), + new class extends mock() { + override async getConfigProvider(): Promise { + return { + onDidChangeConfiguration(_listener: (event: vscode.ConfigurationChangeEvent) => void) { }, + getConfiguration(): vscode.WorkspaceConfiguration { + return { + get() { }, + has() { + return false; + }, + inspect() { + return undefined; + }, + async update() { } + }; + }, + + } as ExtHostConfigProvider; + } + }, logService ); this._pfs = mockPFS as any; } - protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { + protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { return new NativeTextSearchManager(query, provider, this._pfs); } }); @@ -317,9 +338,11 @@ suite('ExtHostSearch', () => { includePattern: { 'foo': true }, - excludePattern: { - 'bar': true - } + excludePattern: [{ + pattern: { + 'bar': true + } + }] }, { folder: rootFolderB } ] @@ -356,9 +379,11 @@ suite('ExtHostSearch', () => { includePattern: { '*.jsx': true }, - excludePattern: { - '*.js': false - } + excludePattern: [{ + pattern: { + '*.js': false + } + }] } ] }; @@ -474,17 +499,21 @@ suite('ExtHostSearch', () => { folderQueries: [ { folder: rootFolderA, - excludePattern: { - 'folder/*.css': { - when: '$(basename).scss' + excludePattern: [{ + pattern: { + 'folder/*.css': { + when: '$(basename).scss' + } } - } + }] }, { folder: rootFolderB, - excludePattern: { - '*.js': false - } + excludePattern: [{ + pattern: { + '*.js': false + } + }] } ] }; @@ -717,13 +746,13 @@ suite('ExtHostSearch', () => { if (resultIsMatch(lineResult)) { actualTextSearchResults.push({ preview: { - text: lineResult.preview.text, + text: lineResult.previewText, matches: mapArrayOrNot( - lineResult.preview.matches, + lineResult.rangeLocations.map(r => r.preview), m => new Range(m.startLineNumber, m.startColumn, m.endLineNumber, m.endColumn)) }, ranges: mapArrayOrNot( - lineResult.ranges, + lineResult.rangeLocations.map(r => r.source), r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn), ), uri: fileMatch.resource @@ -855,9 +884,11 @@ suite('ExtHostSearch', () => { includePattern: { 'foo': true }, - excludePattern: { - 'bar': true - } + excludePattern: [{ + pattern: { + 'bar': true + } + }] }, { folder: rootFolderB } ] @@ -894,9 +925,11 @@ suite('ExtHostSearch', () => { includePattern: { '*.jsx': true }, - excludePattern: { - '*.js': false - } + excludePattern: [{ + pattern: { + '*.js': false + } + }] } ] }; @@ -1020,17 +1053,21 @@ suite('ExtHostSearch', () => { folderQueries: [ { folder: rootFolderA, - excludePattern: { - 'folder/*.css': { - when: '$(basename).scss' + excludePattern: [{ + pattern: { + 'folder/*.css': { + when: '$(basename).scss' + } } - } + }] }, { folder: rootFolderB, - excludePattern: { - '*.js': false - } + excludePattern: [{ + pattern: { + '*.js': false + } + }] } ] }; diff --git a/patched-vscode/src/vs/workbench/api/test/node/extHostTunnelService.test.ts b/patched-vscode/src/vs/workbench/api/test/node/extHostTunnelService.test.ts index 2f567ef7..57fe15e5 100644 --- a/patched-vscode/src/vs/workbench/api/test/node/extHostTunnelService.test.ts +++ b/patched-vscode/src/vs/workbench/api/test/node/extHostTunnelService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { findPorts, getRootProcesses, getSockets, loadConnectionTable, loadListeningPorts, parseIpAddress, tryFindRootPorts } from 'vs/workbench/api/node/extHostTunnelService'; const tcp = diff --git a/patched-vscode/src/vs/workbench/api/worker/extensionHostWorker.esm.ts b/patched-vscode/src/vs/workbench/api/worker/extensionHostWorker.esm.ts new file mode 100644 index 00000000..10a4278a --- /dev/null +++ b/patched-vscode/src/vs/workbench/api/worker/extensionHostWorker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { create } from './extensionHostWorker'; + +const data = create(); +self.onmessage = (e) => data.onmessage(e.data); diff --git a/patched-vscode/src/vs/workbench/api/worker/extensionHostWorker.ts b/patched-vscode/src/vs/workbench/api/worker/extensionHostWorker.ts index 86136a08..b9811731 100644 --- a/patched-vscode/src/vs/workbench/api/worker/extensionHostWorker.ts +++ b/patched-vscode/src/vs/workbench/api/worker/extensionHostWorker.ts @@ -234,6 +234,10 @@ function isInitMessage(a: any): a is IInitMessage { return !!a && typeof a === 'object' && a.type === 'vscode.init' && a.data instanceof Map; } +/** + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle + */ export function create(): { onmessage: (message: any) => void } { performance.mark(`code/extHost/willConnectToRenderer`); const res = new ExtensionWorker(); diff --git a/patched-vscode/src/vs/workbench/browser/actions.ts b/patched-vscode/src/vs/workbench/browser/actions.ts index 1e166016..dbe7355c 100644 --- a/patched-vscode/src/vs/workbench/browser/actions.ts +++ b/patched-vscode/src/vs/workbench/browser/actions.ts @@ -96,9 +96,8 @@ export class CompositeMenuActions extends Disposable { const actions: IAction[] = []; if (this.contextMenuId) { - const menu = this.menuService.createMenu(this.contextMenuId, this.contextKeyService); - createAndFillInActionBarActions(menu, this.options, { primary: [], secondary: actions }); - menu.dispose(); + const menu = this.menuService.getMenuActions(this.contextMenuId, this.contextKeyService, this.options); + createAndFillInActionBarActions(menu, { primary: [], secondary: actions }); } return actions; diff --git a/patched-vscode/src/vs/workbench/browser/actions/developerActions.ts b/patched-vscode/src/vs/workbench/browser/actions/developerActions.ts index 2c20a357..7823fa98 100644 --- a/patched-vscode/src/vs/workbench/browser/actions/developerActions.ts +++ b/patched-vscode/src/vs/workbench/browser/actions/developerActions.ts @@ -63,7 +63,7 @@ class InspectContextKeysAction extends Action2 { const hoverFeedback = document.createElement('div'); const activeDocument = getActiveDocument(); activeDocument.body.appendChild(hoverFeedback); - disposables.add(toDisposable(() => activeDocument.body.removeChild(hoverFeedback))); + disposables.add(toDisposable(() => hoverFeedback.remove())); hoverFeedback.style.position = 'absolute'; hoverFeedback.style.pointerEvents = 'none'; diff --git a/patched-vscode/src/vs/workbench/browser/actions/layoutActions.ts b/patched-vscode/src/vs/workbench/browser/actions/layoutActions.ts index 77574a63..b9668db3 100644 --- a/patched-vscode/src/vs/workbench/browser/actions/layoutActions.ts +++ b/patched-vscode/src/vs/workbench/browser/actions/layoutActions.ts @@ -31,6 +31,7 @@ import { ICommandActionTitle } from 'vs/platform/action/common/action'; import { mainWindow } from 'vs/base/browser/window'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { TitlebarStyle } from 'vs/platform/window/common/window'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; // Register Icons const menubarIcon = registerIcon('menuBar', Codicon.layoutMenubar, localize('menuBarIcon', "Represents the menu bar")); @@ -673,6 +674,48 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 11 }); +// --- Configure Tabs Layout + +export class ConfigureEditorTabsAction extends Action2 { + + static readonly ID = 'workbench.action.configureEditorTabs'; + + constructor() { + super({ + id: ConfigureEditorTabsAction.ID, + title: localize2('configureTabs', "Configure Tabs"), + category: Categories.View, + }); + } + + run(accessor: ServicesAccessor) { + const preferencesService = accessor.get(IPreferencesService); + preferencesService.openSettings({ jsonEditor: false, query: 'workbench.editor tab' }); + } +} +registerAction2(ConfigureEditorTabsAction); + +// --- Configure Editor + +export class ConfigureEditorAction extends Action2 { + + static readonly ID = 'workbench.action.configureEditor'; + + constructor() { + super({ + id: ConfigureEditorAction.ID, + title: localize2('configureEditors', "Configure Editors"), + category: Categories.View, + }); + } + + run(accessor: ServicesAccessor) { + const preferencesService = accessor.get(IPreferencesService); + preferencesService.openSettings({ jsonEditor: false, query: 'workbench.editor' }); + } +} +registerAction2(ConfigureEditorAction); + // --- Toggle Pinned Tabs On Separate Row registerAction2(class extends Action2 { @@ -923,13 +966,14 @@ registerAction2(class extends Action2 { } private async getView(quickInputService: IQuickInputService, viewDescriptorService: IViewDescriptorService, paneCompositePartService: IPaneCompositePartService, viewId?: string): Promise { - const quickPick = quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const quickPick = disposables.add(quickInputService.createQuickPick({ useSeparators: true })); quickPick.placeholder = localize('moveFocusedView.selectView', "Select a View to Move"); quickPick.items = this.getViewItems(viewDescriptorService, paneCompositePartService); quickPick.selectedItems = quickPick.items.filter(item => (item as IQuickPickItem).id === viewId) as IQuickPickItem[]; return new Promise((resolve, reject) => { - quickPick.onDidAccept(() => { + disposables.add(quickPick.onDidAccept(() => { const viewId = quickPick.selectedItems[0]; if (viewId.id) { resolve(viewId.id); @@ -938,9 +982,12 @@ registerAction2(class extends Action2 { } quickPick.hide(); - }); + })); - quickPick.onDidHide(() => reject()); + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + reject(); + })); quickPick.show(); }); @@ -982,7 +1029,8 @@ class MoveFocusedViewAction extends Action2 { return; } - const quickPick = quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const quickPick = disposables.add(quickInputService.createQuickPick({ useSeparators: true })); quickPick.placeholder = localize('moveFocusedView.selectDestination', "Select a Destination for the View"); quickPick.title = localize({ key: 'moveFocusedView.title', comment: ['{0} indicates the title of the view the user has selected to move.'] }, "View: Move {0}", viewDescriptor.name.value); @@ -1077,7 +1125,7 @@ class MoveFocusedViewAction extends Action2 { quickPick.items = items; - quickPick.onDidAccept(() => { + disposables.add(quickPick.onDidAccept(() => { const destination = quickPick.selectedItems[0]; if (destination.id === '_.panel.newcontainer') { @@ -1095,7 +1143,9 @@ class MoveFocusedViewAction extends Action2 { } quickPick.hide(); - }); + })); + + disposables.add(quickPick.onDidHide(() => disposables.dispose())); quickPick.show(); } @@ -1368,7 +1418,7 @@ for (const { active } of [...ToggleVisibilityActions, ...MoveSideBarActions, ... registerAction2(class CustomizeLayoutAction extends Action2 { - private _currentQuickPick?: IQuickPick; + private _currentQuickPick?: IQuickPick; constructor() { super({ @@ -1461,7 +1511,10 @@ registerAction2(class CustomizeLayoutAction extends Action2 { const commandService = accessor.get(ICommandService); const quickInputService = accessor.get(IQuickInputService); const keybindingService = accessor.get(IKeybindingService); - const quickPick = quickInputService.createQuickPick(); + + const disposables = new DisposableStore(); + + const quickPick = disposables.add(quickInputService.createQuickPick({ useSeparators: true })); this._currentQuickPick = quickPick; quickPick.items = this.getItems(contextKeyService, keybindingService); @@ -1486,7 +1539,6 @@ registerAction2(class CustomizeLayoutAction extends Action2 { closeButton ]; - const disposables = new DisposableStore(); let selectedItem: CustomizeLayoutItem | undefined = undefined; disposables.add(contextKeyService.onDidChangeContext(changeEvent => { if (changeEvent.affectsSome(LayoutContextKeySet)) { @@ -1499,21 +1551,21 @@ registerAction2(class CustomizeLayoutAction extends Action2 { } })); - quickPick.onDidAccept(event => { + disposables.add(quickPick.onDidAccept(event => { if (quickPick.selectedItems.length) { selectedItem = quickPick.selectedItems[0] as CustomizeLayoutItem; commandService.executeCommand(selectedItem.id); } - }); + })); - quickPick.onDidTriggerItemButton(event => { + disposables.add(quickPick.onDidTriggerItemButton(event => { if (event.item) { selectedItem = event.item as CustomizeLayoutItem; commandService.executeCommand(selectedItem.id); } - }); + })); - quickPick.onDidTriggerButton((button) => { + disposables.add(quickPick.onDidTriggerButton((button) => { if (button === closeButton) { quickPick.hide(); } else if (button === resetButton) { @@ -1535,16 +1587,16 @@ registerAction2(class CustomizeLayoutAction extends Action2 { commandService.executeCommand('workbench.action.alignPanelCenter'); } - }); + })); - quickPick.onDidHide(() => { + disposables.add(quickPick.onDidHide(() => { quickPick.dispose(); - }); + })); - quickPick.onDispose(() => { + disposables.add(quickPick.onDispose(() => { this._currentQuickPick = undefined; disposables.dispose(); - }); + })); quickPick.show(); } diff --git a/patched-vscode/src/vs/workbench/browser/actions/listCommands.ts b/patched-vscode/src/vs/workbench/browser/actions/listCommands.ts index db6890b9..188a94e1 100644 --- a/patched-vscode/src/vs/workbench/browser/actions/listCommands.ts +++ b/patched-vscode/src/vs/workbench/browser/actions/listCommands.ts @@ -725,7 +725,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const elementWithHover = getCustomHoverForElement(focusedElement as HTMLElement); if (elementWithHover) { - accessor.get(IHoverService).triggerUpdatableHover(elementWithHover as HTMLElement); + accessor.get(IHoverService).showManagedHover(elementWithHover as HTMLElement); } }, }); diff --git a/patched-vscode/src/vs/workbench/browser/actions/quickAccessActions.ts b/patched-vscode/src/vs/workbench/browser/actions/quickAccessActions.ts index 9890994c..acf6652d 100644 --- a/patched-vscode/src/vs/workbench/browser/actions/quickAccessActions.ts +++ b/patched-vscode/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -163,12 +163,13 @@ registerAction2(class QuickAccessAction extends Action2 { run(accessor: ServicesAccessor): void { const quickInputService = accessor.get(IQuickInputService); + const providerOptions: AnythingQuickAccessProviderRunOptions = { + includeHelp: true, + from: 'commandCenter', + }; quickInputService.quickAccess.show(undefined, { preserveValue: true, - providerOptions: { - includeHelp: true, - from: 'commandCenter', - } as AnythingQuickAccessProviderRunOptions + providerOptions }); } }); diff --git a/patched-vscode/src/vs/workbench/browser/client.ts b/patched-vscode/src/vs/workbench/browser/client.ts deleted file mode 100644 index 1de4fedb..00000000 --- a/patched-vscode/src/vs/workbench/browser/client.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Disposable } from 'vs/base/common/lifecycle'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { MenuId, MenuRegistry } from "vs/platform/actions/common/actions"; -import { localize } from "vs/nls"; -import { ILogService } from "vs/platform/log/common/log"; - -export class SagemakerServerClient extends Disposable { - constructor ( - @ILogService private logService: ILogService - ) { - super(); - - this.logService.debug('Initializing SagemakerServerClient...'); - this.registerSagemakerCommands(); - } - - static LOGOUT_COMMAND_ID = 'sagemaker.logout'; - static COOKIE_COMMAND_ID = 'sagemaker.parseCookies'; - - private registerSagemakerCommands() { - const authMode: string | undefined = this.getCookieValue('authMode'); - const expiryTime: string | undefined = this.getCookieValue('expiryTime'); - const studioUserProfileName: string | undefined = this.getCookieValue('studioUserProfileName') - const ssoExpiryTimestamp: string | undefined = this.getCookieValue('ssoExpiryTimestamp') - const redirectURL: string | undefined = this.getCookieValue('redirectURL') - - this.logService.debug('Registering sagemaker commands...'); - - CommandsRegistry.registerCommand(SagemakerServerClient.COOKIE_COMMAND_ID, () => { - return { - authMode: authMode, - expiryTime: expiryTime, - ssoExpiryTimestamp: ssoExpiryTimestamp, - studioUserProfileName: studioUserProfileName, - redirectURL: redirectURL - }; - }); - - CommandsRegistry.registerCommand(SagemakerServerClient.LOGOUT_COMMAND_ID, () => { - const currentUrl = new URL(window.location.href); - const hostname = currentUrl.hostname; - const pathComponents = currentUrl.pathname.split('/'); - const logoutUrl = `https://${hostname}/${pathComponents[1]}/${pathComponents[2]}/logout`; - window.location.href = logoutUrl; - }); - - for (const menuId of [MenuId.CommandPalette, MenuId.MenubarHomeMenu]) { - MenuRegistry.appendMenuItem(menuId, { - command: { - id: SagemakerServerClient.LOGOUT_COMMAND_ID, - title: localize('logout', "{0}: Log out", 'Sagemaker'), - }, - }); - } - } - - private getCookieValue(name: string): string | undefined { - const match = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531 - return match ? match.pop() : undefined; - } -} \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/browser/contextkeys.ts b/patched-vscode/src/vs/workbench/browser/contextkeys.ts index c22caa96..cffce2ce 100644 --- a/patched-vscode/src/vs/workbench/browser/contextkeys.ts +++ b/patched-vscode/src/vs/workbench/browser/contextkeys.ts @@ -203,17 +203,16 @@ export class WorkbenchContextKeysHandler extends Disposable { private registerListeners(): void { this.editorGroupService.whenReady.then(() => { this.updateEditorAreaContextKeys(); - this.updateEditorGroupContextKeys(); + this.updateActiveEditorGroupContextKeys(); this.updateVisiblePanesContextKeys(); }); - this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorGroupContextKeys())); + this._register(this.editorService.onDidActiveEditorChange(() => this.updateActiveEditorGroupContextKeys())); this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateVisiblePanesContextKeys())); - this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorGroupContextKeys())); - this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorGroupContextKeys())); - this._register(this.editorGroupService.onDidChangeGroupIndex(() => this.updateEditorGroupContextKeys())); - this._register(this.editorGroupService.onDidChangeActiveGroup(() => this.updateEditorGroupsContextKeys())); - this._register(this.editorGroupService.onDidChangeGroupLocked(() => this.updateEditorGroupsContextKeys())); + this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorGroupsContextKeys())); + this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorGroupsContextKeys())); + this._register(this.editorGroupService.onDidChangeGroupIndex(() => this.updateActiveEditorGroupContextKeys())); + this._register(this.editorGroupService.onDidChangeGroupLocked(() => this.updateActiveEditorGroupContextKeys())); this._register(this.editorGroupService.onDidChangeEditorPartOptions(() => this.updateEditorAreaContextKeys())); @@ -266,15 +265,22 @@ export class WorkbenchContextKeysHandler extends Disposable { } } - private updateEditorGroupContextKeys(): void { + // Context keys depending on the state of the editor group itself + private updateActiveEditorGroupContextKeys(): void { if (!this.editorService.activeEditor) { this.activeEditorGroupEmpty.set(true); } else { this.activeEditorGroupEmpty.reset(); } + + const activeGroup = this.editorGroupService.activeGroup; + this.activeEditorGroupIndex.set(activeGroup.index + 1); // not zero-indexed + this.activeEditorGroupLocked.set(activeGroup.isLocked); + this.updateEditorGroupsContextKeys(); } + // Context keys depending on the state of other editor groups private updateEditorGroupsContextKeys(): void { const groupCount = this.editorGroupService.count; if (groupCount > 1) { @@ -284,9 +290,7 @@ export class WorkbenchContextKeysHandler extends Disposable { } const activeGroup = this.editorGroupService.activeGroup; - this.activeEditorGroupIndex.set(activeGroup.index + 1); // not zero-indexed this.activeEditorGroupLast.set(activeGroup.index === groupCount - 1); - this.activeEditorGroupLocked.set(activeGroup.isLocked); } private updateEditorAreaContextKeys(): void { diff --git a/patched-vscode/src/vs/workbench/browser/layout.ts b/patched-vscode/src/vs/workbench/browser/layout.ts index 4e2809e1..4ca393d9 100644 --- a/patched-vscode/src/vs/workbench/browser/layout.ts +++ b/patched-vscode/src/vs/workbench/browser/layout.ts @@ -12,14 +12,14 @@ import { isWindows, isLinux, isMacintosh, isWeb, isIOS } from 'vs/base/common/pl import { EditorInputCapabilities, GroupIdentifier, isResourceEditorInput, IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode, EditorActionsLocation, shouldShowCustomTitleBar } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode, EditorActionsLocation, shouldShowCustomTitleBar, isHorizontal } from 'vs/workbench/services/layout/browser/layoutService'; import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { getMenuBarVisibility, IPath, hasNativeTitlebar, hasCustomTitlebar, TitleBarSetting } from 'vs/platform/window/common/window'; +import { getMenuBarVisibility, IPath, hasNativeTitlebar, hasCustomTitlebar, TitleBarSetting, CustomTitleBarVisibility, useWindowControlsOverlay } from 'vs/platform/window/common/window'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -48,7 +48,6 @@ import { AuxiliaryBarPart } from 'vs/workbench/browser/parts/auxiliarybar/auxili import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; import { CodeWindow, mainWindow } from 'vs/base/browser/window'; -import { CustomTitleBarVisibility } from '../../platform/window/common/window'; //#region Layout Implementation @@ -208,11 +207,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private getContainerDimension(container: HTMLElement): IDimension { if (container === this.mainContainer) { - // main window - return this.mainContainerDimension; + return this.mainContainerDimension; // main window } else { - // auxiliary window - return getClientArea(container); + return getClientArea(container); // auxiliary window } } @@ -562,7 +559,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private updateWindowsBorder(skipLayout = false) { if ( isWeb || - isWindows || // not working well with zooming and window control overlays + isWindows || // not working well with zooming (border often not visible) + useWindowControlsOverlay(this.configurationService) || // not working with WCO (border cannot draw over the overlay) hasNativeTitlebar(this.configurationService) ) { return; @@ -610,7 +608,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.stateModel.setRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN, false); } - this.stateModel.onDidChangeState(change => { + this._register(this.stateModel.onDidChangeState(change => { if (change.key === LayoutStateKeys.ACTIVITYBAR_HIDDEN) { this.setActivityBarHidden(change.value as boolean); } @@ -632,12 +630,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } this.doUpdateLayoutConfiguration(); - }); + })); // Layout Initialization State const initialEditorsState = this.getInitialEditorsState(); if (initialEditorsState) { - this.logService.info('Initial editor state', initialEditorsState); + this.logService.trace('Initial editor state', initialEditorsState); } const initialLayoutState: ILayoutInitializationState = { layout: { @@ -678,7 +676,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Only restore last viewlet if window was reloaded or we are in development mode let viewContainerToRestore: string | undefined; - if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow) { + if ( + !this.environmentService.isBuilt || + lifecycleService.startupKind === StartupKind.ReloadedWindow || + this.environmentService.isExtensionDevelopment && !this.environmentService.extensionTestsLocationURI + ) { viewContainerToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); } else { viewContainerToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; @@ -1264,18 +1266,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const containerDimension = this.getContainerDimension(container); if (container === this.mainContainer) { - const panelPosition = this.getPanelPosition(); - const isColumn = panelPosition === Position.RIGHT || panelPosition === Position.LEFT; + const isPanelHorizontal = isHorizontal(this.getPanelPosition()); const takenWidth = (this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) + (this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) + - (this.isVisible(Parts.PANEL_PART) && isColumn ? this.panelPartView.minimumWidth : 0) + + (this.isVisible(Parts.PANEL_PART) && !isPanelHorizontal ? this.panelPartView.minimumWidth : 0) + (this.isVisible(Parts.AUXILIARYBAR_PART) ? this.auxiliaryBarPartView.minimumWidth : 0); const takenHeight = (this.isVisible(Parts.TITLEBAR_PART, targetWindow) ? this.titleBarPartView.minimumHeight : 0) + (this.isVisible(Parts.STATUSBAR_PART, targetWindow) ? this.statusBarPartView.minimumHeight : 0) + - (this.isVisible(Parts.PANEL_PART) && !isColumn ? this.panelPartView.minimumHeight : 0); + (this.isVisible(Parts.PANEL_PART) && isPanelHorizontal ? this.panelPartView.minimumHeight : 0); const availableWidth = containerDimension.width - takenWidth; const availableHeight = containerDimension.height - takenHeight; @@ -1533,28 +1534,29 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })); } - this._register(this.storageService.onWillSaveState(willSaveState => { - if (willSaveState.reason === WillSaveStateReason.SHUTDOWN) { - // Side Bar Size - const sideBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) - ? this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) - : this.workbenchGrid.getViewSize(this.sideBarPartView).width; - this.stateModel.setInitializationValue(LayoutStateKeys.SIDEBAR_SIZE, sideBarSize as number); + this._register(this.storageService.onWillSaveState(e => { - // Panel Size - const panelSize = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) - ? this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) - : (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) === Position.BOTTOM ? this.workbenchGrid.getViewSize(this.panelPartView).height : this.workbenchGrid.getViewSize(this.panelPartView).width); - this.stateModel.setInitializationValue(LayoutStateKeys.PANEL_SIZE, panelSize as number); + // Side Bar Size + const sideBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) + ? this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) + : this.workbenchGrid.getViewSize(this.sideBarPartView).width; + this.stateModel.setInitializationValue(LayoutStateKeys.SIDEBAR_SIZE, sideBarSize as number); - // Auxiliary Bar Size - const auxiliaryBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) - ? this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) - : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; - this.stateModel.setInitializationValue(LayoutStateKeys.AUXILIARYBAR_SIZE, auxiliaryBarSize as number); + // Panel Size + const panelSize = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) + ? this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) + : isHorizontal(this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION)) + ? this.workbenchGrid.getViewSize(this.panelPartView).height + : this.workbenchGrid.getViewSize(this.panelPartView).width; + this.stateModel.setInitializationValue(LayoutStateKeys.PANEL_SIZE, panelSize as number); - this.stateModel.save(true, true); - } + // Auxiliary Bar Size + const auxiliaryBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) + ? this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) + : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; + this.stateModel.setInitializationValue(LayoutStateKeys.AUXILIARYBAR_SIZE, auxiliaryBarSize as number); + + this.stateModel.save(true, true); })); } @@ -1634,8 +1636,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.workbenchGrid.resizeView(this.panelPartView, { - width: viewSize.width + (this.getPanelPosition() !== Position.BOTTOM ? sizeChangePxWidth : 0), - height: viewSize.height + (this.getPanelPosition() !== Position.BOTTOM ? 0 : sizeChangePxHeight) + width: viewSize.width + (isHorizontal(this.getPanelPosition()) ? 0 : sizeChangePxWidth), + height: viewSize.height + (isHorizontal(this.getPanelPosition()) ? sizeChangePxHeight : 0) }); break; @@ -1769,8 +1771,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private adjustPartPositions(sideBarPosition: Position, panelAlignment: PanelAlignment, panelPosition: Position): void { // Move activity bar and side bars - const sideBarSiblingToEditor = panelPosition !== Position.BOTTOM || !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); - const auxiliaryBarSiblingToEditor = panelPosition !== Position.BOTTOM || !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); + const isPanelVertical = !isHorizontal(panelPosition); + const sideBarSiblingToEditor = isPanelVertical || !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); + const auxiliaryBarSiblingToEditor = isPanelVertical || !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); const preMovePanelWidth = !this.isVisible(Parts.PANEL_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) ?? this.panelPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.panelPartView).width; const preMovePanelHeight = !this.isVisible(Parts.PANEL_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) ?? this.panelPartView.minimumHeight) : this.workbenchGrid.getViewSize(this.panelPartView).height; const preMoveSideBarSize = !this.isVisible(Parts.SIDEBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width; @@ -1796,7 +1799,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // We moved all the side parts based on the editor and ignored the panel // Now, we need to put the panel back in the right position when it is next to the editor - if (panelPosition !== Position.BOTTOM) { + if (isPanelVertical) { this.workbenchGrid.moveView(this.panelPartView, preMovePanelWidth, this.editorPartView, panelPosition === Position.LEFT ? Direction.Left : Direction.Right); this.workbenchGrid.resizeView(this.panelPartView, { height: preMovePanelHeight as number, @@ -1823,8 +1826,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi setPanelAlignment(alignment: PanelAlignment, skipLayout?: boolean): void { - // Panel alignment only applies to a panel in the bottom position - if (this.getPanelPosition() !== Position.BOTTOM) { + // Panel alignment only applies to a panel in the top/bottom position + if (!isHorizontal(this.getPanelPosition())) { this.setPanelPosition(Position.BOTTOM); } @@ -1920,7 +1923,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const isMaximized = this.isPanelMaximized(); if (!isMaximized) { if (this.isVisible(Parts.PANEL_PART)) { - if (panelPosition === Position.BOTTOM) { + if (isHorizontal(panelPosition)) { this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height); } else { this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH, size.width); @@ -1931,20 +1934,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } else { this.setEditorHidden(false); this.workbenchGrid.resizeView(this.panelPartView, { - width: panelPosition === Position.BOTTOM ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), - height: panelPosition === Position.BOTTOM ? this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT) : size.height + width: isHorizontal(panelPosition) ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), + height: isHorizontal(panelPosition) ? this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT) : size.height }); } + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_WAS_LAST_MAXIMIZED, !isMaximized); } - /** - * Returns whether or not the panel opens maximized - */ private panelOpensMaximized(): boolean { // The workbench grid currently prevents us from supporting panel maximization with non-center panel alignment - if (this.getPanelAlignment() !== 'center' && this.getPanelPosition() === Position.BOTTOM) { + if (this.getPanelAlignment() !== 'center' && isHorizontal(this.getPanelPosition())) { return false; } @@ -2022,7 +2023,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi isPanelMaximized(): boolean { // the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment - return (this.getPanelAlignment() === 'center' || this.getPanelPosition() !== Position.BOTTOM) && !this.isVisible(Parts.EDITOR_PART, mainWindow); + return (this.getPanelAlignment() === 'center' || !isHorizontal(this.getPanelPosition())) && !this.isVisible(Parts.EDITOR_PART, mainWindow); } getSideBarPosition(): Position { @@ -2098,14 +2099,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Save the current size of the panel for the new orthogonal direction // If moving down, save the width of the panel // Otherwise, save the height of the panel - if (position === Position.BOTTOM) { + if (isHorizontal(position)) { this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH, size.width); - } else if (positionFromString(oldPositionValue) === Position.BOTTOM) { + } else if (isHorizontal(positionFromString(oldPositionValue))) { this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height); } } - if (position === Position.BOTTOM && this.getPanelAlignment() !== 'center' && editorHidden) { + if (isHorizontal(position) && this.getPanelAlignment() !== 'center' && editorHidden) { this.toggleMaximizedPanel(); editorHidden = false; } @@ -2117,6 +2118,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (position === Position.BOTTOM) { this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.height : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT), this.editorPartView, Direction.Down); + } else if (position === Position.TOP) { + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.height : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT), this.editorPartView, Direction.Up); } else if (position === Position.RIGHT) { this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), this.editorPartView, Direction.Right); } else { @@ -2134,7 +2137,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.setAuxiliaryBarHidden(true); } - if (position === Position.BOTTOM) { + if (isHorizontal(position)) { this.adjustPartPositions(this.getSideBarPosition(), this.getPanelAlignment(), position); } @@ -2243,17 +2246,20 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const auxiliaryBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) ? 0 : nodes.auxiliaryBar.size; const panelSize = this.stateModel.getInitializationValue(LayoutStateKeys.PANEL_SIZE) ? 0 : nodes.panel.size; + const panelPostion = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION); + const sideBarPosition = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON); + const result = [] as ISerializedNode[]; - if (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) !== Position.BOTTOM) { + if (!isHorizontal(panelPostion)) { result.push(nodes.editor); nodes.editor.size = availableWidth - activityBarSize - sideBarSize - panelSize - auxiliaryBarSize; - if (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) === Position.RIGHT) { + if (panelPostion === Position.RIGHT) { result.push(nodes.panel); } else { result.splice(0, 0, nodes.panel); } - if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.LEFT) { + if (sideBarPosition === Position.LEFT) { result.push(nodes.auxiliaryBar); result.splice(0, 0, nodes.sideBar); result.splice(0, 0, nodes.activityBar); @@ -2264,18 +2270,20 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } else { const panelAlignment = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT); - const sideBarPosition = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON); const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); const editorSectionWidth = availableWidth - activityBarSize - (sideBarNextToEditor ? 0 : sideBarSize) - (auxiliaryBarNextToEditor ? 0 : auxiliaryBarSize); + + const editorNodes = this.arrangeEditorNodes({ + editor: nodes.editor, + sideBar: sideBarNextToEditor ? nodes.sideBar : undefined, + auxiliaryBar: auxiliaryBarNextToEditor ? nodes.auxiliaryBar : undefined + }, availableHeight - panelSize, editorSectionWidth); + result.push({ type: 'branch', - data: [this.arrangeEditorNodes({ - editor: nodes.editor, - sideBar: sideBarNextToEditor ? nodes.sideBar : undefined, - auxiliaryBar: auxiliaryBarNextToEditor ? nodes.auxiliaryBar : undefined - }, availableHeight - panelSize, editorSectionWidth), nodes.panel], + data: panelPostion === Position.BOTTOM ? [editorNodes, nodes.panel] : [nodes.panel, editorNodes], size: editorSectionWidth }); @@ -2367,7 +2375,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) }; - const middleSection: ISerializedNode[] = this.arrangeMiddleSectionNodes({ activityBar: activityBarNode, auxiliaryBar: auxiliaryBarNode, @@ -2419,7 +2426,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi panelVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the panel is visible' }; statusbarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the status bar is visible' }; sideBarPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the primary side bar is on the left or right' }; - panelPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the panel is on the bottom, left, or right' }; + panelPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the panel is on the top, bottom, left, or right' }; }; const layoutDescriptor: StartupLayoutEvent = { @@ -2627,7 +2634,7 @@ class LayoutStateModel extends Disposable { LayoutStateKeys.GRID_SIZE.defaultValue = { height: workbenchDimensions.height, width: workbenchDimensions.width }; LayoutStateKeys.SIDEBAR_SIZE.defaultValue = Math.min(300, workbenchDimensions.width / 4); LayoutStateKeys.AUXILIARYBAR_SIZE.defaultValue = Math.min(300, workbenchDimensions.width / 4); - LayoutStateKeys.PANEL_SIZE.defaultValue = (this.stateCache.get(LayoutStateKeys.PANEL_POSITION.name) ?? LayoutStateKeys.PANEL_POSITION.defaultValue) === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; + LayoutStateKeys.PANEL_SIZE.defaultValue = (this.stateCache.get(LayoutStateKeys.PANEL_POSITION.name) ?? isHorizontal(LayoutStateKeys.PANEL_POSITION.defaultValue)) ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; LayoutStateKeys.SIDEBAR_HIDDEN.defaultValue = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; // Apply all defaults @@ -2717,6 +2724,7 @@ class LayoutStateModel extends Disposable { if (oldValue !== undefined) { return !oldValue; } + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.DEFAULT; } diff --git a/patched-vscode/src/vs/workbench/browser/media/code-icon.svg b/patched-vscode/src/vs/workbench/browser/media/code-icon.svg index e0af6a3d..cc61f81e 100644 --- a/patched-vscode/src/vs/workbench/browser/media/code-icon.svg +++ b/patched-vscode/src/vs/workbench/browser/media/code-icon.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/browser/media/style.css b/patched-vscode/src/vs/workbench/browser/media/style.css index 35f856b9..6c9dbb9a 100644 --- a/patched-vscode/src/vs/workbench/browser/media/style.css +++ b/patched-vscode/src/vs/workbench/browser/media/style.css @@ -87,6 +87,15 @@ body.web { text-decoration: none; } + +.monaco-workbench p > a { + text-decoration: var(--text-link-decoration); +} + +.monaco-workbench.underline-links { + --text-link-decoration: underline; +} + .monaco-workbench.hc-black p > a, .monaco-workbench.hc-light p > a { text-decoration: underline !important; diff --git a/patched-vscode/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/patched-vscode/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 4610ddff..97f38610 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -363,10 +363,9 @@ export class ActivityBarCompositeBar extends PaneCompositeBar { } getActivityBarContextMenuActions(): IAction[] { - const activityBarPositionMenu = this.menuService.createMenu(MenuId.ActivityBarPositionMenu, this.contextKeyService); + const activityBarPositionMenu = this.menuService.getMenuActions(MenuId.ActivityBarPositionMenu, this.contextKeyService, { shouldForwardArgs: true, renderShortTitle: true }); const positionActions: IAction[] = []; - createAndFillInContextMenuActions(activityBarPositionMenu, { shouldForwardArgs: true, renderShortTitle: true }, { primary: [], secondary: positionActions }); - activityBarPositionMenu.dispose(); + createAndFillInContextMenuActions(activityBarPositionMenu, { primary: [], secondary: positionActions }); return [ new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), toAction({ id: ToggleSidebarPositionAction.ID, label: ToggleSidebarPositionAction.getLabel(this.layoutService), run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarPositionAction().run(accessor)) }) diff --git a/patched-vscode/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/patched-vscode/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index f42d3307..b40341d2 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/patched-vscode/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -104,7 +104,8 @@ display: none; } -.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.clicked:focus:before { +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.clicked:focus:before, +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.clicked:focus .active-item-indicator::before { border-left: none !important; /* no focus feedback when using mouse */ } diff --git a/patched-vscode/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/patched-vscode/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 16c40e83..246ac87f 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -191,10 +191,9 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { actions.push(viewsSubmenuAction); } - const activityBarPositionMenu = this.menuService.createMenu(MenuId.ActivityBarPositionMenu, this.contextKeyService); + const activityBarPositionMenu = this.menuService.getMenuActions(MenuId.ActivityBarPositionMenu, this.contextKeyService, { shouldForwardArgs: true, renderShortTitle: true }); const positionActions: IAction[] = []; - createAndFillInContextMenuActions(activityBarPositionMenu, { shouldForwardArgs: true, renderShortTitle: true }, { primary: [], secondary: positionActions }); - activityBarPositionMenu.dispose(); + createAndFillInContextMenuActions(activityBarPositionMenu, { primary: [], secondary: positionActions }); actions.push(...[ new Separator(), @@ -241,14 +240,6 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { return headerArea; } - protected override getToolbarWidth(): number { - if (this.getCompositeBarPosition() === CompositeBarPosition.TOP) { - return 22; - } - - return super.getToolbarWidth(); - } - private headerActionViewItemProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === ToggleAuxiliaryBarAction.ID) { return this.instantiationService.createInstance(ActionViewItem, undefined, action, options); diff --git a/patched-vscode/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css b/patched-vscode/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css index b1e39af1..781c090f 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css +++ b/patched-vscode/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css @@ -52,7 +52,7 @@ .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label::before, .monaco-workbench .part.auxiliarybar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label::before { position: absolute; - left: 6px; /* place icon in center */ + left: 5px; /* place icon in center */ } .monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, diff --git a/patched-vscode/src/vs/workbench/browser/parts/compositeBarActions.ts b/patched-vscode/src/vs/workbench/browser/parts/compositeBarActions.ts index de10721d..24b1b97b 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -319,11 +319,7 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { else if (badge instanceof NumberBadge) { if (badge.number) { let number = badge.number.toString(); - if (this.options.compact) { - if (badge.number > 99) { - number = ''; - } - } else if (badge.number > 999) { + if (badge.number > 999) { const noOfThousands = badge.number / 1000; const floor = Math.floor(noOfThousands); if (noOfThousands > floor) { @@ -332,6 +328,9 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { number = `${noOfThousands}K`; } } + if (this.options.compact && number.length >= 3) { + classes.push('compact-content'); + } this.badgeContent.textContent = number; show(this.badge); } diff --git a/patched-vscode/src/vs/workbench/browser/parts/compositePart.ts b/patched-vscode/src/vs/workbench/browser/parts/compositePart.ts index c86d7fd7..cbc47830 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/compositePart.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/compositePart.ts @@ -422,7 +422,7 @@ export abstract class CompositePart extends Part { const titleContainer = append(parent, $('.title-label')); const titleLabel = append(titleContainer, $('h2')); this.titleLabelElement = titleLabel; - const hover = this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), titleLabel, '')); + const hover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), titleLabel, '')); const $this = this; return { diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index e2fefb17..6bba2fcc 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -34,7 +34,6 @@ import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { ILabelService } from 'vs/platform/label/common/label'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { IOutline } from 'vs/workbench/services/outline/browser/outline'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; @@ -85,7 +84,7 @@ class OutlineItem extends BreadcrumbsItem { } const template = renderer.renderTemplate(container); - renderer.renderElement(>{ + renderer.renderElement({ element, children: [], depth: 0, diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editor.contribution.ts index dfb20dc6..753f4aa8 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -69,7 +69,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorHandler } from 'vs/workbench/services/untitled/common/untitledTextEditorHandler'; import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration'; -import { EditorActionsDefaultAction, EditorActionsTitleBarAction, HideEditorActionsAction, HideEditorTabsAction, ShowMultipleEditorTabsAction, ShowSingleEditorTabAction, ZenHideEditorTabsAction, ZenShowMultipleEditorTabsAction, ZenShowSingleEditorTabAction } from 'vs/workbench/browser/actions/layoutActions'; +import { ConfigureEditorAction, ConfigureEditorTabsAction, EditorActionsDefaultAction, EditorActionsTitleBarAction, HideEditorActionsAction, HideEditorTabsAction, ShowMultipleEditorTabsAction, ShowSingleEditorTabAction, ZenHideEditorTabsAction, ZenShowMultipleEditorTabsAction, ZenShowSingleEditorTabAction } from 'vs/workbench/browser/actions/layoutActions'; import { ICommandAction } from 'vs/platform/action/common/action'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -385,6 +385,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: EditorActionsTitleBarAction.ID, title: localize('titleBar', "Title Bar"), toggled: ContextKeyExpr.or(ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'titleBar'), ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none'), ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'default'))) }, group: '1_config', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: HideEditorActionsAction.ID, title: localize('hidden', "Hidden"), toggled: ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'hidden') }, group: '1_config', order: 30 }); +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: ConfigureEditorTabsAction.ID, title: localize('configureTabs', "Configure Tabs") }, group: '9_configure', order: 10 }); + // Editor Title Context Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize('close', "Close") }, group: '1_close', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeOthers', "Close Others"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 20 }); @@ -414,6 +416,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_KEEP_EDI 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: IsAuxiliaryEditorPartContext.toNegated() /* already a primary action for aux windows */ }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: ConfigureEditorAction.ID, title: localize('configureEditors', "Configure Editors") }, group: '9_configure', order: 10 }); function appendEditorToolItem(primary: ICommandAction, when: ContextKeyExpression | undefined, order: number, alternative?: ICommandAction, precondition?: ContextKeyExpression | undefined): void { const item: IMenuItem = { diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorActions.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorActions.ts index 67d4b717..3293be1a 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -13,7 +13,7 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { GoFilter, IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, resolveCommandsContext, getCommandsContext, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID as NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, resolveEditorsContext, getEditorsContext } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID as NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -34,9 +34,11 @@ import { IKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common import { ILogService } from 'vs/platform/log/common/log'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext, AuxiliaryBarVisibleContext, EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, MultipleEditorGroupsContext, SideBarVisibleContext } from 'vs/workbench/common/contextkeys'; -import { URI } from 'vs/base/common/uri'; import { getActiveDocument } from 'vs/base/browser/dom'; import { ICommandActionTitle } from 'vs/platform/action/common/action'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { resolveCommandsContext } from 'vs/workbench/browser/parts/editor/editorCommandsContext'; +import { IListService } from 'vs/platform/list/browser/listService'; class ExecuteCommandAction extends Action2 { @@ -61,12 +63,14 @@ abstract class AbstractSplitEditorAction extends Action2 { return preferredSideBySideGroupDirection(configurationService); } - override async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - const editorGroupService = accessor.get(IEditorGroupsService); + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const editorGroupsService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); - const commandContext = getCommandsContext(accessor, resourceOrContext, context); - splitEditor(editorGroupService, this.getDirection(configurationService), commandContext ? [commandContext] : undefined); + const direction = this.getDirection(configurationService); + const commandContext = resolveCommandsContext(args, accessor.get(IEditorService), editorGroupsService, accessor.get(IListService)); + + splitEditor(editorGroupsService, direction, commandContext); } } @@ -564,6 +568,8 @@ abstract class AbstractCloseAllAction extends Action2 { override async run(accessor: ServicesAccessor): Promise { const editorService = accessor.get(IEditorService); + const logService = accessor.get(ILogService); + const progressService = accessor.get(IProgressService); const editorGroupService = accessor.get(IEditorGroupsService); const filesConfigurationService = accessor.get(IFilesConfigurationService); const fileDialogService = accessor.get(IFileDialogService); @@ -636,7 +642,7 @@ abstract class AbstractCloseAllAction extends Action2 { case ConfirmResult.CANCEL: return; case ConfirmResult.DONT_SAVE: - await editorService.revert(editors, { soft: true }); + await this.revertEditors(editorService, logService, progressService, editors); break; case ConfirmResult.SAVE: await editorService.save(editors, { reason: SaveReason.EXPLICIT }); @@ -656,7 +662,7 @@ abstract class AbstractCloseAllAction extends Action2 { case ConfirmResult.CANCEL: return; case ConfirmResult.DONT_SAVE: - await editorService.revert(editors, { soft: true }); + await this.revertEditors(editorService, logService, progressService, editors); break; case ConfirmResult.SAVE: await editorService.save(editors, { reason: SaveReason.EXPLICIT }); @@ -686,6 +692,33 @@ abstract class AbstractCloseAllAction extends Action2 { return this.doCloseAll(editorGroupService); } + private revertEditors(editorService: IEditorService, logService: ILogService, progressService: IProgressService, editors: IEditorIdentifier[]): Promise { + return progressService.withProgress({ + location: ProgressLocation.Window, // use window progress to not be too annoying about this operation + delay: 800, // delay so that it only appears when operation takes a long time + title: localize('reverting', "Reverting Editors..."), + }, () => this.doRevertEditors(editorService, logService, editors)); + } + + private async doRevertEditors(editorService: IEditorService, logService: ILogService, editors: IEditorIdentifier[]): Promise { + try { + // We first attempt to revert all editors with `soft: false`, to ensure that + // working copies revert to their state on disk. Even though we close editors, + // it is possible that other parties hold a reference to the working copy + // and expect it to be in a certain state after the editor is closed without + // saving. + await editorService.revert(editors); + } catch (error) { + logService.error(error); + + // if that fails, since we are about to close the editor, we accept that + // the editor cannot be reverted and instead do a soft revert that just + // enables us to close the editor. With this, a user can always close a + // dirty editor even when reverting fails. + await editorService.revert(editors, { soft: true }); + } + } + private async revealEditorsToConfirm(editors: ReadonlyArray, editorGroupService: IEditorGroupsService): Promise { try { const handledGroups = new Set(); @@ -1139,11 +1172,13 @@ export class ToggleMaximizeEditorGroupAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { + override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { const editorGroupsService = accessor.get(IEditorGroupsService); - const { group } = resolveCommandsContext(editorGroupsService, getCommandsContext(accessor, resourceOrContext, context)); - editorGroupsService.toggleMaximizeGroup(group); + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), editorGroupsService, accessor.get(IListService)); + if (resolvedContext.groupedEditors.length) { + editorGroupsService.toggleMaximizeGroup(resolvedContext.groupedEditors[0].group); + } } } @@ -2508,21 +2543,24 @@ abstract class BaseMoveCopyEditorToNewWindowAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) { + override async run(accessor: ServicesAccessor, ...args: unknown[]) { const editorGroupService = accessor.get(IEditorGroupsService); - const editorsContext = resolveEditorsContext(getEditorsContext(accessor, resourceOrContext, context)); - if (editorsContext.length === 0) { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), editorGroupService, accessor.get(IListService)); + if (!resolvedContext.groupedEditors.length) { return; } const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart(); - const sourceGroup = editorsContext[0].group; // only single group supported for move/copy for now - const sourceEditors = editorsContext.filter(({ group }) => group === sourceGroup); + // only single group supported for move/copy for now + const { group, editors } = resolvedContext.groupedEditors[0]; + const options = { preserveFocus: resolvedContext.preserveFocus }; + const editorsWithOptions = editors.map(editor => ({ editor, options })); + if (this.move) { - sourceGroup.moveEditors(sourceEditors, auxiliaryEditorPart.activeGroup); + group.moveEditors(editorsWithOptions, auxiliaryEditorPart.activeGroup); } else { - sourceGroup.copyEditors(sourceEditors, auxiliaryEditorPart.activeGroup); + group.copyEditors(editorsWithOptions, auxiliaryEditorPart.activeGroup); } auxiliaryEditorPart.activeGroup.focus(); diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index 2b3de674..32f66e1d 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -84,7 +84,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution ) { this.discardAutoSave(workingCopyResult.workingCopy); - this.logService.info(`[editor auto save] running auto save from condition change event`, workingCopyResult.workingCopy.resource.toString(), workingCopyResult.workingCopy.typeId); + this.logService.trace(`[editor auto save] running auto save from condition change event`, workingCopyResult.workingCopy.resource.toString(), workingCopyResult.workingCopy.typeId); workingCopyResult.workingCopy.save({ reason: workingCopyResult.reason }); } } @@ -100,7 +100,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution ) { this.waitingOnConditionAutoSaveEditors.delete(resource); - this.logService.info(`[editor auto save] running auto save from condition change event with reason ${editorResult.reason}`); + this.logService.trace(`[editor auto save] running auto save from condition change event with reason ${editorResult.reason}`); this.editorService.save(editorResult.editor, { reason: editorResult.reason }); } } diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorCommands.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorCommands.ts index c8670de6..10405ef3 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -3,13 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveElement } from 'vs/base/browser/dom'; -import { List } from 'vs/base/browser/ui/list/listWidget'; -import { coalesce, distinct } from 'vs/base/common/arrays'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Schemas, matchesScheme } from 'vs/base/common/network'; -import { extname, isEqual } from 'vs/base/common/resources'; +import { extname } from 'vs/base/common/resources'; import { isNumber, isObject, isString, isUndefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -31,17 +28,18 @@ import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/br import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys'; -import { CloseDirection, EditorInputCapabilities, EditorsOrder, IEditorCommandsContext, IEditorIdentifier, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IVisibleEditorPane, isEditorIdentifier, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; +import { CloseDirection, EditorInputCapabilities, EditorsOrder, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IVisibleEditorPane, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { EditorGroupColumn, columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; -import { EditorGroupLayout, GroupDirection, GroupLocation, GroupsOrder, IEditorGroup, IEditorGroupsService, IEditorReplacement, isEditorGroup, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorGroupLayout, GroupDirection, GroupLocation, GroupsOrder, IEditorGroup, IEditorGroupsService, IEditorReplacement, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { DIFF_FOCUS_OTHER_SIDE, DIFF_FOCUS_PRIMARY_SIDE, DIFF_FOCUS_SECONDARY_SIDE, DIFF_OPEN_SIDE, registerDiffEditorCommands } from './diffEditorCommands'; +import { IResolvedEditorCommandsContext, resolveCommandsContext } from 'vs/workbench/browser/parts/editor/editorCommandsContext'; export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; @@ -242,7 +240,7 @@ function registerActiveEditorMoveCopyCommand(): void { } function moveCopyActiveEditorToGroup(isMove: boolean, args: ActiveEditorMoveCopyArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void { - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); const sourceGroup = control.group; @@ -250,49 +248,49 @@ function registerActiveEditorMoveCopyCommand(): void { switch (args.to) { case 'left': - targetGroup = editorGroupService.findGroup({ direction: GroupDirection.LEFT }, sourceGroup); + targetGroup = editorGroupsService.findGroup({ direction: GroupDirection.LEFT }, sourceGroup); if (!targetGroup) { - targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.LEFT); + targetGroup = editorGroupsService.addGroup(sourceGroup, GroupDirection.LEFT); } break; case 'right': - targetGroup = editorGroupService.findGroup({ direction: GroupDirection.RIGHT }, sourceGroup); + targetGroup = editorGroupsService.findGroup({ direction: GroupDirection.RIGHT }, sourceGroup); if (!targetGroup) { - targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.RIGHT); + targetGroup = editorGroupsService.addGroup(sourceGroup, GroupDirection.RIGHT); } break; case 'up': - targetGroup = editorGroupService.findGroup({ direction: GroupDirection.UP }, sourceGroup); + targetGroup = editorGroupsService.findGroup({ direction: GroupDirection.UP }, sourceGroup); if (!targetGroup) { - targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.UP); + targetGroup = editorGroupsService.addGroup(sourceGroup, GroupDirection.UP); } break; case 'down': - targetGroup = editorGroupService.findGroup({ direction: GroupDirection.DOWN }, sourceGroup); + targetGroup = editorGroupsService.findGroup({ direction: GroupDirection.DOWN }, sourceGroup); if (!targetGroup) { - targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.DOWN); + targetGroup = editorGroupsService.addGroup(sourceGroup, GroupDirection.DOWN); } break; case 'first': - targetGroup = editorGroupService.findGroup({ location: GroupLocation.FIRST }, sourceGroup); + targetGroup = editorGroupsService.findGroup({ location: GroupLocation.FIRST }, sourceGroup); break; case 'last': - targetGroup = editorGroupService.findGroup({ location: GroupLocation.LAST }, sourceGroup); + targetGroup = editorGroupsService.findGroup({ location: GroupLocation.LAST }, sourceGroup); break; case 'previous': - targetGroup = editorGroupService.findGroup({ location: GroupLocation.PREVIOUS }, sourceGroup); + targetGroup = editorGroupsService.findGroup({ location: GroupLocation.PREVIOUS }, sourceGroup); break; case 'next': - targetGroup = editorGroupService.findGroup({ location: GroupLocation.NEXT }, sourceGroup); + targetGroup = editorGroupsService.findGroup({ location: GroupLocation.NEXT }, sourceGroup); if (!targetGroup) { - targetGroup = editorGroupService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService)); + targetGroup = editorGroupsService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService)); } break; case 'center': - targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupService.count / 2) - 1]; + targetGroup = editorGroupsService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupsService.count / 2) - 1]; break; case 'position': - targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(args.value ?? 1) - 1]; + targetGroup = editorGroupsService.getGroups(GroupsOrder.GRID_APPEARANCE)[(args.value ?? 1) - 1]; break; } @@ -314,8 +312,8 @@ function registerEditorGroupsLayoutCommands(): void { return; } - const editorGroupService = accessor.get(IEditorGroupsService); - editorGroupService.applyLayout(layout); + const editorGroupsService = accessor.get(IEditorGroupsService); + editorGroupsService.applyLayout(layout); } CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, (accessor: ServicesAccessor, args: EditorGroupLayout) => { @@ -352,9 +350,9 @@ function registerEditorGroupsLayoutCommands(): void { CommandsRegistry.registerCommand({ id: 'vscode.getEditorLayout', handler: (accessor: ServicesAccessor) => { - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); - return editorGroupService.getLayout(); + return editorGroupsService.getLayout(); }, metadata: { description: 'Get Editor Layout', @@ -392,7 +390,7 @@ function registerOpenEditorAPICommands(): void { CommandsRegistry.registerCommand(API_OPEN_EDITOR_COMMAND_ID, async function (accessor: ServicesAccessor, resourceArg: UriComponents | string, columnAndOptions?: [EditorGroupColumn?, ITextEditorOptions?], label?: string, context?: IOpenEvent) { const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); const openerService = accessor.get(IOpenerService); const pathService = accessor.get(IPathService); const configurationService = accessor.get(IConfigurationService); @@ -421,7 +419,7 @@ function registerOpenEditorAPICommands(): void { input = { resource, options, label }; } - await editorService.openEditor(input, columnToEditorGroup(editorGroupService, configurationService, column)); + await editorService.openEditor(input, columnToEditorGroup(editorGroupsService, configurationService, column)); } // do not allow to execute commands from here @@ -454,7 +452,7 @@ function registerOpenEditorAPICommands(): void { CommandsRegistry.registerCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, async function (accessor: ServicesAccessor, originalResource: UriComponents, modifiedResource: UriComponents, labelAndOrDescription?: string | { label: string; description: string }, columnAndOptions?: [EditorGroupColumn?, ITextEditorOptions?], context?: IOpenEvent) { const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); const [columnArg, optionsArg] = columnAndOptions ?? []; @@ -475,7 +473,7 @@ function registerOpenEditorAPICommands(): void { label, description, options - }, columnToEditorGroup(editorGroupService, configurationService, column)); + }, columnToEditorGroup(editorGroupsService, configurationService, column)); }); CommandsRegistry.registerCommand(API_OPEN_WITH_EDITOR_COMMAND_ID, async (accessor: ServicesAccessor, resource: UriComponents, id: string, columnAndOptions?: [EditorGroupColumn?, ITextEditorOptions?]) => { @@ -596,30 +594,30 @@ function registerFocusEditorGroupAtIndexCommands(): void { when: undefined, primary: KeyMod.CtrlCmd | toKeyCode(groupIndex), handler: accessor => { - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); // To keep backwards compatibility (pre-grid), allow to focus a group // that does not exist as long as it is the next group after the last // opened group. Otherwise we return. - if (groupIndex > editorGroupService.count) { + if (groupIndex > editorGroupsService.count) { return; } // Group exists: just focus - const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE); + const groups = editorGroupsService.getGroups(GroupsOrder.GRID_APPEARANCE); if (groups[groupIndex]) { return groups[groupIndex].focus(); } // Group does not exist: create new by splitting the active one of the last group const direction = preferredSideBySideGroupDirection(configurationService); - const lastGroup = editorGroupService.findGroup({ location: GroupLocation.LAST }); + const lastGroup = editorGroupsService.findGroup({ location: GroupLocation.LAST }); if (!lastGroup) { return; } - const newGroup = editorGroupService.addGroup(lastGroup, direction); + const newGroup = editorGroupsService.addGroup(lastGroup, direction); // Focus newGroup.focus(); @@ -656,50 +654,25 @@ function registerFocusEditorGroupAtIndexCommands(): void { } } -export function splitEditor(editorGroupService: IEditorGroupsService, direction: GroupDirection, contexts?: IEditorCommandsContext[]): void { - let newGroup: IEditorGroup | undefined; - let sourceGroup: IEditorGroup | undefined; - - for (const context of contexts ?? [undefined]) { - let currentGroup: IEditorGroup | undefined; - - if (context) { - currentGroup = editorGroupService.getGroup(context.groupId); - } else { - currentGroup = editorGroupService.activeGroup; - } - - if (!currentGroup) { - continue; - } - - if (!sourceGroup) { - sourceGroup = currentGroup; - } else if (sourceGroup.id !== currentGroup.id) { - continue; // Only support splitting from the same group - } +export function splitEditor(editorGroupsService: IEditorGroupsService, direction: GroupDirection, resolvedContext: IResolvedEditorCommandsContext): void { + if (!resolvedContext.groupedEditors.length) { + return; + } - // Add group - if (!newGroup) { - newGroup = editorGroupService.addGroup(currentGroup, direction); - } + // Only support splitting from one source group + const { group, editors } = resolvedContext.groupedEditors[0]; + const preserveFocus = resolvedContext.preserveFocus; + const newGroup = editorGroupsService.addGroup(group, direction); + for (const editorToCopy of editors) { // Split editor (if it can be split) - let editorToCopy: EditorInput | undefined; - if (context && typeof context.editorIndex === 'number') { - editorToCopy = currentGroup.getEditorByIndex(context.editorIndex); - } else { - editorToCopy = currentGroup.activeEditor ?? undefined; - } - - // Copy the editor to the new group, else create an empty group if (editorToCopy && !editorToCopy.hasCapability(EditorInputCapabilities.Singleton)) { - currentGroup.copyEditor(editorToCopy, newGroup, { preserveFocus: context?.preserveFocus }); + group.copyEditor(editorToCopy, newGroup, { preserveFocus }); } } // Focus - newGroup?.focus(); + newGroup.focus(); } function registerSplitEditorCommands() { @@ -709,9 +682,9 @@ function registerSplitEditorCommands() { { id: SPLIT_EDITOR_LEFT, direction: GroupDirection.LEFT }, { id: SPLIT_EDITOR_RIGHT, direction: GroupDirection.RIGHT } ].forEach(({ id, direction }) => { - CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) { - const { editors } = getEditorsContext(accessor, resourceOrContext, context); - splitEditor(accessor.get(IEditorGroupsService), direction, editors); + CommandsRegistry.registerCommand(id, function (accessor, ...args) { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + splitEditor(accessor.get(IEditorGroupsService), direction, resolvedContext); }); }); } @@ -721,14 +694,14 @@ function registerCloseEditorCommands() { // A special handler for "Close Editor" depending on context // - keybindining: do not close sticky editors, rather open the next non-sticky editor // - menu: always close editor, even sticky ones - function closeEditorHandler(accessor: ServicesAccessor, forceCloseStickyEditors: boolean, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { + function closeEditorHandler(accessor: ServicesAccessor, forceCloseStickyEditors: boolean, ...args: unknown[]): Promise { const editorGroupsService = accessor.get(IEditorGroupsService); const editorService = accessor.get(IEditorService); let keepStickyEditors: boolean | undefined = undefined; if (forceCloseStickyEditors) { keepStickyEditors = false; // explicitly close sticky editors - } else if (resourceOrContext || context) { + } else if (args.length) { keepStickyEditors = false; // we have a context, as such this command was used e.g. from the tab context menu } else { keepStickyEditors = editorGroupsService.partOptions.preventPinnedEditorClose === 'keyboard' || editorGroupsService.partOptions.preventPinnedEditorClose === 'keyboardAndMouse'; // respect setting otherwise @@ -756,17 +729,12 @@ function registerCloseEditorCommands() { } // With context: proceed to close editors as instructed - const { editors, groups } = getEditorsContext(accessor, resourceOrContext, context); + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + const preserveFocus = resolvedContext.preserveFocus; - return Promise.all(groups.map(async group => { - if (group) { - const editorsToClose = coalesce(editors - .filter(editor => editor.groupId === group.id) - .map(editor => typeof editor.editorIndex === 'number' ? group.getEditorByIndex(editor.editorIndex) : group.activeEditor)) - .filter(editor => !keepStickyEditors || !group.isSticky(editor)); - - await group.closeEditors(editorsToClose, { preserveFocus: context?.preserveFocus }); - } + return Promise.all(resolvedContext.groupedEditors.map(async ({ group, editors }) => { + const editorsToClose = editors.filter(editor => !keepStickyEditors || !group.isSticky(editor)); + await group.closeEditors(editorsToClose, { preserveFocus }); })); } @@ -776,13 +744,13 @@ function registerCloseEditorCommands() { when: undefined, primary: KeyMod.CtrlCmd | KeyCode.KeyW, win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KeyW] }, - handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - return closeEditorHandler(accessor, false, resourceOrContext, context); + handler: (accessor, ...args: unknown[]) => { + return closeEditorHandler(accessor, false, ...args); } }); - CommandsRegistry.registerCommand(CLOSE_PINNED_EDITOR_COMMAND_ID, (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - return closeEditorHandler(accessor, true /* force close pinned editors */, resourceOrContext, context); + CommandsRegistry.registerCommand(CLOSE_PINNED_EDITOR_COMMAND_ID, (accessor, ...args: unknown[]) => { + return closeEditorHandler(accessor, true /* force close pinned editors */, ...args); }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -790,12 +758,10 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyW), - handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - return Promise.all(getEditorsContext(accessor, resourceOrContext, context).groups.map(async group => { - if (group) { - await group.closeAllEditors({ excludeSticky: true }); - return; - } + handler: (accessor, ...args: unknown[]) => { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + return Promise.all(resolvedContext.groupedEditors.map(async ({ group }) => { + await group.closeAllEditors({ excludeSticky: true }); })); } }); @@ -806,19 +772,12 @@ function registerCloseEditorCommands() { when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext), primary: KeyMod.CtrlCmd | KeyCode.KeyW, win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KeyW] }, - handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); - const commandsContext = getCommandsContext(accessor, resourceOrContext, context); + handler: (accessor, ...args: unknown[]) => { + const editorGroupsService = accessor.get(IEditorGroupsService); + const commandsContext = resolveCommandsContext(args, accessor.get(IEditorService), editorGroupsService, accessor.get(IListService)); - let group: IEditorGroup | undefined; - if (commandsContext && typeof commandsContext.groupId === 'number') { - group = editorGroupService.getGroup(commandsContext.groupId); - } else { - group = editorGroupService.activeGroup; - } - - if (group) { - editorGroupService.removeGroup(group); + if (commandsContext.groupedEditors.length) { + editorGroupsService.removeGroup(commandsContext.groupedEditors[0].group); } } }); @@ -828,11 +787,10 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyU), - handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - return Promise.all(getEditorsContext(accessor, resourceOrContext, context).groups.map(async group => { - if (group) { - await group.closeEditors({ savedOnly: true, excludeSticky: true }, { preserveFocus: context?.preserveFocus }); - } + handler: (accessor, ...args: unknown[]) => { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + return Promise.all(resolvedContext.groupedEditors.map(async ({ group }) => { + await group.closeEditors({ savedOnly: true, excludeSticky: true }, { preserveFocus: resolvedContext.preserveFocus }); })); } }); @@ -843,24 +801,19 @@ function registerCloseEditorCommands() { when: undefined, primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyT }, - handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const { editors, groups } = getEditorsContext(accessor, resourceOrContext, context); - return Promise.all(groups.map(async group => { - if (group) { - const editorsToKeep = editors - .filter(editor => editor.groupId === group.id) - .map(editor => typeof editor.editorIndex === 'number' ? group.getEditorByIndex(editor.editorIndex) : group.activeEditor); - - const editorsToClose = group.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }).filter(editor => !editorsToKeep.includes(editor)); - - for (const editorToKeep of editorsToKeep) { - if (editorToKeep) { - group.pinEditor(editorToKeep); - } - } + handler: (accessor, ...args: unknown[]) => { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + + return Promise.all(resolvedContext.groupedEditors.map(async ({ group, editors }) => { + const editorsToClose = group.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }).filter(editor => !editors.includes(editor)); - await group.closeEditors(editorsToClose, { preserveFocus: context?.preserveFocus }); + for (const editorToKeep of editors) { + if (editorToKeep) { + group.pinEditor(editorToKeep); + } } + + await group.closeEditors(editorsToClose, { preserveFocus: resolvedContext.preserveFocus }); })); } }); @@ -870,16 +823,15 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); - - const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(accessor, resourceOrContext, context)); - if (group && editor) { + handler: async (accessor, ...args: unknown[]) => { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + if (resolvedContext.groupedEditors.length) { + const { group, editors } = resolvedContext.groupedEditors[0]; if (group.activeEditor) { group.pinEditor(group.activeEditor); } - await group.closeEditors({ direction: CloseDirection.RIGHT, except: editor, excludeSticky: true }, { preserveFocus: context?.preserveFocus }); + await group.closeEditors({ direction: CloseDirection.RIGHT, except: editors[0], excludeSticky: true }, { preserveFocus: resolvedContext.preserveFocus }); } } }); @@ -889,62 +841,64 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, ...args: unknown[]) => { const editorService = accessor.get(IEditorService); const editorResolverService = accessor.get(IEditorResolverService); const telemetryService = accessor.get(ITelemetryService); - const editorsAndGroup = resolveEditorsContext(getEditorsContext(accessor, resourceOrContext, context)); + const resolvedContext = resolveCommandsContext(args, editorService, accessor.get(IEditorGroupsService), accessor.get(IListService)); const editorReplacements = new Map(); - for (const { editor, group } of editorsAndGroup) { - const untypedEditor = editor.toUntyped(); - if (!untypedEditor) { - return; // Resolver can only resolve untyped editors - } + for (const { group, editors } of resolvedContext.groupedEditors) { + for (const editor of editors) { + const untypedEditor = editor.toUntyped(); + if (!untypedEditor) { + return; // Resolver can only resolve untyped editors + } - untypedEditor.options = { ...editorService.activeEditorPane?.options, override: EditorResolution.PICK }; - const resolvedEditor = await editorResolverService.resolveEditor(untypedEditor, group); - if (!isEditorInputWithOptionsAndGroup(resolvedEditor)) { - return; - } + untypedEditor.options = { ...editorService.activeEditorPane?.options, override: EditorResolution.PICK }; + const resolvedEditor = await editorResolverService.resolveEditor(untypedEditor, group); + if (!isEditorInputWithOptionsAndGroup(resolvedEditor)) { + return; + } - let editorReplacementsInGroup = editorReplacements.get(group); - if (!editorReplacementsInGroup) { - editorReplacementsInGroup = []; - editorReplacements.set(group, editorReplacementsInGroup); - } + let editorReplacementsInGroup = editorReplacements.get(group); + if (!editorReplacementsInGroup) { + editorReplacementsInGroup = []; + editorReplacements.set(group, editorReplacementsInGroup); + } - editorReplacementsInGroup.push({ - editor: editor, - replacement: resolvedEditor.editor, - forceReplaceDirty: editor.resource?.scheme === Schemas.untitled, - options: resolvedEditor.options - }); - - // Telemetry - type WorkbenchEditorReopenClassification = { - owner: 'rebornix'; - comment: 'Identify how a document is reopened'; - scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the resource' }; - ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the resource' }; - from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The editor view type the resource is switched from' }; - to: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The editor view type the resource is switched to' }; - }; - - type WorkbenchEditorReopenEvent = { - scheme: string; - ext: string; - from: string; - to: string; - }; - - telemetryService.publicLog2('workbenchEditorReopen', { - scheme: editor.resource?.scheme ?? '', - ext: editor.resource ? extname(editor.resource) : '', - from: editor.editorId ?? '', - to: resolvedEditor.editor.editorId ?? '' - }); + editorReplacementsInGroup.push({ + editor: editor, + replacement: resolvedEditor.editor, + forceReplaceDirty: editor.resource?.scheme === Schemas.untitled, + options: resolvedEditor.options + }); + + // Telemetry + type WorkbenchEditorReopenClassification = { + owner: 'rebornix'; + comment: 'Identify how a document is reopened'; + scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the resource' }; + ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the resource' }; + from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The editor view type the resource is switched from' }; + to: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The editor view type the resource is switched to' }; + }; + + type WorkbenchEditorReopenEvent = { + scheme: string; + ext: string; + from: string; + to: string; + }; + + telemetryService.publicLog2('workbenchEditorReopen', { + scheme: editor.resource?.scheme ?? '', + ext: editor.resource ? extname(editor.resource) : '', + from: editor.editorId ?? '', + to: resolvedEditor.editor.editorId ?? '' + }); + } } // Replace editor with resolved one and make active @@ -955,15 +909,16 @@ function registerCloseEditorCommands() { } }); - CommandsRegistry.registerCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, async (accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); + CommandsRegistry.registerCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, async (accessor: ServicesAccessor, ...args: unknown[]) => { + const editorGroupsService = accessor.get(IEditorGroupsService); - const { group } = resolveCommandsContext(editorGroupService, getCommandsContext(accessor, resourceOrContext, context)); - if (group) { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), editorGroupsService, accessor.get(IListService)); + if (resolvedContext.groupedEditors.length) { + const { group } = resolvedContext.groupedEditors[0]; await group.closeAllEditors(); - if (group.count === 0 && editorGroupService.getGroup(group.id) /* could be gone by now */) { - editorGroupService.removeGroup(group); // only remove group if it is now empty + if (group.count === 0 && editorGroupsService.getGroup(group.id) /* could be gone by now */) { + editorGroupsService.removeGroup(group); // only remove group if it is now empty } } }); @@ -992,9 +947,9 @@ function registerFocusEditorGroupWihoutWrapCommands(): void { for (const command of commands) { CommandsRegistry.registerCommand(command.id, async (accessor: ServicesAccessor) => { - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); - const group = editorGroupService.findGroup({ direction: command.direction }, editorGroupService.activeGroup, false); + const group = editorGroupsService.findGroup({ direction: command.direction }, editorGroupsService.activeGroup, false); group?.focus(); }); } @@ -1002,11 +957,15 @@ function registerFocusEditorGroupWihoutWrapCommands(): void { function registerSplitEditorInGroupCommands(): void { - async function splitEditorInGroup(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - const editorGroupService = accessor.get(IEditorGroupsService); + async function splitEditorInGroup(accessor: ServicesAccessor, resolvedContext: IResolvedEditorCommandsContext): Promise { const instantiationService = accessor.get(IInstantiationService); - const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(accessor, resourceOrContext, context)); + if (!resolvedContext.groupedEditors.length) { + return; + } + + const { group, editors } = resolvedContext.groupedEditors[0]; + const editor = editors[0]; if (!editor) { return; } @@ -1033,15 +992,22 @@ function registerSplitEditorInGroupCommands(): void { } }); } - run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - return splitEditorInGroup(accessor, resourceOrContext, context); + run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + return splitEditorInGroup(accessor, resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService))); } }); - async function joinEditorInGroup(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - const editorGroupService = accessor.get(IEditorGroupsService); + async function joinEditorInGroup(resolvedContext: IResolvedEditorCommandsContext): Promise { + if (!resolvedContext.groupedEditors.length) { + return; + } + + const { group, editors } = resolvedContext.groupedEditors[0]; + const editor = editors[0]; + if (!editor) { + return; + } - const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(accessor, resourceOrContext, context)); if (!(editor instanceof SideBySideEditorInput)) { return; } @@ -1079,8 +1045,8 @@ function registerSplitEditorInGroupCommands(): void { } }); } - run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - return joinEditorInGroup(accessor, resourceOrContext, context); + run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + return joinEditorInGroup(resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService))); } }); @@ -1094,14 +1060,18 @@ function registerSplitEditorInGroupCommands(): void { f1: true }); } - async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - const editorGroupService = accessor.get(IEditorGroupsService); + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + if (!resolvedContext.groupedEditors.length) { + return; + } + + const { editors } = resolvedContext.groupedEditors[0]; - const { editor } = resolveCommandsContext(editorGroupService, getCommandsContext(accessor, resourceOrContext, context)); - if (editor instanceof SideBySideEditorInput) { - await joinEditorInGroup(accessor, resourceOrContext, context); - } else if (editor) { - await splitEditorInGroup(accessor, resourceOrContext, context); + if (editors[0] instanceof SideBySideEditorInput) { + await joinEditorInGroup(resolvedContext); + } else if (editors[0]) { + await splitEditorInGroup(accessor, resolvedContext); } } }); @@ -1215,12 +1185,12 @@ function registerOtherEditorCommands(): void { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.Enter), - handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); - - const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(accessor, resourceOrContext, context)); - if (group && editor) { - return group.pinEditor(editor); + handler: async (accessor, ...args: unknown[]) => { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + for (const { group, editors } of resolvedContext.groupedEditors) { + for (const editor of editors) { + group.pinEditor(editor); + } } } }); @@ -1236,10 +1206,9 @@ function registerOtherEditorCommands(): void { } }); - function setEditorGroupLock(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext, locked?: boolean): void { - const editorGroupService = accessor.get(IEditorGroupsService); - - const { group } = resolveCommandsContext(editorGroupService, getCommandsContext(accessor, resourceOrContext, context)); + function setEditorGroupLock(accessor: ServicesAccessor, locked: boolean | undefined, ...args: unknown[]): void { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + const group = resolvedContext.groupedEditors[0]?.group; group?.lock(locked ?? !group.isLocked); } @@ -1252,8 +1221,8 @@ function registerOtherEditorCommands(): void { f1: true }); } - async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - setEditorGroupLock(accessor, resourceOrContext, context); + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + setEditorGroupLock(accessor, undefined, ...args); } }); @@ -1267,8 +1236,8 @@ function registerOtherEditorCommands(): void { f1: true }); } - async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - setEditorGroupLock(accessor, resourceOrContext, context, true); + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + setEditorGroupLock(accessor, true, ...args); } }); @@ -1282,8 +1251,8 @@ function registerOtherEditorCommands(): void { f1: true }); } - async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - setEditorGroupLock(accessor, resourceOrContext, context, false); + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + setEditorGroupLock(accessor, false, ...args); } }); @@ -1292,9 +1261,12 @@ function registerOtherEditorCommands(): void { weight: KeybindingWeight.WorkbenchContrib, when: ActiveEditorStickyContext.toNegated(), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.Shift | KeyCode.Enter), - handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - for (const { editor, group } of resolveEditorsContext(getEditorsContext(accessor, resourceOrContext, context))) { - group.stickEditor(editor); + handler: async (accessor, ...args: unknown[]) => { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + for (const { group, editors } of resolvedContext.groupedEditors) { + for (const editor of editors) { + group.stickEditor(editor); + } } } }); @@ -1306,7 +1278,7 @@ function registerOtherEditorCommands(): void { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.Shift | KeyCode.KeyO), handler: async accessor => { const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); const activeEditor = editorService.activeEditor; const activeTextEditorControl = editorService.activeTextEditorControl; @@ -1322,7 +1294,7 @@ function registerOtherEditorCommands(): void { editor = activeEditor.modified; } - return editorGroupService.activeGroup.openEditor(editor); + return editorGroupsService.activeGroup.openEditor(editor); } }); @@ -1331,9 +1303,12 @@ function registerOtherEditorCommands(): void { weight: KeybindingWeight.WorkbenchContrib, when: ActiveEditorStickyContext, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.Shift | KeyCode.Enter), - handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - for (const { editor, group } of resolveEditorsContext(getEditorsContext(accessor, resourceOrContext, context))) { - group.unstickEditor(editor); + handler: async (accessor, ...args: unknown[]) => { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + for (const { group, editors } of resolvedContext.groupedEditors) { + for (const editor of editors) { + group.unstickEditor(editor); + } } } }); @@ -1343,16 +1318,14 @@ function registerOtherEditorCommands(): void { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); + handler: (accessor, ...args: unknown[]) => { + const editorGroupsService = accessor.get(IEditorGroupsService); const quickInputService = accessor.get(IQuickInputService); - const commandsContext = getCommandsContext(accessor, resourceOrContext, context); - if (commandsContext && typeof commandsContext.groupId === 'number') { - const group = editorGroupService.getGroup(commandsContext.groupId); - if (group) { - editorGroupService.activateGroup(group); // we need the group to be active - } + const commandsContext = resolveCommandsContext(args, accessor.get(IEditorService), editorGroupsService, accessor.get(IListService)); + const group = commandsContext.groupedEditors[0]?.group; + if (group) { + editorGroupsService.activateGroup(group); // we need the group to be active } return quickInputService.quickAccess.show(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX); @@ -1360,130 +1333,6 @@ function registerOtherEditorCommands(): void { }); } -type EditorsContext = { editors: IEditorCommandsContext[]; groups: Array }; -export function getEditorsContext(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): EditorsContext { - const editorGroupService = accessor.get(IEditorGroupsService); - const listService = accessor.get(IListService); - - const editorContext = getMultiSelectedEditorContexts(getCommandsContext(accessor, resourceOrContext, context), listService, editorGroupService); - - const activeGroup = editorGroupService.activeGroup; - if (editorContext.length === 0 && activeGroup.activeEditor) { - // add the active editor as fallback - editorContext.push({ - groupId: activeGroup.id, - editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) - }); - } - - return { - editors: editorContext, - groups: distinct(editorContext.map(context => context.groupId)).map(groupId => editorGroupService.getGroup(groupId)) - }; -} - -export function resolveEditorsContext(context: EditorsContext): { editor: EditorInput; group: IEditorGroup }[] { - const { editors, groups } = context; - - const editorsAndGroup = editors.map(e => { - if (e.editorIndex === undefined) { - return undefined; - } - const group = groups.find(group => group && group.id === e.groupId); - const editor = group?.getEditorByIndex(e.editorIndex); - if (!editor || !group) { - return undefined; - } - return { editor, group }; - }); - - return coalesce(editorsAndGroup); -} - -export function getCommandsContext(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined { - const isUri = URI.isUri(resourceOrContext); - - const editorCommandsContext = isUri ? context : resourceOrContext ? resourceOrContext : context; - if (editorCommandsContext) { - return editorCommandsContext; - } - - if (isUri) { - const editorGroupService = accessor.get(IEditorGroupsService); - const editorGroup = editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => isEqual(group.activeEditor?.resource, resourceOrContext)); - if (editorGroup) { - return { groupId: editorGroup.index, editorIndex: editorGroup.getIndexOfEditor(editorGroup.activeEditor!) }; - } - } - - return undefined; -} - -export function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup; editor?: EditorInput } { - - // Resolve from context - let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; - let editor = group && context && typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) ?? undefined : undefined; - - // Fallback to active group as needed - if (!group) { - group = editorGroupService.activeGroup; - } - - // Fallback to active editor as needed - if (!editor) { - editor = group.activeEditor ?? undefined; - } - - return { group, editor }; -} - -export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext | undefined, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] { - - // First check for a focused list to return the selected items from - const list = listService.lastFocusedList; - if (list instanceof List && list.getHTMLElement() === getActiveElement()) { - const elementToContext = (element: IEditorIdentifier | IEditorGroup) => { - if (isEditorGroup(element)) { - return { groupId: element.id, editorIndex: undefined }; - } - - const group = editorGroupService.getGroup(element.groupId); - - return { groupId: element.groupId, editorIndex: group ? group.getIndexOfEditor(element.editor) : -1 }; - }; - - const onlyEditorGroupAndEditor = (e: IEditorIdentifier | IEditorGroup) => isEditorGroup(e) || isEditorIdentifier(e); - - const focusedElements: Array = list.getFocusedElements().filter(onlyEditorGroupAndEditor); - const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : undefined; // need to take into account when editor context is { group: group } - - if (focus) { - const selection: Array = list.getSelectedElements().filter(onlyEditorGroupAndEditor); - - if (selection.length > 1) { - return selection.map(elementToContext); - } - - return [focus]; - } - } - // Check editors selected in the group (tabs) - else { - const group = editorContext ? editorGroupService.getGroup(editorContext.groupId) : editorGroupService.activeGroup; - const editor = editorContext && editorContext.editorIndex !== undefined ? group?.getEditorByIndex(editorContext.editorIndex) : group?.activeEditor; - // If the editor is selected, return all selected editors otherwise only use the editors context - if (group && editor) { - if (group.isSelected(editor)) { - return group.selectedEditors.map(se => ({ groupId: group.id, editorIndex: group.getIndexOfEditor(se) })); - } - } - } - - // Otherwise go with passed in context - return !!editorContext ? [editorContext] : []; -} - export function setup(): void { registerActiveEditorMoveCopyCommand(); registerEditorGroupsLayoutCommands(); diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorCommandsContext.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorCommandsContext.ts new file mode 100644 index 00000000..b9fea217 --- /dev/null +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorCommandsContext.ts @@ -0,0 +1,200 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getActiveElement } from 'vs/base/browser/dom'; +import { List } from 'vs/base/browser/ui/list/listWidget'; +import { URI } from 'vs/base/common/uri'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { IEditorCommandsContext, isEditorCommandsContext, IEditorIdentifier, isEditorIdentifier } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IEditorGroup, IEditorGroupsService, isEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export interface IResolvedEditorCommandsContext { + readonly groupedEditors: { + readonly group: IEditorGroup; + readonly editors: EditorInput[]; + }[]; + readonly preserveFocus: boolean; +} + +export function resolveCommandsContext(commandArgs: unknown[], editorService: IEditorService, editorGroupsService: IEditorGroupsService, listService: IListService): IResolvedEditorCommandsContext { + + const commandContext = getCommandsContext(commandArgs, editorService, editorGroupsService, listService); + const preserveFocus = commandContext.length ? commandContext[0].preserveFocus || false : false; + const resolvedContext: IResolvedEditorCommandsContext = { groupedEditors: [], preserveFocus }; + + for (const editorContext of commandContext) { + const groupAndEditor = getEditorAndGroupFromContext(editorContext, editorGroupsService); + if (!groupAndEditor) { + continue; + } + + const { group, editor } = groupAndEditor; + + // Find group context if already added + let groupContext = undefined; + for (const targetGroupContext of resolvedContext.groupedEditors) { + if (targetGroupContext.group.id === group.id) { + groupContext = targetGroupContext; + break; + } + } + + // Otherwise add new group context + if (!groupContext) { + groupContext = { group, editors: [] }; + resolvedContext.groupedEditors.push(groupContext); + } + + // Add editor to group context + if (editor) { + groupContext.editors.push(editor); + } + } + + return resolvedContext; +} + +function getCommandsContext(commandArgs: unknown[], editorService: IEditorService, editorGroupsService: IEditorGroupsService, listService: IListService): IEditorCommandsContext[] { + // Figure out if command is executed from a list + const list = listService.lastFocusedList; + let isListAction = list instanceof List && list.getHTMLElement() === getActiveElement(); + + // Get editor context for which the command was triggered + let editorContext = getEditorContextFromCommandArgs(commandArgs, isListAction, editorService, editorGroupsService, listService); + + // If the editor context can not be determind use the active editor + if (!editorContext) { + const activeGroup = editorGroupsService.activeGroup; + const activeEditor = activeGroup.activeEditor; + editorContext = { groupId: activeGroup.id, editorIndex: activeEditor ? activeGroup.getIndexOfEditor(activeEditor) : undefined }; + isListAction = false; + } + + const multiEditorContext = getMultiSelectContext(editorContext, isListAction, editorService, editorGroupsService, listService); + + // Make sure the command context is the first one in the list + return moveCurrentEditorContextToFront(editorContext, multiEditorContext); +} + +function moveCurrentEditorContextToFront(editorContext: IEditorCommandsContext, multiEditorContext: IEditorCommandsContext[]): IEditorCommandsContext[] { + if (multiEditorContext.length <= 1) { + return multiEditorContext; + } + + const editorContextIndex = multiEditorContext.findIndex(context => + context.groupId === editorContext.groupId && + context.editorIndex === editorContext.editorIndex + ); + + if (editorContextIndex !== -1) { + multiEditorContext.splice(editorContextIndex, 1); + multiEditorContext.unshift(editorContext); + } else if (editorContext.editorIndex === undefined) { + multiEditorContext.unshift(editorContext); + } else { + throw new Error('Editor context not found in multi editor context'); + } + + return multiEditorContext; +} + +function getEditorContextFromCommandArgs(commandArgs: unknown[], isListAction: boolean, editorService: IEditorService, editorGroupsService: IEditorGroupsService, listService: IListService): IEditorCommandsContext | undefined { + // We only know how to extraxt the command context from URI and IEditorCommandsContext arguments + const filteredArgs = commandArgs.filter(arg => isEditorCommandsContext(arg) || URI.isUri(arg)); + + // If the command arguments contain an editor context, use it + for (const arg of filteredArgs) { + if (isEditorCommandsContext(arg)) { + return arg; + } + } + + // Otherwise, try to find the editor group by the URI of the resource + for (const uri of filteredArgs as URI[]) { + const editorIdentifiers = editorService.findEditors(uri); + if (editorIdentifiers.length) { + const editorIdentifier = editorIdentifiers[0]; + const group = editorGroupsService.getGroup(editorIdentifier.groupId); + return { groupId: editorIdentifier.groupId, editorIndex: group?.getIndexOfEditor(editorIdentifier.editor) }; + } + } + + // If there is no context in the arguments, try to find the context from the focused list + // if the action was executed from a list + if (isListAction) { + const list = listService.lastFocusedList as List; + for (const focusedElement of list.getFocusedElements()) { + if (isGroupOrEditor(focusedElement)) { + return groupOrEditorToEditorContext(focusedElement, undefined, editorGroupsService); + } + } + } + + return undefined; +} + +function getMultiSelectContext(editorContext: IEditorCommandsContext, isListAction: boolean, editorService: IEditorService, editorGroupsService: IEditorGroupsService, listService: IListService): IEditorCommandsContext[] { + + // If the action was executed from a list, return all selected editors + if (isListAction) { + const list = listService.lastFocusedList as List; + const selection = list.getSelectedElements().filter(isGroupOrEditor); + + if (selection.length > 1) { + return selection.map(e => groupOrEditorToEditorContext(e, editorContext.preserveFocus, editorGroupsService)); + } + + if (selection.length === 0) { + // TODO@benibenj workaround for https://github.com/microsoft/vscode/issues/224050 + // Explainer: the `isListAction` flag can be a false positive in certain cases because + // it will be `true` if the active element is a `List` even if it is part of the editor + // area. The workaround here is to fallback to `isListAction: false` if the list is not + // having any editor or group selected. + return getMultiSelectContext(editorContext, false, editorService, editorGroupsService, listService); + } + } + // Check editors selected in the group (tabs) + else { + const group = editorGroupsService.getGroup(editorContext.groupId); + const editor = editorContext.editorIndex !== undefined ? group?.getEditorByIndex(editorContext.editorIndex) : group?.activeEditor; + // If the editor is selected, return all selected editors otherwise only use the editors context + if (group && editor && group.isSelected(editor)) { + return group.selectedEditors.map(editor => groupOrEditorToEditorContext({ editor, groupId: group.id }, editorContext.preserveFocus, editorGroupsService)); + } + } + + // Otherwise go with passed in context + return [editorContext]; +} + +function groupOrEditorToEditorContext(element: IEditorIdentifier | IEditorGroup, preserveFocus: boolean | undefined, editorGroupsService: IEditorGroupsService): IEditorCommandsContext { + if (isEditorGroup(element)) { + return { groupId: element.id, editorIndex: undefined, preserveFocus }; + } + + const group = editorGroupsService.getGroup(element.groupId); + + return { groupId: element.groupId, editorIndex: group ? group.getIndexOfEditor(element.editor) : -1, preserveFocus }; +} + +function isGroupOrEditor(element: unknown): element is IEditorIdentifier | IEditorGroup { + return isEditorGroup(element) || isEditorIdentifier(element); +} + +function getEditorAndGroupFromContext(commandContext: IEditorCommandsContext, editorGroupsService: IEditorGroupsService): { group: IEditorGroup; editor: EditorInput | undefined } | undefined { + const group = editorGroupsService.getGroup(commandContext.groupId); + if (!group) { + return undefined; + } + + if (commandContext.editorIndex === undefined) { + return { group, editor: undefined }; + } + + const editor = group.getEditorByIndex(commandContext.editorIndex); + return { group, editor }; +} diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 463b527e..b11c66e6 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -92,7 +92,7 @@ class DropOverlay extends Themable { this.groupView.element.appendChild(container); this.groupView.element.classList.add('dragged-over'); this._register(toDisposable(() => { - this.groupView.element.removeChild(container); + container.remove(); this.groupView.element.classList.remove('dragged-over'); })); diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 6c8c42ec..41e4e3cc 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroupModel, IEditorOpenOptions, IGroupModelChangeEvent, ISerializedEditorGroupModel, isGroupEditorCloseEvent, isGroupEditorOpenEvent, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions, IToolbarActions, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor'; -import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ResourceContextKey, applyAvailableEditorIds, ActiveEditorAvailableEditorIdsContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorContext, ActiveEditorReadonlyContext, ActiveEditorCanRevertContext, ActiveEditorCanToggleReadonlyContext, ActiveCompareEditorCanSwapContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext } from 'vs/workbench/common/contextkeys'; +import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ResourceContextKey, applyAvailableEditorIds, ActiveEditorAvailableEditorIdsContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorContext, ActiveEditorReadonlyContext, ActiveEditorCanRevertContext, ActiveEditorCanToggleReadonlyContext, ActiveCompareEditorCanSwapContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext, SelectedEditorsInGroupFileOrUntitledResourceContextKey } from 'vs/workbench/common/contextkeys'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { Emitter, Relay } from 'vs/base/common/event'; @@ -259,6 +259,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const multipleEditorsSelectedContext = MultipleEditorsSelectedInGroupContext.bindTo(this.scopedContextKeyService); const twoEditorsSelectedContext = TwoEditorsSelectedInGroupContext.bindTo(this.scopedContextKeyService); + const selectedEditorsHaveFileOrUntitledResourceContext = SelectedEditorsInGroupFileOrUntitledResourceContextKey.bindTo(this.scopedContextKeyService); const groupActiveEditorContext = this.editorPartsView.bind(ActiveEditorContext, this); const groupActiveEditorIsReadonly = this.editorPartsView.bind(ActiveEditorReadonlyContext, this); @@ -355,6 +356,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { case GroupModelChangeKind.EDITORS_SELECTION: multipleEditorsSelectedContext.set(this.model.selectedEditors.length > 1); twoEditorsSelectedContext.set(this.model.selectedEditors.length === 2); + selectedEditorsHaveFileOrUntitledResourceContext.set(this.model.selectedEditors.every(e => e.resource && (this.fileService.hasProvider(e.resource) || e.resource.scheme === Schemas.untitled))); break; } @@ -558,14 +560,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { options.preserveFocus = true; // handle focus after editor is restored const internalOptions: IInternalEditorOpenOptions = { - preserveWindowOrder: true // handle window order after editor is restored + preserveWindowOrder: true, // handle window order after editor is restored + skipTitleUpdate: true, // update the title later for all editors at once }; const activeElement = getActiveElement(); // Show active editor (intentionally not using async to keep // `restoreEditors` from executing in same stack) - return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options, internalOptions).then(() => { + const result = this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options, internalOptions).then(() => { // Set focused now if this is the active group and focus has // not changed meanwhile. This prevents focus from being @@ -576,6 +579,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.focus(); } }); + + // Restore editors in title control + this.titleControl.openEditors(this.editors); + + return result; } //#region event handling @@ -827,7 +835,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Ensure to show active editor if any if (this.model.activeEditor) { - this.titleControl.openEditor(this.model.activeEditor); + this.titleControl.openEditors(this.model.getEditors(EditorsOrder.SEQUENTIAL)); } } diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorPanes.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorPanes.ts index 5d638871..ef1b25dd 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -466,7 +466,7 @@ export class EditorPanes extends Disposable { // Remove editor pane from parent const editorPaneContainer = this._activeEditorPane.getContainer(); if (editorPaneContainer) { - this.editorPanesParent.removeChild(editorPaneContainer); + editorPaneContainer.remove(); hide(editorPaneContainer); } diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorPart.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorPart.ts index d785a513..c305eb57 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -114,7 +114,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { private readonly _onDidActivateGroup = this._register(new Emitter()); readonly onDidActivateGroup = this._onDidActivateGroup.event; - private readonly _onDidAddGroup = this._register(new Emitter()); + private readonly _onDidAddGroup = this._register(new PauseableEmitter()); readonly onDidAddGroup = this._onDidAddGroup.event; private readonly _onDidRemoveGroup = this._register(new PauseableEmitter()); @@ -134,6 +134,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { private readonly _onDidChangeEditorPartOptions = this._register(new Emitter()); readonly onDidChangeEditorPartOptions = this._onDidChangeEditorPartOptions.event; + private readonly _onWillDispose = this._register(new Emitter()); + readonly onWillDispose = this._onWillDispose.event; + //#endregion private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.USER); @@ -1103,6 +1106,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { openVerticalPosition = Position.BOTTOM; } + if (e.eventData.clientY < boundingRect.top + proximity) { + openVerticalPosition = Position.TOP; + } + if (horizontalOpenerTimeout && openHorizontalPosition !== lastOpenHorizontalPosition) { clearTimeout(horizontalOpenerTimeout); horizontalOpenerTimeout = undefined; @@ -1352,7 +1359,17 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { private async doApplyState(state: IEditorPartUIState, options?: IEditorGroupViewOptions): Promise { const groups = await this.doPrepareApplyState(); - const resumeEvents = this.disposeGroups(true /* suspress events for the duration of applying state */); + + // Pause add/remove events for groups during the duration of applying the state + // This ensures that we can do this transition atomically with the new state + // being ready when the events are fired. This is important because usually there + // is never the state where no groups are present, but for this transition we + // need to temporarily dispose all groups to restore the new set. + + this._onDidAddGroup.pause(); + this._onDidRemoveGroup.pause(); + + this.disposeGroups(); // MRU this.mostRecentActiveGroups = state.mostRecentActiveGroups; @@ -1361,7 +1378,12 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { try { this.doApplyGridState(state.serializedGrid, state.activeGroup, undefined, options); } finally { - resumeEvents(); + // It is very important to keep this order: first resume the events for + // removed groups and then for added groups. Many listeners may store + // groups in sets by their identifier and groups can have the same + // identifier before and after. + this._onDidRemoveGroup.resume(); + this._onDidAddGroup.resume(); } // Restore editors that were not closed before and are now opened now @@ -1435,13 +1457,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { }; } - private disposeGroups(): void; - private disposeGroups(surpressEvents: boolean): Function; - private disposeGroups(surpressEvents?: boolean): Function | void { - if (surpressEvents) { - this._onDidRemoveGroup.pause(); - } - + private disposeGroups(): void { for (const group of this.groups) { group.dispose(); @@ -1450,14 +1466,13 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { this.groupViews.clear(); this.mostRecentActiveGroups = []; - - if (surpressEvents) { - return () => this._onDidRemoveGroup.resume(); - } } override dispose(): void { + // Event + this._onWillDispose.fire(); + // Forward to all groups this.disposeGroups(); diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorParts.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorParts.ts index e505a3fd..c04fd776 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { EditorGroupLayout, GroupDirection, GroupLocation, GroupOrientation, GroupsArrangement, GroupsOrder, IAuxiliaryEditorPart, IAuxiliaryEditorPartCreateEvent, IEditorGroupContextKeyProvider, IEditorDropTargetDelegate, IEditorGroupsService, IEditorSideGroup, IEditorWorkingSet, IFindGroupScope, IMergeGroupOptions, IEditorWorkingSetOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorGroupLayout, GroupDirection, GroupLocation, GroupOrientation, GroupsArrangement, GroupsOrder, IAuxiliaryEditorPart, IEditorGroupContextKeyProvider, IEditorDropTargetDelegate, IEditorGroupsService, IEditorSideGroup, IEditorWorkingSet, IFindGroupScope, IMergeGroupOptions, IEditorWorkingSetOptions, IEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Emitter } from 'vs/base/common/event'; import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { GroupIdentifier } from 'vs/workbench/common/editor'; @@ -22,6 +22,8 @@ import { IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workben import { generateUuid } from 'vs/base/common/uuid'; import { ContextKeyValue, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { isHTMLElement } from 'vs/base/browser/dom'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; interface IEditorPartsUIState { readonly auxiliary: IAuxiliaryEditorPartState[]; @@ -70,19 +72,44 @@ export class EditorParts extends MultiWindowParts implements IEditor return this.instantiationService.createInstance(MainEditorPart, this); } + //#region Scoped Instantiation Services + + private readonly mapPartToInstantiationService = new Map(); + + getScopedInstantiationService(part: IEditorPart): IInstantiationService { + if (part === this.mainPart) { + if (!this.mapPartToInstantiationService.has(part.windowId)) { + this.instantiationService.invokeFunction(accessor => { + const editorService = accessor.get(IEditorService); // using `invokeFunction` to get hold of `IEditorService` lazily + + this.mapPartToInstantiationService.set(part.windowId, this._register(this.instantiationService.createChild(new ServiceCollection( + [IEditorService, editorService.createScoped('main', this._store)] + )))); + }); + } + } + + return this.mapPartToInstantiationService.get(part.windowId) ?? this.instantiationService; + } + + //#endregion + //#region Auxiliary Editor Parts - private readonly _onDidCreateAuxiliaryEditorPart = this._register(new Emitter()); + private readonly _onDidCreateAuxiliaryEditorPart = this._register(new Emitter()); readonly onDidCreateAuxiliaryEditorPart = this._onDidCreateAuxiliaryEditorPart.event; async createAuxiliaryEditorPart(options?: IAuxiliaryEditorPartOpenOptions): Promise { const { part, instantiationService, disposables } = await this.instantiationService.createInstance(AuxiliaryEditorPart, this).create(this.getGroupsLabel(this._parts.size), options); + // Keep instantiation service + this.mapPartToInstantiationService.set(part.windowId, instantiationService); + disposables.add(toDisposable(() => this.mapPartToInstantiationService.delete(part.windowId))); + // Events this._onDidAddGroup.fire(part.activeGroup); - const eventDisposables = disposables.add(new DisposableStore()); - this._onDidCreateAuxiliaryEditorPart.fire({ part, instantiationService, disposables: eventDisposables }); + this._onDidCreateAuxiliaryEditorPart.fire(part); return part; } @@ -761,7 +788,7 @@ export class EditorParts extends MultiWindowParts implements IEditor let groupRegisteredContextKeys = this.registeredContextKeys.get(group.id); if (!groupRegisteredContextKeys) { groupRegisteredContextKeys = new Map(); - this.scopedContextKeys.set(group.id, groupRegisteredContextKeys); + this.registeredContextKeys.set(group.id, groupRegisteredContextKeys); } let scopedRegisteredContextKey = groupRegisteredContextKeys.get(provider.contextKey.key); diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts index def52c5b..e434679f 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts @@ -185,7 +185,7 @@ export class WorkspaceTrustRequiredPlaceholderEditor extends EditorPlaceholder { static readonly ID = 'workbench.editors.workspaceTrustRequiredEditor'; private static readonly LABEL = localize('trustRequiredEditor', "Workspace Trust Required"); - static readonly DESCRIPTOR = EditorPaneDescriptor.create(WorkspaceTrustRequiredPlaceholderEditor, WorkspaceTrustRequiredPlaceholderEditor.ID, WorkspaceTrustRequiredPlaceholderEditor.LABEL); + static readonly DESCRIPTOR = EditorPaneDescriptor.create(WorkspaceTrustRequiredPlaceholderEditor, this.ID, this.LABEL); constructor( group: IEditorGroup, @@ -223,7 +223,7 @@ export class ErrorPlaceholderEditor extends EditorPlaceholder { private static readonly ID = 'workbench.editors.errorEditor'; private static readonly LABEL = localize('errorEditor', "Error Editor"); - static readonly DESCRIPTOR = EditorPaneDescriptor.create(ErrorPlaceholderEditor, ErrorPlaceholderEditor.ID, ErrorPlaceholderEditor.LABEL); + static readonly DESCRIPTOR = EditorPaneDescriptor.create(ErrorPlaceholderEditor, this.ID, this.LABEL); constructor( group: IEditorGroup, diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 22533043..3a69dbbf 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -60,7 +60,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro ); } - override provide(picker: IQuickPick, token: CancellationToken): IDisposable { + override provide(picker: IQuickPick, token: CancellationToken): IDisposable { // Reset the pick state for this run this.pickState.reset(!!picker.quickNavigate); diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/editorStatus.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/editorStatus.ts index c2ceeb2d..dcef2cf9 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -55,9 +55,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { TabFocus } from 'vs/editor/browser/config/tabFocus'; -import { mainWindow } from 'vs/base/browser/window'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IEditorGroupsService, IEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private primary: IEncodingSupport, private secondary: IEncodingSupport) { } @@ -886,22 +884,23 @@ export class EditorStatusContribution extends Disposable implements IWorkbenchCo static readonly ID = 'workbench.contrib.editorStatus'; constructor( - @IInstantiationService instantiationService: IInstantiationService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, ) { super(); - // Main Editor Status - const mainInstantiationService = this._register(instantiationService.createChild(new ServiceCollection( - [IEditorService, editorService.createScoped('main', this._store)] - ))); - this._register(mainInstantiationService.createInstance(EditorStatus, mainWindow.vscodeWindowId)); + for (const part of editorGroupService.parts) { + this.createEditorStatus(part); + } - // Auxiliary Editor Status - this._register(editorGroupService.onDidCreateAuxiliaryEditorPart(({ part, instantiationService, disposables }) => { - disposables.add(instantiationService.createInstance(EditorStatus, part.windowId)); - })); + this._register(editorGroupService.onDidCreateAuxiliaryEditorPart(part => this.createEditorStatus(part))); + } + + private createEditorStatus(part: IEditorPart): void { + const disposables = new DisposableStore(); + Event.once(part.onWillDispose)(() => disposables.dispose()); + + const scopedInstantiationService = this.editorGroupService.getScopedInstantiationService(part); + disposables.add(scopedInstantiationService.createInstance(EditorStatus, part.windowId)); } } @@ -1079,11 +1078,20 @@ export class ChangeLanguageAction extends Action2 { weight: KeybindingWeight.WorkbenchContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyM) }, - precondition: ContextKeyExpr.not('notebookEditorFocused') + precondition: ContextKeyExpr.not('notebookEditorFocused'), + metadata: { + description: localize('changeLanguageMode.description', "Change the language mode of the active text editor."), + args: [ + { + name: localize('changeLanguageMode.arg.name', "The name of the language mode to change to."), + constraint: (value: any) => typeof value === 'string', + } + ] + } }); } - override async run(accessor: ServicesAccessor): Promise { + override async run(accessor: ServicesAccessor, languageMode?: string): Promise { const quickInputService = accessor.get(IQuickInputService); const editorService = accessor.get(IEditorService); const languageService = accessor.get(ILanguageService); @@ -1162,7 +1170,7 @@ export class ChangeLanguageAction extends Action2 { }; picks.unshift(autoDetectLanguage); - const pick = await quickInputService.pick(picks, { placeHolder: localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }); + const pick = typeof languageMode === 'string' ? { label: languageMode } : await quickInputService.pick(picks, { placeHolder: localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }); if (!pick) { return; } @@ -1444,13 +1452,16 @@ export class ChangeEncodingAction extends Action2 { let guessedEncoding: string | undefined = undefined; if (fileService.hasProvider(resource)) { - const content = await textFileService.readStream(resource, { autoGuessEncoding: true }); + const content = await textFileService.readStream(resource, { + autoGuessEncoding: true, + candidateGuessEncodings: textResourceConfigurationService.getValue(resource, 'files.candidateGuessEncodings') + }); guessedEncoding = content.encoding; } const isReopenWithEncoding = (action === reopenWithEncodingPick); - const configuredEncoding = textResourceConfigurationService.getValue(resource ?? undefined, 'files.encoding'); + const configuredEncoding = textResourceConfigurationService.getValue(resource, 'files.encoding'); let directMatchIndex: number | undefined; let aliasMatchIndex: number | undefined; diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg b/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg index 427fbd47..5d6fca43 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg @@ -1,4 +1,33 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-hcDark.svg b/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-hcDark.svg index 427fbd47..4e32c94c 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-hcDark.svg +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-hcDark.svg @@ -1,4 +1,33 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-hcLight.svg b/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-hcLight.svg index 427fbd47..4edccd11 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-hcLight.svg +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-hcLight.svg @@ -1,4 +1,33 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-light.svg b/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-light.svg index 427fbd47..86f01932 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-light.svg +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/media/letterpress-light.svg @@ -1,4 +1,31 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 588c3344..9007f132 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -1093,7 +1093,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } // Apply some datatransfer types to allow for dragging the element outside of the application - this.doFillResourceDataTransfers([editor], e, isNewWindowOperation); + this.doFillResourceDataTransfers(selectedEditors, e, isNewWindowOperation); scheduleAtNextAnimationFrame(getWindow(this.parent), () => this.updateDropFeedback(tab, false, e, tabIndex)); }, @@ -1288,24 +1288,24 @@ export class MultiEditorTabsControl extends EditorTabsControl { throw new BugIndicatingError(); } - const anchorIndex = this.groupView.getIndexOfEditor(anchor); - if (anchorIndex === -1) { + const anchorEditorIndex = this.groupView.getIndexOfEditor(anchor); + if (anchorEditorIndex === -1) { throw new BugIndicatingError(); } let selection = this.groupView.selectedEditors; // Unselect editors on other side of anchor in relation to the target - let currentIndex = anchorIndex; - while (currentIndex >= 0 && currentIndex <= this.groupView.count - 1) { - currentIndex = anchorIndex < editorIndex ? currentIndex - 1 : currentIndex + 1; + let currentEditorIndex = anchorEditorIndex; + while (currentEditorIndex >= 0 && currentEditorIndex <= this.groupView.count - 1) { + currentEditorIndex = anchorEditorIndex < editorIndex ? currentEditorIndex - 1 : currentEditorIndex + 1; - if (!this.tabsModel.isSelected(currentIndex)) { + const currentEditor = this.groupView.getEditorByIndex(currentEditorIndex); + if (!currentEditor) { break; } - const currentEditor = this.groupView.getEditorByIndex(currentIndex); - if (!currentEditor) { + if (!this.groupView.isSelected(currentEditor)) { break; } @@ -1313,12 +1313,12 @@ export class MultiEditorTabsControl extends EditorTabsControl { } // Select editors between anchor and target - const fromIndex = anchorIndex < editorIndex ? anchorIndex : editorIndex; - const toIndex = anchorIndex < editorIndex ? editorIndex : anchorIndex; + const fromEditorIndex = anchorEditorIndex < editorIndex ? anchorEditorIndex : editorIndex; + const toEditorIndex = anchorEditorIndex < editorIndex ? editorIndex : anchorEditorIndex; - const editorsToSelect = this.groupView.getEditors(EditorsOrder.SEQUENTIAL).slice(fromIndex, toIndex + 1); + const editorsToSelect = this.groupView.getEditors(EditorsOrder.SEQUENTIAL).slice(fromEditorIndex, toEditorIndex + 1); for (const editor of editorsToSelect) { - if (!this.tabsModel.isSelected(editor)) { + if (!this.groupView.isSelected(editor)) { selection.push(editor); } } @@ -1343,7 +1343,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { const recentEditors = this.groupView.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); for (let i = 1; i < recentEditors.length; i++) { // First one is the active editor const recentEditor = recentEditors[i]; - if (this.tabsModel.isSelected(recentEditor)) { + if (this.groupView.isSelected(recentEditor)) { newActiveEditor = recentEditor; break; } diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts index 83823d3e..caada501 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts @@ -20,6 +20,8 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont private readonly stickyEditorTabsControl: IEditorTabsControl; private readonly unstickyEditorTabsControl: IEditorTabsControl; + private activeControl: IEditorTabsControl | undefined; + constructor( private readonly parent: HTMLElement, editorPartsView: IEditorPartsView, @@ -36,10 +38,15 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont this.stickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, editorPartsView, this.groupsView, this.groupView, stickyModel)); this.unstickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, editorPartsView, this.groupsView, this.groupView, unstickyModel)); - this.handlePinnedTabsLayoutChange(); + this.handleTabBarsStateChange(); + } + + private handleTabBarsStateChange(): void { + this.activeControl = this.model.activeEditor ? this.getEditorTabsController(this.model.activeEditor) : undefined; + this.handleTabBarsLayoutChange(); } - private handlePinnedTabsLayoutChange(): void { + private handleTabBarsLayoutChange(): void { if (this.groupView.count === 0) { // Do nothing as no tab bar is visible return; @@ -56,17 +63,20 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont } } + private didActiveControlChange() { + return this.activeControl !== (this.model.activeEditor ? this.getEditorTabsController(this.model.activeEditor) : undefined); + } + private getEditorTabsController(editor: EditorInput): IEditorTabsControl { return this.model.isSticky(editor) ? this.stickyEditorTabsControl : this.unstickyEditorTabsControl; } openEditor(editor: EditorInput, options: IInternalEditorOpenOptions): boolean { - const [editorTabController, otherTabController] = this.model.isSticky(editor) ? [this.stickyEditorTabsControl, this.unstickyEditorTabsControl] : [this.unstickyEditorTabsControl, this.stickyEditorTabsControl]; - const didChange = editorTabController.openEditor(editor, options); - if (didChange) { - // HACK: To render all editor tabs on startup, otherwise only one row gets rendered - otherTabController.openEditors([]); + const didActiveControlChange = this.didActiveControlChange(); + const didOpenEditorChange = this.getEditorTabsController(editor).openEditor(editor, options); + const didChange = didOpenEditorChange || didActiveControlChange; + if (didChange) { this.handleOpenedEditors(); } return didChange; @@ -76,11 +86,11 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont const stickyEditors = editors.filter(e => this.model.isSticky(e)); const unstickyEditors = editors.filter(e => !this.model.isSticky(e)); + const didActiveControlChange = this.didActiveControlChange(); const didChangeOpenEditorsSticky = this.stickyEditorTabsControl.openEditors(stickyEditors); const didChangeOpenEditorsUnSticky = this.unstickyEditorTabsControl.openEditors(unstickyEditors); - const didChange = didChangeOpenEditorsSticky || didChangeOpenEditorsUnSticky; - + const didChange = didChangeOpenEditorsSticky || didChangeOpenEditorsUnSticky || didActiveControlChange; if (didChange) { this.handleOpenedEditors(); } @@ -89,7 +99,7 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont } private handleOpenedEditors(): void { - this.handlePinnedTabsLayoutChange(); + this.handleTabBarsStateChange(); } beforeCloseEditor(editor: EditorInput): void { @@ -115,7 +125,7 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont } private handleClosedEditors(): void { - this.handlePinnedTabsLayoutChange(); + this.handleTabBarsStateChange(); } moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number, stickyStateChange: boolean): void { @@ -129,7 +139,7 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont this.unstickyEditorTabsControl.openEditor(editor); } - this.handlePinnedTabsLayoutChange(); + this.handleTabBarsStateChange(); } else { if (this.model.isSticky(editor)) { @@ -148,14 +158,14 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont this.unstickyEditorTabsControl.closeEditor(editor); this.stickyEditorTabsControl.openEditor(editor); - this.handlePinnedTabsLayoutChange(); + this.handleTabBarsStateChange(); } unstickEditor(editor: EditorInput): void { this.stickyEditorTabsControl.closeEditor(editor); this.unstickyEditorTabsControl.openEditor(editor); - this.handlePinnedTabsLayoutChange(); + this.handleTabBarsStateChange(); } setActive(isActive: boolean): void { diff --git a/patched-vscode/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/patched-vscode/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index c3d7a0cc..2dab828b 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -160,7 +160,7 @@ export class SideBySideEditor extends AbstractEditorWithViewState { + if (e.event.removed) { + for (const removed of e.event.removed) { + this.removeAccount(e.providerId, removed.account); + } + } for (const changed of [...(e.event.changed ?? []), ...(e.event.added ?? [])]) { try { await this.addOrUpdateAccount(e.providerId, changed.account); @@ -344,11 +349,6 @@ export class AccountsActivityActionViewItem extends AbstractGlobalActivityAction this.logService.error(e); } } - if (e.event.removed) { - for (const removed of e.event.removed) { - this.removeAccount(e.providerId, removed.account); - } - } })); } diff --git a/patched-vscode/src/vs/workbench/browser/parts/media/paneCompositePart.css b/patched-vscode/src/vs/workbench/browser/parts/media/paneCompositePart.css index 52baa532..52f11645 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/patched-vscode/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -73,13 +73,9 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { height: 35px; /* matches height of composite container */ - padding: 0 5px; + padding: 0 3px; } -.monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon, -.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon { - font-size: 18px; -} .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon), .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { @@ -237,6 +233,12 @@ text-align: center; } +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.compact-content .badge-content, +.monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.compact-content .badge-content { + font-size: 8px; + padding: 0 3px; +} + .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before, .monaco-workbench .pane-composite-part > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before { mask-size: 11px; diff --git a/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css b/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css index b4ac2170..a9e4fd0e 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css +++ b/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsCenter.css @@ -44,14 +44,12 @@ justify-content: flex-end; } -.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { +.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row:not(:last-child) > .notification-list-item { border-bottom: 1px solid var(--vscode-notifications-border); } -.monaco-workbench > .notifications-center .notifications-list-container, -.monaco-workbench > .notifications-center .notifications-list-container .monaco-scrollable-element, -.monaco-workbench > .notifications-center .notifications-list-container .notification-list-item { - border-radius: 0; +.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row:last-child { + border-radius: 0px 0px 4px 4px; /* adopt the border radius at the end of the notifications center */ } /* Icons */ diff --git a/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index 1e46a103..c5b4faf8 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -9,7 +9,6 @@ color: var(--vscode-notifications-foreground); background: var(--vscode-notifications-background); outline-color: var(--vscode-contrastBorder); - border-radius: inherit; } .monaco-workbench .notifications-list-container .notification-list-item { @@ -19,7 +18,6 @@ padding: 10px 5px; height: 100%; box-sizing: border-box; - border-radius: 4px; } .monaco-workbench .notifications-list-container .notification-offset-helper { @@ -29,10 +27,6 @@ word-wrap: break-word; /* never overflow long words, but break to next line */ } -.monaco-workbench .notifications-list-container .monaco-scrollable-element { - border-radius: 4px; -} - /** Notification: Main Row */ .monaco-workbench .notifications-list-container .notification-list-item > .notification-list-item-main-row { diff --git a/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css b/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css index 89ce01ed..d4ef2314 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css +++ b/patched-vscode/src/vs/workbench/browser/parts/notifications/media/notificationsToasts.css @@ -30,6 +30,13 @@ transform: translate3d(0px, 100%, 0px); /* move the notification 50px to the bottom (to prevent bleed through) */ opacity: 0; /* fade the toast in */ transition: transform 300ms ease-out, opacity 300ms ease-out; +} + +.monaco-workbench > .notifications-toasts .notifications-list-container, +.monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast, +.monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast .monaco-scrollable-element, +.monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast .monaco-list:not(.element-focused):focus:before, +.monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast .monaco-list-row { border-radius: 4px; } diff --git a/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts b/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts index 15a240f9..f36a4dde 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts @@ -7,8 +7,8 @@ import { IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; -import { IAccessibleViewService, AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; -import { IAccessibleViewImplentation, alertAccessibleViewFocusChange } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { IAccessibleViewService, AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; +import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { IAccessibilitySignalService, AccessibilitySignal } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -35,11 +35,9 @@ export class NotificationAccessibleView implements IAccessibleViewImplentation { } commandService.executeCommand('notifications.showList'); let notificationIndex: number | undefined; - let length: number | undefined; const list = listService.lastFocusedList; if (list instanceof WorkbenchList) { notificationIndex = list.indexOf(notification); - length = list.length; } if (notificationIndex === undefined) { return; @@ -54,45 +52,48 @@ export class NotificationAccessibleView implements IAccessibleViewImplentation { } catch { } } } - const message = notification.message.original.toString(); - if (!message) { + + function getContentForNotification(): string | undefined { + const notification = getNotificationFromContext(listService); + const message = notification?.message.original.toString(); + if (!notification) { + return; + } + return notification.source ? localize('notification.accessibleViewSrc', '{0} Source: {1}', message, notification.source) : localize('notification.accessibleView', '{0}', message); + } + const content = getContentForNotification(); + if (!content) { return; } notification.onDidClose(() => accessibleViewService.next()); - return { - id: AccessibleViewProviderId.Notification, - provideContent: () => { - return notification.source ? localize('notification.accessibleViewSrc', '{0} Source: {1}', message, notification.source) : localize('notification.accessibleView', '{0}', message); - }, - onClose(): void { - focusList(); - }, - next(): void { + return new AccessibleContentProvider( + AccessibleViewProviderId.Notification, + { type: AccessibleViewType.View }, + () => content, + () => focusList(), + 'accessibility.verbosity.notification', + undefined, + getActionsFromNotification(notification, accessibilitySignalService), + () => { if (!list) { return; } focusList(); list.focusNext(); - alertAccessibleViewFocusChange(notificationIndex, length, 'next'); - getProvider(); + return getContentForNotification(); }, - previous(): void { + () => { if (!list) { return; } focusList(); list.focusPrevious(); - alertAccessibleViewFocusChange(notificationIndex, length, 'previous'); - getProvider(); + return getContentForNotification(); }, - verbositySettingKey: 'accessibility.verbosity.notification', - options: { type: AccessibleViewType.View }, - actions: getActionsFromNotification(notification, accessibilitySignalService) - }; + ); } return getProvider(); } - dispose() { } } diff --git a/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 92b1b45d..5151fdc1 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -312,12 +312,12 @@ export function registerNotificationCommands(center: INotificationsCenterControl picker.canSelectMany = true; picker.placeholder = localize('selectSources', "Select sources to enable all notifications from"); - picker.selectedItems = picker.items.filter(item => (item as INotificationSourceFilter).filter === NotificationsFilter.OFF) as (IQuickPickItem & INotificationSourceFilter)[]; + picker.selectedItems = picker.items.filter(item => item.filter === NotificationsFilter.OFF); picker.show(); disposables.add(picker.onDidAccept(async () => { - for (const item of picker.items as (IQuickPickItem & INotificationSourceFilter)[]) { + for (const item of picker.items) { notificationService.setFilter({ id: item.id, label: item.label, diff --git a/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 5d29de17..94313673 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -55,7 +55,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast // Count for the number of notifications over 800ms... interval: 800, // ...and ensure we are not showing more than MAX_NOTIFICATIONS - limit: NotificationsToasts.MAX_NOTIFICATIONS + limit: this.MAX_NOTIFICATIONS }; private readonly _onDidChangeVisibility = this._register(new Emitter()); @@ -602,7 +602,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast if (visible) { notificationsToastsContainer.appendChild(toast.container); } else { - notificationsToastsContainer.removeChild(toast.container); + toast.container.remove(); } // Update visibility in model diff --git a/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 65ad1afe..c1fda244 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -30,7 +30,7 @@ import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; export class NotificationsListDelegate implements IListVirtualDelegate { @@ -379,14 +379,14 @@ export class NotificationTemplateRenderer extends Disposable { this.renderSeverity(notification); // Message - const messageCustomHover = this.inputDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.template.message, '')); + const messageCustomHover = this.inputDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.template.message, '')); const messageOverflows = this.renderMessage(notification, messageCustomHover); // Secondary Actions this.renderSecondaryActions(notification, messageOverflows); // Source - const sourceCustomHover = this.inputDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.template.source, '')); + const sourceCustomHover = this.inputDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.template.source, '')); this.renderSource(notification, sourceCustomHover); // Buttons @@ -424,7 +424,7 @@ export class NotificationTemplateRenderer extends Disposable { this.template.icon.classList.add(...ThemeIcon.asClassNameArray(this.toSeverityIcon(notification.severity))); } - private renderMessage(notification: INotificationViewItem, customHover: IUpdatableHover): boolean { + private renderMessage(notification: INotificationViewItem, customHover: IManagedHover): boolean { clearNode(this.template.message); this.template.message.appendChild(NotificationMessageRenderer.render(notification.message, { callback: link => this.openerService.open(URI.parse(link), { allowCommands: true }), @@ -474,7 +474,7 @@ export class NotificationTemplateRenderer extends Disposable { actions.forEach(action => this.template.toolbar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) })); } - private renderSource(notification: INotificationViewItem, sourceCustomHover: IUpdatableHover): void { + private renderSource(notification: INotificationViewItem, sourceCustomHover: IManagedHover): void { if (notification.expanded && notification.source) { this.template.source.textContent = localize('notificationSource', "Source: {0}", notification.source); sourceCustomHover.update(notification.source); diff --git a/patched-vscode/src/vs/workbench/browser/parts/paneCompositePart.ts b/patched-vscode/src/vs/workbench/browser/parts/paneCompositePart.ts index 68e797d6..cf29cbed 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -629,8 +629,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart true); + const menu = this.menuService.getMenuActions(ViewsSubMenu, scopedContextKeyService, { shouldForwardArgs: true, renderShortTitle: true }); + createAndFillInActionBarActions(menu, { primary: viewsActions, secondary: [] }, () => true); disposables.dispose(); return viewsActions.length > 1 && viewsActions.some(a => a.enabled) ? new SubmenuAction('views', localize('views', "Views"), viewsActions) : undefined; } diff --git a/patched-vscode/src/vs/workbench/browser/parts/panel/media/panelpart.css b/patched-vscode/src/vs/workbench/browser/parts/panel/media/panelpart.css index 40a5ee28..e1c147d8 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/patched-vscode/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -17,6 +17,15 @@ border-top-width: 0; /* no border when main editor area is hiden */ } +.monaco-workbench .part.panel.top { + border-bottom-width: 1px; + border-bottom-style: solid; +} + +.monaco-workbench.nomaineditorarea .part.panel.top { + border-bottom-width: 0; /* no border when main editor area is hiden */ +} + .monaco-workbench .part.panel.right { border-left-width: 1px; border-left-style: solid; @@ -81,3 +90,11 @@ display: inline-block; transform: rotate(90deg); } + +/* Rotate icons when panel is on left */ +.monaco-workbench .part.basepanel.top .title-actions .codicon-split-horizontal::before, +.monaco-workbench .part.basepanel.top .global-actions .codicon-panel-maximize::before, +.monaco-workbench .part.basepanel.top .global-actions .codicon-panel-restore::before { + display: inline-block; + transform: rotate(180deg); +} diff --git a/patched-vscode/src/vs/workbench/browser/parts/panel/panelActions.ts b/patched-vscode/src/vs/workbench/browser/parts/panel/panelActions.ts index 534db028..49d0e197 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -8,7 +8,7 @@ import { localize, localize2 } from 'vs/nls'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuId, MenuRegistry, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, PanelAlignment, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; +import { ActivityBarPosition, isHorizontal, IWorkbenchLayoutService, LayoutSettings, PanelAlignment, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/contextkeys'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { Codicon } from 'vs/base/common/codicons'; @@ -99,6 +99,7 @@ const PositionPanelActionId = { LEFT: 'workbench.action.positionPanelLeft', RIGHT: 'workbench.action.positionPanelRight', BOTTOM: 'workbench.action.positionPanelBottom', + TOP: 'workbench.action.positionPanelTop' }; const AlignPanelActionId = { @@ -136,6 +137,7 @@ function createAlignmentPanelActionConfig(id: string, title: ICommandActionTitle const PositionPanelActionConfigs: PanelActionConfig[] = [ + createPositionPanelActionConfig(PositionPanelActionId.TOP, localize2('positionPanelTop', "Move Panel To Top"), localize('positionPanelTopShort', "Top"), Position.TOP), createPositionPanelActionConfig(PositionPanelActionId.LEFT, localize2('positionPanelLeft', "Move Panel Left"), localize('positionPanelLeftShort', "Left"), Position.LEFT), createPositionPanelActionConfig(PositionPanelActionId.RIGHT, localize2('positionPanelRight', "Move Panel Right"), localize('positionPanelRightShort', "Right"), Position.RIGHT), createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, localize2('positionPanelBottom', "Move Panel To Bottom"), localize('positionPanelBottomShort', "Bottom"), Position.BOTTOM), @@ -158,7 +160,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 4 }); -PositionPanelActionConfigs.forEach(positionPanelAction => { +PositionPanelActionConfigs.forEach((positionPanelAction, index) => { const { id, title, shortLabel, value, when } = positionPanelAction; registerAction2(class extends Action2 { @@ -182,7 +184,7 @@ PositionPanelActionConfigs.forEach(positionPanelAction => { title: shortLabel, toggled: when.negate() }, - order: 5 + order: 5 + index }); }); @@ -280,23 +282,23 @@ registerAction2(class extends Action2 { tooltip: localize('maximizePanel', "Maximize Panel Size"), category: Categories.View, f1: true, - icon: maximizeIcon, + icon: maximizeIcon, // This is being rotated in CSS depending on the panel position // the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment - precondition: ContextKeyExpr.or(PanelAlignmentContext.isEqualTo('center'), PanelPositionContext.notEqualsTo('bottom')), + precondition: ContextKeyExpr.or(PanelAlignmentContext.isEqualTo('center'), ContextKeyExpr.and(PanelPositionContext.notEqualsTo('bottom'), PanelPositionContext.notEqualsTo('top'))), toggled: { condition: PanelMaximizedContext, icon: restoreIcon, tooltip: localize('minimizePanel', "Restore Panel Size") }, menu: [{ id: MenuId.PanelTitle, group: 'navigation', order: 1, // the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment - when: ContextKeyExpr.or(PanelAlignmentContext.isEqualTo('center'), PanelPositionContext.notEqualsTo('bottom')) + when: ContextKeyExpr.or(PanelAlignmentContext.isEqualTo('center'), ContextKeyExpr.and(PanelPositionContext.notEqualsTo('bottom'), PanelPositionContext.notEqualsTo('top'))) }] }); } run(accessor: ServicesAccessor) { const layoutService = accessor.get(IWorkbenchLayoutService); const notificationService = accessor.get(INotificationService); - if (layoutService.getPanelAlignment() !== 'center' && layoutService.getPanelPosition() === Position.BOTTOM) { + if (layoutService.getPanelAlignment() !== 'center' && isHorizontal(layoutService.getPanelPosition())) { notificationService.warn(localize('panelMaxNotSupported', "Maximizing the panel is only supported when it is center aligned.")); return; } @@ -351,10 +353,6 @@ registerAction2(class extends Action2 { group: 'navigation', order: 2, when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP) - }, { - id: MenuId.AuxiliaryBarHeader, - group: 'navigation', - when: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP) }] }); } diff --git a/patched-vscode/src/vs/workbench/browser/parts/panel/panelPart.ts b/patched-vscode/src/vs/workbench/browser/parts/panel/panelPart.ts index e8e8b49b..899d97b4 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -112,6 +112,7 @@ export class PanelPart extends AbstractPaneCompositePart { const borderColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || ''; container.style.borderLeftColor = borderColor; container.style.borderRightColor = borderColor; + container.style.borderBottomColor = borderColor; const title = this.getTitleArea(); if (title) { @@ -149,14 +150,12 @@ export class PanelPart extends AbstractPaneCompositePart { } private fillExtraContextMenuActions(actions: IAction[]): void { - const panelPositionMenu = this.menuService.createMenu(MenuId.PanelPositionMenu, this.contextKeyService); - const panelAlignMenu = this.menuService.createMenu(MenuId.PanelAlignmentMenu, this.contextKeyService); + const panelPositionMenu = this.menuService.getMenuActions(MenuId.PanelPositionMenu, this.contextKeyService, { shouldForwardArgs: true }); + const panelAlignMenu = this.menuService.getMenuActions(MenuId.PanelAlignmentMenu, this.contextKeyService, { shouldForwardArgs: true }); const positionActions: IAction[] = []; const alignActions: IAction[] = []; - createAndFillInContextMenuActions(panelPositionMenu, { shouldForwardArgs: true }, { primary: [], secondary: positionActions }); - createAndFillInContextMenuActions(panelAlignMenu, { shouldForwardArgs: true }, { primary: [], secondary: alignActions }); - panelAlignMenu.dispose(); - panelPositionMenu.dispose(); + createAndFillInContextMenuActions(panelPositionMenu, { primary: [], secondary: positionActions }); + createAndFillInContextMenuActions(panelAlignMenu, { primary: [], secondary: alignActions }); actions.push(...[ new Separator(), @@ -168,10 +167,16 @@ export class PanelPart extends AbstractPaneCompositePart { override layout(width: number, height: number, top: number, left: number): void { let dimensions: Dimension; - if (this.layoutService.getPanelPosition() === Position.RIGHT) { - dimensions = new Dimension(width - 1, height); // Take into account the 1px border when layouting - } else { - dimensions = new Dimension(width, height); + switch (this.layoutService.getPanelPosition()) { + case Position.RIGHT: + dimensions = new Dimension(width - 1, height); // Take into account the 1px border when layouting + break; + case Position.TOP: + dimensions = new Dimension(width, height - 1); // Take into account the 1px border when layouting + break; + default: + dimensions = new Dimension(width, height); + break; } // Layout contents diff --git a/patched-vscode/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/patched-vscode/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index d4be01b7..2c940789 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/patched-vscode/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -78,7 +78,7 @@ .monaco-workbench .part.sidebar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label::before, .monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label::before { position: absolute; - left: 6px; /* place icon in center */ + left: 5px; /* place icon in center */ } .monaco-workbench .part.sidebar > .header-or-footer > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, diff --git a/patched-vscode/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/patched-vscode/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index d6897bf0..b30d2bee 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/patched-vscode/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -88,6 +88,11 @@ margin-left: 3px; } +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.compact-left.compact-right > .statusbar-item-label { + margin-right:0; + margin-left: 0; +} + .monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item { padding-left: 7px; /* Add padding to the most left status bar item */ } diff --git a/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index 07dd5640..d458ce91 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -24,7 +24,7 @@ import { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry' import { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; export class StatusbarEntryItem extends Disposable { @@ -42,7 +42,7 @@ export class StatusbarEntryItem extends Disposable { private readonly focusListener = this._register(new MutableDisposable()); private readonly focusOutListener = this._register(new MutableDisposable()); - private hover: IUpdatableHover | undefined = undefined; + private hover: IManagedHover | undefined = undefined; readonly labelContainer: HTMLElement; readonly beakContainer: HTMLElement; @@ -122,7 +122,7 @@ export class StatusbarEntryItem extends Disposable { if (this.hover) { this.hover.update(hoverContents); } else { - this.hover = this._register(this.hoverService.setupUpdatableHover(this.hoverDelegate, this.container, hoverContents)); + this.hover = this._register(this.hoverService.setupManagedHover(this.hoverDelegate, this.container, hoverContents)); } if (entry.command !== ShowTooltipCommand /* prevents flicker on click */) { this.focusListener.value = addDisposableListener(this.labelContainer, EventType.FOCUS, e => { @@ -283,7 +283,7 @@ class StatusBarCodiconLabel extends SimpleIconLabel { private progressCodicon = renderIcon(syncing); private currentText = ''; - private currentShowProgress: boolean | 'syncing' | 'loading' = false; + private currentShowProgress: boolean | 'loading' | 'syncing' = false; constructor( private readonly container: HTMLElement @@ -291,10 +291,10 @@ class StatusBarCodiconLabel extends SimpleIconLabel { super(container); } - set showProgress(showProgress: boolean | 'syncing' | 'loading') { + set showProgress(showProgress: boolean | 'loading' | 'syncing') { if (this.currentShowProgress !== showProgress) { this.currentShowProgress = showProgress; - this.progressCodicon = renderIcon(showProgress === 'loading' ? spinningLoading : syncing); + this.progressCodicon = renderIcon(showProgress === 'syncing' ? syncing : spinningLoading); this.text = this.currentText; } } diff --git a/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts b/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts index 8fd7e353..8c0ecf35 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarModel.ts @@ -258,19 +258,34 @@ export class StatusbarViewModel extends Disposable { // - those with `priority: number` that can be compared // - those with `priority: string` that must be sorted // relative to another entry if possible - const mapEntryWithNumberedPriorityToIndex = new Map(); - const mapEntryWithRelativePriority = new Map(); + const mapEntryWithNumberedPriorityToIndex = new Map(); + const mapEntryWithRelativePriority = new Map>(); for (let i = 0; i < this._entries.length; i++) { const entry = this._entries[i]; if (typeof entry.priority.primary === 'number') { mapEntryWithNumberedPriorityToIndex.set(entry, i); } else { - let entries = mapEntryWithRelativePriority.get(entry.priority.primary.id); + const referenceEntryId = entry.priority.primary.id; + let entries = mapEntryWithRelativePriority.get(referenceEntryId); if (!entries) { - entries = []; - mapEntryWithRelativePriority.set(entry.priority.primary.id, entries); + + // It is possible that this entry references another entry + // that itself references an entry. In that case, we want + // to add it to the entries of the referenced entry. + + for (const relativeEntries of mapEntryWithRelativePriority.values()) { + if (relativeEntries.has(referenceEntryId)) { + entries = relativeEntries; + break; + } + } + + if (!entries) { + entries = new Map(); + mapEntryWithRelativePriority.set(referenceEntryId, entries); + } } - entries.push(entry); + entries.set(entry.id, entry); } } @@ -311,7 +326,8 @@ export class StatusbarViewModel extends Disposable { sortedEntries = []; for (const entry of sortedEntriesWithNumberedPriority) { - const relativeEntries = mapEntryWithRelativePriority.get(entry.id); + const relativeEntriesMap = mapEntryWithRelativePriority.get(entry.id); + const relativeEntries = relativeEntriesMap ? Array.from(relativeEntriesMap.values()) : undefined; // Fill relative entries to LEFT if (relativeEntries) { @@ -333,7 +349,7 @@ export class StatusbarViewModel extends Disposable { // Finally, just append all entries that reference another entry // that does not exist to the end of the list for (const [, entries] of mapEntryWithRelativePriority) { - sortedEntries.push(...entries); + sortedEntries.push(...entries.values()); } } diff --git a/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index f938ea7b..bac67111 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -433,7 +433,7 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { } // Figure out groups of entries with `compact` alignment - const compactEntryGroups = new Map>(); + const compactEntryGroups = new Map>(); for (const entry of mapIdToVisibleEntry.values()) { if ( isStatusbarEntryLocation(entry.priority.primary) && // entry references another entry as location @@ -448,11 +448,25 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { // Build a map of entries that are compact among each other let compactEntryGroup = compactEntryGroups.get(locationId); if (!compactEntryGroup) { - compactEntryGroup = new Set([entry, location]); - compactEntryGroups.set(locationId, compactEntryGroup); - } else { - compactEntryGroup.add(entry); + + // It is possible that this entry references another entry + // that itself references an entry. In that case, we want + // to add it to the entries of the referenced entry. + + for (const group of compactEntryGroups.values()) { + if (group.has(locationId)) { + compactEntryGroup = group; + break; + } + } + + if (!compactEntryGroup) { + compactEntryGroup = new Map(); + compactEntryGroups.set(locationId, compactEntryGroup); + } } + compactEntryGroup.set(entry.id, entry); + compactEntryGroup.set(location.id, location); // Adjust CSS classes to move compact items closer together if (entry.priority.primary.alignment === StatusbarAlignment.LEFT) { @@ -465,7 +479,6 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { } } - // Install mouse listeners to update hover feedback for // all compact entries that belong to each other const statusBarItemHoverBackground = this.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); @@ -473,7 +486,7 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { this.compactEntriesDisposable.value = new DisposableStore(); if (statusBarItemHoverBackground && statusBarItemCompactHoverBackground && !isHighContrast(this.theme.type)) { for (const [, compactEntryGroup] of compactEntryGroups) { - for (const compactEntry of compactEntryGroup) { + for (const compactEntry of compactEntryGroup.values()) { if (!compactEntry.hasCommand) { continue; // only show hover feedback when we have a command } diff --git a/patched-vscode/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/patched-vscode/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 59b379f4..88d54359 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -96,7 +96,7 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { container.classList.add('command-center-center'); container.classList.toggle('multiple', (this._submenu.actions.length > 1)); - const hover = this._store.add(this._hoverService.setupUpdatableHover(this._hoverDelegate, container, this.getTooltip())); + const hover = this._store.add(this._hoverService.setupManagedHover(this._hoverDelegate, container, this.getTooltip())); // update label & tooltip when window title changes this._store.add(this._windowTitle.onDidChange(() => { @@ -157,7 +157,7 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { labelElement.innerText = label; reset(container, searchIcon, labelElement); - const hover = this._store.add(that._hoverService.setupUpdatableHover(that._hoverDelegate, container, this.getTooltip())); + const hover = this._store.add(that._hoverService.setupManagedHover(that._hoverDelegate, container, this.getTooltip())); // update label & tooltip when window title changes this._store.add(that._windowTitle.onDidChange(() => { diff --git a/patched-vscode/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/patched-vscode/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 15afa9df..9a66a127 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/patched-vscode/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -138,11 +138,11 @@ color: var(--vscode-titleBar-activeForeground); } -.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { +.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: var(--vscode-titleBar-inactiveForeground); } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: inherit; } @@ -182,7 +182,7 @@ text-overflow: ellipsis; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { justify-content: flex-start; padding: 0 12px; } @@ -280,7 +280,7 @@ border-left: 1px solid transparent; } -/* Window Controls (Minimize, Max/Restore, Close) */ +/* Window Controls Container */ .monaco-workbench .part.titlebar .window-controls-container { display: flex; flex-grow: 0; @@ -292,7 +292,12 @@ height: 100%; } -/* Web WCO Sizing/Ordering */ +.monaco-workbench.fullscreen .part.titlebar .window-controls-container { + display: none; + background-color: transparent; +} + +/* Window Controls Container Web: Apply WCO environment variables (https://developer.mozilla.org/en-US/docs/Web/CSS/env#titlebar-area-x) */ .monaco-workbench.web .part.titlebar .titlebar-right .window-controls-container { width: calc(100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px)); height: env(titlebar-area-height, 35px); @@ -311,29 +316,31 @@ order: 1; } -/* Desktop Windows/Linux Window Controls*/ -.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container.primary { +/* Window Controls Container Desktop: apply zoom friendly size */ +.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container { width: calc(138px / var(--zoom-factor, 1)); } -.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.primary { +.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container { width: 138px; } +.monaco-workbench.linux:not(.web) .part.titlebar .window-controls-container.wco-enabled { + width: calc(var(--title-wco-width, 138px)); +} + +.monaco-workbench.linux:not(.web) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.wco-enabled { + width: var(--title-wco-width, 138px); +} + .monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container:not(.counter-zoom) .window-controls-container * { zoom: calc(1 / var(--zoom-factor, 1)); } -/* Desktop macOS Window Controls */ -.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container.primary { +.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container { width: 70px; } -.monaco-workbench.fullscreen .part.titlebar .window-controls-container { - display: none; - background-color: transparent; -} - /* Window Control Icons */ .monaco-workbench .part.titlebar .window-controls-container > .window-icon { display: flex; @@ -342,6 +349,11 @@ height: 100%; width: 46px; font-size: 16px; + color: var(--vscode-titleBar-activeForeground); +} + +.monaco-workbench .part.titlebar.inactive .window-controls-container > .window-icon { + color: var(--vscode-titleBar-inactiveForeground); } .monaco-workbench .part.titlebar .window-controls-container > .window-icon::before { @@ -376,7 +388,6 @@ z-index: 2500; -webkit-app-region: no-drag; height: 100%; - min-width: 28px; } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container { @@ -455,11 +466,3 @@ border-radius: 16px; text-align: center; } - -.monaco-workbench .part.titlebar .window-controls-container .window-icon { - color: var(--vscode-titleBar-activeForeground); -} - -.monaco-workbench .part.titlebar.inactive .window-controls-container .window-icon { - color: var(--vscode-titleBar-inactiveForeground); -} diff --git a/patched-vscode/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/patched-vscode/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 80c99f15..ff858e53 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/titlebarpart'; import { localize, localize2 } from 'vs/nls'; import { MultiWindowParts, Part } from 'vs/workbench/browser/part'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; -import { getZoomFactor, isWCOEnabled } from 'vs/base/browser/browser'; +import { getWCOTitlebarAreaRect, getZoomFactor, isWCOEnabled, onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, TitlebarStyle, hasCustomTitlebar, hasNativeTitlebar, DEFAULT_CUSTOM_TITLEBAR_HEIGHT } from 'vs/platform/window/common/window'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -21,7 +21,7 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative, platformLocale } from import { Color } from 'vs/base/common/color'; import { EventType, EventHelper, Dimension, append, $, addDisposableListener, prepend, reset, getWindow, getWindowId, isAncestor, getActiveDocument, isHTMLElement } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService, ActivityBarPosition, LayoutSettings, EditorActionsLocation, EditorTabsMode } from 'vs/workbench/services/layout/browser/layoutService'; @@ -55,6 +55,7 @@ import { IView } from 'vs/base/browser/ui/grid/grid'; import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export interface ITitleVariable { readonly name: string; @@ -101,6 +102,7 @@ export class BrowserTitleService extends MultiWindowParts i this._register(this.registerPart(this.mainPart)); this.registerActions(); + this.registerAPICommands(); } protected createMainTitlebarPart(): BrowserTitlebarPart { @@ -128,6 +130,22 @@ export class BrowserTitleService extends MultiWindowParts i })); } + private registerAPICommands(): void { + this._register(CommandsRegistry.registerCommand({ + id: 'registerWindowTitleVariable', + handler: (accessor: ServicesAccessor, name: string, contextKey: string) => { + this.registerVariables([{ name, contextKey }]); + }, + metadata: { + description: 'Registers a new title variable', + args: [ + { name: 'name', schema: { type: 'string' }, description: 'The name of the variable to register' }, + { name: 'contextKey', schema: { type: 'string' }, description: 'The context key to use for the value of the variable' } + ] + } + })); + } + //#region Auxiliary Titlebar Parts createAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer): IAuxiliaryTitlebarPart { @@ -149,8 +167,8 @@ export class BrowserTitleService extends MultiWindowParts i titlebarPart.updateProperties(this.properties); } - if (this.variables.length) { - titlebarPart.registerVariables(this.variables); + if (this.variables.size) { + titlebarPart.registerVariables(Array.from(this.variables.values())); } Event.once(titlebarPart.onWillDispose)(() => disposables.dispose()); @@ -179,13 +197,20 @@ export class BrowserTitleService extends MultiWindowParts i } } - private variables: ITitleVariable[] = []; + private readonly variables = new Map(); registerVariables(variables: ITitleVariable[]): void { - this.variables.push(...variables); + const newVariables: ITitleVariable[] = []; + + for (const variable of variables) { + if (!this.variables.has(variable.name)) { + this.variables.set(variable.name, variable); + newVariables.push(variable); + } + } for (const part of this.parts) { - part.registerVariables(variables); + part.registerVariables(newVariables); } } @@ -200,7 +225,11 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { readonly maximumWidth: number = Number.POSITIVE_INFINITY; get minimumHeight(): number { - const value = this.isCommandCenterVisible || (isWeb && isWCOEnabled()) ? DEFAULT_CUSTOM_TITLEBAR_HEIGHT : 30; + const wcoEnabled = isWeb && isWCOEnabled(); + let value = this.isCommandCenterVisible || wcoEnabled ? DEFAULT_CUSTOM_TITLEBAR_HEIGHT : 30; + if (wcoEnabled) { + value = Math.max(value, getWCOTitlebarAreaRect(getWindow(this.element))?.height ?? 0); + } return value / (this.preventZoom ? getZoomFactor(getWindow(this.element)) : 1); } @@ -220,7 +249,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { //#endregion protected rootContainer!: HTMLElement; - protected primaryWindowControls: HTMLElement | undefined; + protected windowControlsContainer: HTMLElement | undefined; protected dragRegion: HTMLElement | undefined; private title!: HTMLElement; @@ -447,21 +476,49 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.createActionToolBarMenus(); } - let primaryControlLocation = isMacintosh ? 'left' : 'right'; - if (isMacintosh && isNative) { + // Window Controls Container + if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { + let primaryWindowControlsLocation = isMacintosh ? 'left' : 'right'; + if (isMacintosh && isNative) { - // Check if the locale is RTL, macOS will move traffic lights in RTL locales - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo + // Check if the locale is RTL, macOS will move traffic lights in RTL locales + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo - const localeInfo = new Intl.Locale(platformLocale) as any; - if (localeInfo?.textInfo?.direction === 'rtl') { - primaryControlLocation = 'right'; + const localeInfo = new Intl.Locale(platformLocale) as any; + if (localeInfo?.textInfo?.direction === 'rtl') { + primaryWindowControlsLocation = 'right'; + } } - } - if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { - this.primaryWindowControls = append(primaryControlLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container.primary')); - append(primaryControlLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container.secondary')); + if (isMacintosh && isNative && primaryWindowControlsLocation === 'left') { + // macOS native: controls are on the left and the container is not needed to make room + // for something, except for web where a custom menu being supported). not putting the + // container helps with allowing to move the window when clicking very close to the + // window control buttons. + } else { + this.windowControlsContainer = append(primaryWindowControlsLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container')); + if (isWeb) { + // Web: its possible to have control overlays on both sides, for example on macOS + // with window controls on the left and PWA controls on the right. + append(primaryWindowControlsLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container')); + } + + if (isWCOEnabled()) { + this.windowControlsContainer.classList.add('wco-enabled'); + + const updateWCOWidthVariable = () => { + const targetWindow = getWindow(this.element); + const wcoTitlebarAreaRect = getWCOTitlebarAreaRect(targetWindow); + if (wcoTitlebarAreaRect) { + const wcoWidth = targetWindow.innerWidth - wcoTitlebarAreaRect.width - wcoTitlebarAreaRect.x; + this.windowControlsContainer?.style.setProperty('--title-wco-width', `${wcoWidth}px`); + } + }; + updateWCOWidthVariable(); + + this._register(onDidChangeZoomLevel(() => setTimeout(() => updateWCOWidthVariable(), 5))); // Somehow it does not get the right size without this timeout :-/ + } + } } // Context menu over title bar: depending on the OS and the location of the click this will either be @@ -634,7 +691,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.editorToolbarMenuDisposables.add(this.actionToolBar.actionRunner); } else { this.actionToolBar.actionRunner = new ActionRunner(); - this.actionToolBar.context = {}; + this.actionToolBar.context = undefined; this.editorToolbarMenuDisposables.add(this.actionToolBar.actionRunner); } diff --git a/patched-vscode/src/vs/workbench/browser/parts/views/checkbox.ts b/patched-vscode/src/vs/workbench/browser/parts/views/checkbox.ts index 6d6125e4..f428de9b 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/views/checkbox.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/views/checkbox.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; @@ -28,7 +28,7 @@ export class TreeItemCheckbox extends Disposable { public toggle: Toggle | undefined; private checkboxContainer: HTMLDivElement; public isDisposed = false; - private hover: IUpdatableHover | undefined; + private hover: IManagedHover | undefined; public static readonly checkboxClass = 'custom-view-tree-node-item-checkbox'; @@ -87,7 +87,7 @@ export class TreeItemCheckbox extends Disposable { private setHover(checkbox: ITreeItemCheckboxState) { if (this.toggle) { if (!this.hover) { - this.hover = this._register(this.hoverService.setupUpdatableHover(this.hoverDelegate, this.toggle.domNode, this.checkboxHoverContent(checkbox))); + this.hover = this._register(this.hoverService.setupManagedHover(this.hoverDelegate, this.toggle.domNode, this.checkboxHoverContent(checkbox))); } else { this.hover.update(checkbox.tooltip); } @@ -122,7 +122,7 @@ export class TreeItemCheckbox extends Disposable { private removeCheckbox() { const children = this.checkboxContainer.children; for (const child of children) { - this.checkboxContainer.removeChild(child); + child.remove(); } } } diff --git a/patched-vscode/src/vs/workbench/browser/parts/views/treeView.ts b/patched-vscode/src/vs/workbench/browser/parts/views/treeView.ts index db78e940..5b82b9f9 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/views/treeView.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/views/treeView.ts @@ -36,7 +36,7 @@ import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/plat import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression, IContextKey, IContextKeyChangeEvent, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { FileKind } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -70,11 +70,12 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; -import type { IUpdatableHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; import { parseLinkedText } from 'vs/base/common/linkedText'; import { Button } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IAccessibleViewInformationService } from 'vs/workbench/services/accessibility/common/accessibleViewInformationService'; +import { Command } from 'vs/editor/common/languages'; export class TreeViewPane extends ViewPane { @@ -175,15 +176,22 @@ class Root implements ITreeItem { children: ITreeItem[] | undefined = undefined; } -function isTreeCommandEnabled(treeCommand: TreeCommand, contextKeyService: IContextKeyService): boolean { - const command = CommandsRegistry.getCommand(treeCommand.originalId ? treeCommand.originalId : treeCommand.id); +function commandPreconditions(commandId: string): ContextKeyExpression | undefined { + const command = CommandsRegistry.getCommand(commandId); if (command) { const commandAction = MenuRegistry.getCommand(command.id); - const precondition = commandAction && commandAction.precondition; - if (precondition) { - return contextKeyService.contextMatchesRules(precondition); - } + return commandAction && commandAction.precondition; + } + return undefined; +} + +function isTreeCommandEnabled(treeCommand: TreeCommand | Command, contextKeyService: IContextKeyService): boolean { + const commandId: string = (treeCommand as TreeCommand).originalId ? (treeCommand as TreeCommand).originalId! : treeCommand.id; + const precondition = commandPreconditions(commandId); + if (precondition) { + return contextKeyService.contextMatchesRules(precondition); } + return true; } @@ -709,6 +717,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { dnd: this.treeViewDnd, overrideStyles: getLocationBasedViewColors(this.viewLocation).listOverrideStyles }) as WorkbenchAsyncDataTree); + + this.treeDisposables.add(renderer.onDidChangeMenuContext(e => e.forEach(e => this.tree?.rerender(e)))); + this.treeDisposables.add(this.tree); treeMenus.setContextKeyService(this.tree.contextKeyService); aligner.tree = this.tree; @@ -863,6 +874,20 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { button.onDidClick(_ => { this.openerService.open(node.href, { allowCommands: true }); }, null, disposables); + + const href = URI.parse(node.href); + if (href.scheme === Schemas.command) { + const preConditions = commandPreconditions(href.path); + if (preConditions) { + button.enabled = this.contextKeyService.contextMatchesRules(preConditions); + disposables.add(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(new Set(preConditions.keys()))) { + button.enabled = this.contextKeyService.contextMatchesRules(preConditions); + } + })); + } + } + disposables.add(button); hasFoundButton = true; result.push(buttonContainer); @@ -1135,7 +1160,6 @@ class TreeDataSource implements IAsyncDataSource { } interface ITreeExplorerTemplateData { - readonly elementDisposable: DisposableStore; readonly container: HTMLElement; readonly resourceLabel: IResourceLabel; readonly icon: HTMLElement; @@ -1151,6 +1175,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer = this._register(new Emitter()); readonly onDidChangeCheckboxState: Event = this._onDidChangeCheckboxState.event; + private _onDidChangeMenuContext: Emitter = this._register(new Emitter()); + readonly onDidChangeMenuContext: Event = this._onDidChangeMenuContext.event; + private _actionRunner: MultipleSelectionActionRunner | undefined; private _hoverDelegate: IHoverDelegate; private _hasCheckbox: boolean = false; @@ -1178,6 +1205,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { this.updateCheckboxes(items); })); + this._register(this.contextKeyService.onDidChangeContext(e => this.onDidChangeContext(e))); } get templateId(): string { @@ -1199,10 +1227,10 @@ class TreeRenderer extends Disposable implements ITreeRenderer, index: number, templateData: ITreeExplorerTemplateData): void { - templateData.elementDisposable.clear(); - const itemRenders = this._renderedElements.get(resource.element.handle) ?? []; const renderedIndex = itemRenders.findIndex(renderedItem => templateData === renderedItem.rendered); @@ -1491,7 +1531,6 @@ class TreeRenderer extends Disposable implements ITreeRenderer { + return new Map([ + ['view', this.id], + ['viewItem', element.contextValue] + ]); + } + + public getEntireMenuContexts(): ReadonlySet { + return this.menuService.getMenuContexts(this.getMenuId()); + } + + public getMenuId(): MenuId { + return MenuId.ViewItemContext; + } + + private getActions(menuId: MenuId, elements: ITreeItem[]): { primary: IAction[]; secondary: IAction[] } { if (!this.contextKeyService) { return { primary: [], secondary: [] }; } @@ -1646,16 +1706,14 @@ class TreeMenus implements IDisposable { let secondaryGroups: Map[] = []; for (let i = 0; i < elements.length; i++) { const element = elements[i]; - const contextKeyService = this.contextKeyService.createOverlay([ - ['view', this.id], - ['viewItem', element.contextValue] - ]); + const contextKeyService = this.contextKeyService.createOverlay(this.getElementOverlayContexts(element)); + + const menuData = this.menuService.getMenuActions(menuId, contextKeyService, { shouldForwardArgs: true }); - const menu = this.menuService.createMenu(menuId, contextKeyService); const primary: IAction[] = []; const secondary: IAction[] = []; - const result = { primary, secondary, menu }; - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, 'inline'); + const result = { primary, secondary }; + createAndFillInContextMenuActions(menuData, result, 'inline'); if (i === 0) { primaryGroups = this.createGroups(result.primary); secondaryGroups = this.createGroups(result.secondary); @@ -1663,12 +1721,6 @@ class TreeMenus implements IDisposable { this.filterNonUniversalActions(primaryGroups, result.primary); this.filterNonUniversalActions(secondaryGroups, result.secondary); } - if (listen && elements.length === 1) { - listen.add(menu.onDidChange(() => this._onDidChange.fire(element))); - listen.add(menu); - } else { - menu.dispose(); - } } return { primary: this.buildMenu(primaryGroups), secondary: this.buildMenu(secondaryGroups) }; diff --git a/patched-vscode/src/vs/workbench/browser/parts/views/viewFilter.ts b/patched-vscode/src/vs/workbench/browser/parts/views/viewFilter.ts index 3331c892..724a67b9 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/views/viewFilter.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/views/viewFilter.ts @@ -230,6 +230,8 @@ export class FilterWidget extends Widget { if (event.equals(KeyCode.Space) || event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) + || event.equals(KeyCode.Home) + || event.equals(KeyCode.End) ) { event.stopPropagation(); } diff --git a/patched-vscode/src/vs/workbench/browser/parts/views/viewPane.ts b/patched-vscode/src/vs/workbench/browser/parts/views/viewPane.ts index c62f1b0a..ea8047a2 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/views/viewPane.ts @@ -48,7 +48,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { PANEL_BACKGROUND, PANEL_STICKY_SCROLL_BACKGROUND, PANEL_STICKY_SCROLL_BORDER, PANEL_STICKY_SCROLL_SHADOW, SIDE_BAR_BACKGROUND, SIDE_BAR_STICKY_SCROLL_BACKGROUND, SIDE_BAR_STICKY_SCROLL_BORDER, SIDE_BAR_STICKY_SCROLL_SHADOW } from 'vs/workbench/common/theme'; @@ -354,11 +354,11 @@ export abstract class ViewPane extends Pane implements IView { private readonly showActions: ViewPaneShowActions; private headerContainer?: HTMLElement; private titleContainer?: HTMLElement; - private titleContainerHover?: IUpdatableHover; + private titleContainerHover?: IManagedHover; private titleDescriptionContainer?: HTMLElement; - private titleDescriptionContainerHover?: IUpdatableHover; + private titleDescriptionContainerHover?: IManagedHover; private iconContainer?: HTMLElement; - private iconContainerHover?: IUpdatableHover; + private iconContainerHover?: IManagedHover; protected twistiesContainer?: HTMLElement; private viewWelcomeController!: ViewWelcomeController; @@ -376,7 +376,7 @@ export abstract class ViewPane extends Pane implements IView { @IThemeService protected themeService: IThemeService, @ITelemetryService protected telemetryService: ITelemetryService, @IHoverService protected readonly hoverService: IHoverService, - protected readonly accessibleViewService?: IAccessibleViewInformationService + protected readonly accessibleViewInformationService?: IAccessibleViewInformationService ) { super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); @@ -391,7 +391,8 @@ export abstract class ViewPane extends Pane implements IView { const viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)); this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)))); - this.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs, renderShortTitle: true })); + const childInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); + this.menuActions = this._register(childInstantiationService.createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs, renderShortTitle: true })); this._register(this.menuActions.onDidChange(() => this.updateActions())); } @@ -540,19 +541,19 @@ export abstract class ViewPane extends Pane implements IView { const calculatedTitle = this.calculateTitle(title); this.titleContainer = append(container, $('h3.title', {}, calculatedTitle)); - this.titleContainerHover = this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.titleContainer, calculatedTitle)); + this.titleContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.titleContainer, calculatedTitle)); if (this._titleDescription) { this.setTitleDescription(this._titleDescription); } - this.iconContainerHover = this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.iconContainer, calculatedTitle)); + this.iconContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.iconContainer, calculatedTitle)); this.iconContainer.setAttribute('aria-label', this._getAriaLabel(calculatedTitle)); } private _getAriaLabel(title: string): string { const viewHasAccessibilityHelpContent = this.viewDescriptorService.getViewDescriptorById(this.id)?.accessibilityHelpContent; - const accessibleViewHasShownForView = this.accessibleViewService?.hasShownAccessibleView(this.id); + const accessibleViewHasShownForView = this.accessibleViewInformationService?.hasShownAccessibleView(this.id); if (!viewHasAccessibilityHelpContent || accessibleViewHasShownForView) { return title; } @@ -583,7 +584,7 @@ export abstract class ViewPane extends Pane implements IView { } else if (description && this.titleContainer) { this.titleDescriptionContainer = after(this.titleContainer, $('span.description', {}, description)); - this.titleDescriptionContainerHover = this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.titleDescriptionContainer, description)); + this.titleDescriptionContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.titleDescriptionContainer, description)); } } @@ -747,7 +748,8 @@ export abstract class FilterViewPane extends ViewPane { accessibleViewService?: IAccessibleViewInformationService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService, accessibleViewService); - this.filterWidget = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(FilterWidget, options.filterOptions)); + const childInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); + this.filterWidget = this._register(childInstantiationService.createInstance(FilterWidget, options.filterOptions)); } override getFilterWidget(): FilterWidget { diff --git a/patched-vscode/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/patched-vscode/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 38459b48..9cb5f592 100644 --- a/patched-vscode/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/patched-vscode/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -39,7 +39,7 @@ import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerMo import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchLayoutService, LayoutSettings, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { isHorizontal, IWorkbenchLayoutService, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const ViewsSubMenu = new MenuId('Views'); @@ -112,7 +112,7 @@ class ViewPaneDropOverlay extends Themable { this.paneElement.appendChild(this.container); this.paneElement.classList.add('dragged-over'); this._register(toDisposable(() => { - this.paneElement.removeChild(this.container); + this.container.remove(); this.paneElement.classList.remove('dragged-over'); })); @@ -593,6 +593,9 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getActionsContext(): unknown { + if (this.isViewMergedWithContainer()) { + return this.panes[0].getActionsContext(); + } return undefined; } @@ -625,8 +628,9 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { case ViewContainerLocation.Sidebar: case ViewContainerLocation.AuxiliaryBar: return Orientation.VERTICAL; - case ViewContainerLocation.Panel: - return this.layoutService.getPanelPosition() === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; + case ViewContainerLocation.Panel: { + return isHorizontal(this.layoutService.getPanelPosition()) ? Orientation.HORIZONTAL : Orientation.VERTICAL; + } } return Orientation.VERTICAL; diff --git a/patched-vscode/src/vs/workbench/browser/web.api.ts b/patched-vscode/src/vs/workbench/browser/web.api.ts index 1aa9425d..697e6742 100644 --- a/patched-vscode/src/vs/workbench/browser/web.api.ts +++ b/patched-vscode/src/vs/workbench/browser/web.api.ts @@ -298,11 +298,6 @@ export interface IWorkbenchConstructionOptions { */ readonly configurationDefaults?: Record; - /** - * Path to the user data directory. - */ - readonly userDataPath?: string - //#endregion //#region Profile options diff --git a/patched-vscode/src/vs/workbench/browser/web.main.ts b/patched-vscode/src/vs/workbench/browser/web.main.ts index 75c69c06..a7772c27 100644 --- a/patched-vscode/src/vs/workbench/browser/web.main.ts +++ b/patched-vscode/src/vs/workbench/browser/web.main.ts @@ -96,7 +96,6 @@ import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { TunnelSource } from 'vs/workbench/services/remote/common/tunnelModel'; import { mainWindow } from 'vs/base/browser/window'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { SagemakerServerClient } from 'vs/workbench/browser/client'; export class BrowserMain extends Disposable { @@ -132,9 +131,6 @@ export class BrowserMain extends Disposable { // Startup const instantiationService = workbench.startup(); - // Create instance of SagemakerServerClient - this._register(instantiationService.createInstance(SagemakerServerClient)); - // Window this._register(instantiationService.createInstance(BrowserWindow)); @@ -592,13 +588,14 @@ export class BrowserMain extends Disposable { } } - private async getCurrentProfile(workspace: IAnyWorkspaceIdentifier, userDataProfilesService: BrowserUserDataProfilesService, environmentService: IBrowserWorkbenchEnvironmentService): Promise { - if (environmentService.options?.profile) { - const profile = userDataProfilesService.profiles.find(p => p.name === environmentService.options?.profile?.name); + private async getCurrentProfile(workspace: IAnyWorkspaceIdentifier, userDataProfilesService: BrowserUserDataProfilesService, environmentService: BrowserWorkbenchEnvironmentService): Promise { + const profileName = environmentService.options?.profile?.name ?? environmentService.profile; + if (profileName) { + const profile = userDataProfilesService.profiles.find(p => p.name === profileName); if (profile) { return profile; } - return userDataProfilesService.createNamedProfile(environmentService.options?.profile?.name, undefined, workspace); + return userDataProfilesService.createNamedProfile(profileName, undefined, workspace); } return userDataProfilesService.getProfileForWorkspace(workspace) ?? userDataProfilesService.defaultProfile; } diff --git a/patched-vscode/src/vs/workbench/browser/window.ts b/patched-vscode/src/vs/workbench/browser/window.ts index e964af65..84c49ea9 100644 --- a/patched-vscode/src/vs/workbench/browser/window.ts +++ b/patched-vscode/src/vs/workbench/browser/window.ts @@ -133,9 +133,19 @@ export abstract class BaseWindow extends Disposable { continue; // skip over hidden windows (but never over main window) } - const handle = (window as any).vscodeOriginalSetTimeout.apply(this, [handlerFn, timeout, ...args]); + // we track didClear in case the browser does not properly clear the timeout + // this can happen for timeouts on unfocused windows + let didClear = false; + + const handle = (window as any).vscodeOriginalSetTimeout.apply(this, [(...args: unknown[]) => { + if (didClear) { + return; + } + handlerFn(...args); + }, timeout, ...args]); const timeoutDisposable = toDisposable(() => { + didClear = true; (window as any).vscodeOriginalClearTimeout(handle); timeoutDisposables.delete(timeoutDisposable); }); diff --git a/patched-vscode/src/vs/workbench/browser/workbench.contribution.ts b/patched-vscode/src/vs/workbench/browser/workbench.contribution.ts index 8494c963..bf162986 100644 --- a/patched-vscode/src/vs/workbench/browser/workbench.contribution.ts +++ b/patched-vscode/src/vs/workbench/browser/workbench.contribution.ts @@ -6,7 +6,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { localize } from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchSecurityConfiguration, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase, windowConfigurationNodeBase, DynamicWindowConfiguration } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; @@ -29,6 +29,12 @@ const registry = Registry.as(ConfigurationExtensions.Con registry.registerConfiguration({ ...workbenchConfigurationNodeBase, 'properties': { + 'workbench.externalBrowser': { + type: 'string', + markdownDescription: localize('browser', "Configure the browser to use for opening http or https links externally. This can either be the name of the browser (`edge`, `chrome`, `firefox`) or an absolute path to the browser's executable. Will use the system default if not set."), + included: isNative, + restricted: true + }, 'workbench.editor.titleScrollbarSizing': { type: 'string', enum: ['default', 'large'], @@ -102,9 +108,10 @@ const registry = Registry.as(ConfigurationExtensions.Con let customEditorLabelDescription = localize('workbench.editor.label.patterns', "Controls the rendering of the editor label. Each __Item__ is a pattern that matches a file path. Both relative and absolute file paths are supported. The relative path must include the WORKSPACE_FOLDER (e.g `WORKSPACE_FOLDER/src/**.tsx` or `*/src/**.tsx`). Absolute patterns must start with a `/`. In case multiple patterns match, the longest matching path will be picked. Each __Value__ is the template for the rendered editor when the __Item__ matches. Variables are substituted based on the context:"); customEditorLabelDescription += '\n- ' + [ localize('workbench.editor.label.dirname', "`${dirname}`: name of the folder in which the file is located (e.g. `WORKSPACE_FOLDER/folder/file.txt -> folder`)."), - localize('workbench.editor.label.nthdirname', "`${dirname(N)}`: name of the nth parent folder in which the file is located (e.g. `N=2: WORKSPACE_FOLDER/static/folder/file.txt -> WORKSPACE_FOLDER`). Folders can be picked from the start of the path by using negative numbers (e.g. `N=-1: WORKSPACE_FOLDER/folder/file.txt -> WORKSPACE_FOLDER`). If the __Item__ is an absolute pattern path, the first folder (`N=-1`) refers to the first folder in the absoulte path, otherwise it corresponds to the workspace folder."), + localize('workbench.editor.label.nthdirname', "`${dirname(N)}`: name of the nth parent folder in which the file is located (e.g. `N=2: WORKSPACE_FOLDER/static/folder/file.txt -> WORKSPACE_FOLDER`). Folders can be picked from the start of the path by using negative numbers (e.g. `N=-1: WORKSPACE_FOLDER/folder/file.txt -> WORKSPACE_FOLDER`). If the __Item__ is an absolute pattern path, the first folder (`N=-1`) refers to the first folder in the absolute path, otherwise it corresponds to the workspace folder."), localize('workbench.editor.label.filename', "`${filename}`: name of the file without the file extension (e.g. `WORKSPACE_FOLDER/folder/file.txt -> file`)."), localize('workbench.editor.label.extname', "`${extname}`: the file extension (e.g. `WORKSPACE_FOLDER/folder/file.txt -> txt`)."), + localize('workbench.editor.label.nthextname', "`${extname(N)}`: the nth extension of the file separated by '.' (e.g. `N=2: WORKSPACE_FOLDER/folder/file.ext1.ext2.ext3 -> ext1`). Extension can be picked from the start of the extension by using negative numbers (e.g. `N=-1: WORKSPACE_FOLDER/folder/file.ext1.ext2.ext3 -> ext2`)."), ].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations customEditorLabelDescription += '\n\n' + localize('customEditorLabelDescriptionExample', "Example: `\"**/static/**/*.html\": \"${filename} - ${dirname} (${extname})\"` will render a file `WORKSPACE_FOLDER/static/folder/file.html` as `file - folder (html)`."); @@ -112,8 +119,8 @@ const registry = Registry.as(ConfigurationExtensions.Con })(), additionalProperties: { - type: 'string', - markdownDescription: localize('workbench.editor.label.template', "The template which should be rendered when the pattern mtches. May include the variables ${dirname}, ${filename} and ${extname}."), + type: ['string', 'null'], + markdownDescription: localize('workbench.editor.label.template', "The template which should be rendered when the pattern matches. May include the variables ${dirname}, ${filename} and ${extname}."), minLength: 1, pattern: '.*[a-zA-Z0-9].*' }, @@ -288,7 +295,7 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.editor.enablePreview': { 'type': 'boolean', - 'description': localize('enablePreview', "Controls whether opened editors show as preview editors. Preview editors do not stay open, are reused until explicitly set to be kept open (via double-click or editing), and show file names in italics."), + 'description': localize('enablePreview', "Controls whether preview mode is used when editors open. There is a maximum of one preview mode editor per editor group. This editor displays its filename in italics on its tab or title label and in the Open Editors view. Its contents will be replaced by the next editor opened in preview mode. Making a change in a preview mode editor will persist it, as will a double-click on its label, or the 'Keep Open' option in its label context menu. Opening a file from Explorer with a double-click persists its editor immediately."), 'default': true }, 'workbench.editor.enablePreviewFromQuickOpen': { @@ -509,9 +516,9 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.panel.defaultLocation': { 'type': 'string', - 'enum': ['left', 'bottom', 'right'], + 'enum': ['left', 'bottom', 'top', 'right'], 'default': 'bottom', - 'description': localize('panelDefaultLocation', "Controls the default location of the panel (Terminal, Debug Console, Output, Problems) in a new workspace. It can either show at the bottom, right, or left of the editor area."), + 'description': localize('panelDefaultLocation', "Controls the default location of the panel (Terminal, Debug Console, Output, Problems) in a new workspace. It can either show at the bottom, top, right, or left of the editor area."), }, 'workbench.panel.opensMaximized': { 'type': 'string', diff --git a/patched-vscode/src/vs/workbench/browser/workbench.ts b/patched-vscode/src/vs/workbench/browser/workbench.ts index b0688133..7c11f502 100644 --- a/patched-vscode/src/vs/workbench/browser/workbench.ts +++ b/patched-vscode/src/vs/workbench/browser/workbench.ts @@ -50,6 +50,7 @@ import { AccessibilityProgressSignalScheduler } from 'vs/platform/accessibilityS import { setProgressAcccessibilitySignalScheduler } from 'vs/base/browser/ui/progressbar/progressAccessibilitySignal'; import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { NotificationAccessibleView } from 'vs/workbench/browser/parts/notifications/notificationAccessibleView'; +import { isESM } from 'vs/base/common/amd'; export interface IWorkbenchOptions { @@ -99,21 +100,20 @@ export class Workbench extends Layout { setUnexpectedErrorHandler(error => this.handleUnexpectedError(error, logService)); // Inform user about loading issues from the loader - interface AnnotatedLoadingError extends Error { - phase: 'loading'; - moduleId: string; - neededBy: string[]; - } - interface AnnotatedFactoryError extends Error { - phase: 'factory'; - moduleId: string; - } - interface AnnotatedValidationError extends Error { - phase: 'configuration'; - } - type AnnotatedError = AnnotatedLoadingError | AnnotatedFactoryError | AnnotatedValidationError; - - if (typeof mainWindow.require?.config === 'function') { + if (!isESM && typeof mainWindow.require?.config === 'function') { + interface AnnotatedLoadingError extends Error { + phase: 'loading'; + moduleId: string; + neededBy: string[]; + } + interface AnnotatedFactoryError extends Error { + phase: 'factory'; + moduleId: string; + } + interface AnnotatedValidationError extends Error { + phase: 'configuration'; + } + type AnnotatedError = AnnotatedLoadingError | AnnotatedFactoryError | AnnotatedValidationError; mainWindow.require.config({ onError: (err: AnnotatedError) => { if (err.phase === 'loading') { diff --git a/patched-vscode/src/vs/workbench/common/configuration.ts b/patched-vscode/src/vs/workbench/common/configuration.ts index 01b4d2b5..20bd1258 100644 --- a/patched-vscode/src/vs/workbench/common/configuration.ts +++ b/patched-vscode/src/vs/workbench/common/configuration.ts @@ -234,7 +234,7 @@ export class DynamicWorkbenchSecurityConfiguration extends Disposable implements } } -const CONFIG_NEW_WINDOW_PROFILE = 'window.newWindowProfile'; +export const CONFIG_NEW_WINDOW_PROFILE = 'window.newWindowProfile'; export class DynamicWindowConfiguration extends Disposable implements IWorkbenchContribution { @@ -272,7 +272,7 @@ export class DynamicWindowConfiguration extends Disposable implements IWorkbench 'default': null, 'enum': [...this.userDataProfilesService.profiles.map(profile => profile.name), null], 'enumItemLabels': [...this.userDataProfilesService.profiles.map(p => ''), localize('active window', "Active Window")], - 'description': localize('newWindowProfile', "Specifies the profile to use when opening a new window. If a profile name is provided, the new window will use that profile. If no profile name is provided, the new window will use the profile of the active window or the default profile if no active window exists."), + 'description': localize('newWindowProfile', "Specifies the profile to use when opening a new window. If a profile name is provided, the new window will use that profile. If no profile name is provided, the new window will use the profile of the active window or the Default profile if no active window exists."), 'scope': ConfigurationScope.APPLICATION, } } diff --git a/patched-vscode/src/vs/workbench/common/contextkeys.ts b/patched-vscode/src/vs/workbench/common/contextkeys.ts index 97937218..d02f9e0e 100644 --- a/patched-vscode/src/vs/workbench/common/contextkeys.ts +++ b/patched-vscode/src/vs/workbench/common/contextkeys.ts @@ -74,6 +74,7 @@ export const MultipleEditorGroupsContext = new RawContextKey('multipleE export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); export const MultipleEditorsSelectedInGroupContext = new RawContextKey('multipleEditorsSelectedInGroup', false, localize('multipleEditorsSelectedInGroup', "Whether multiple editors have been selected in an editor group")); export const TwoEditorsSelectedInGroupContext = new RawContextKey('twoEditorsSelectedInGroup', false, localize('twoEditorsSelectedInGroup', "Whether exactly two editors have been selected in an editor group")); +export const SelectedEditorsInGroupFileOrUntitledResourceContextKey = new RawContextKey('SelectedEditorsInGroupFileOrUntitledResourceContextKey', true, localize('SelectedEditorsInGroupFileOrUntitledResourceContextKey', "Whether all selected editors in a group have a file or untitled resource associated")); // Editor Part Context Keys export const EditorPartMultipleEditorGroupsContext = new RawContextKey('editorPartMultipleEditorGroups', false, localize('editorPartMultipleEditorGroups', "Whether there are multiple editor groups opened in an editor part")); diff --git a/patched-vscode/src/vs/workbench/common/contributions.ts b/patched-vscode/src/vs/workbench/common/contributions.ts index aaf1452c..f7e23fae 100644 --- a/patched-vscode/src/vs/workbench/common/contributions.ts +++ b/patched-vscode/src/vs/workbench/common/contributions.ts @@ -11,7 +11,7 @@ import { mark } from 'vs/base/common/performance'; import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { getOrSet } from 'vs/base/common/map'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, isDisposable } from 'vs/base/common/lifecycle'; import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; /** @@ -156,6 +156,7 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb private readonly contributionsById = new Map(); private readonly instancesById = new Map(); + private readonly instanceDisposables = this._register(new DisposableStore()); private readonly timingsByPhase = new Map>(); get timings() { return this.timingsByPhase; } @@ -249,6 +250,11 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb const environmentService = this.environmentService = accessor.get(IEnvironmentService); const editorPaneService = this.editorPaneService = accessor.get(IEditorPaneService); + // Dispose contributions on shutdown + this._register(lifecycleService.onDidShutdown(() => { + this.instanceDisposables.clear(); + })); + // Instantiate contributions by phase when they are ready for (const phase of [LifecyclePhase.Starting, LifecyclePhase.Ready, LifecyclePhase.Restored, LifecyclePhase.Eventually]) { this.instantiateByPhase(instantiationService, lifecycleService, logService, environmentService, phase); @@ -377,6 +383,9 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb this.instancesById.set(contribution.id, instance); this.contributionsById.delete(contribution.id); } + if (isDisposable(instance)) { + this.instanceDisposables.add(instance); + } } catch (error) { logService.error(`Unable to create workbench contribution '${contribution.id ?? contribution.ctor.name}'.`, error); } finally { diff --git a/patched-vscode/src/vs/workbench/common/editor.ts b/patched-vscode/src/vs/workbench/common/editor.ts index 02aff158..d3d6adb4 100644 --- a/patched-vscode/src/vs/workbench/common/editor.ts +++ b/patched-vscode/src/vs/workbench/common/editor.ts @@ -547,7 +547,7 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { * The list of resources to compare. * If not set, the resources are dynamically derived from the {@link multiDiffSource}. */ - readonly resources?: IResourceDiffEditorInput[]; + readonly resources?: IMultiDiffEditorResource[]; /** * Whether the editor should be serialized and stored for subsequent sessions. @@ -555,6 +555,9 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { readonly isTransient?: boolean; } +export interface IMultiDiffEditorResource extends IResourceDiffEditorInput { + readonly goToFileResource?: URI; +} export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; /** diff --git a/patched-vscode/src/vs/workbench/common/theme.ts b/patched-vscode/src/vs/workbench/common/theme.ts index 3412f81c..1e6aba05 100644 --- a/patched-vscode/src/vs/workbench/common/theme.ts +++ b/patched-vscode/src/vs/workbench/common/theme.ts @@ -28,19 +28,9 @@ export function WORKBENCH_BACKGROUND(theme: IColorTheme): Color { //#region Tab Background -export const TAB_ACTIVE_BACKGROUND = registerColor('tab.activeBackground', { - dark: editorBackground, - light: editorBackground, - hcDark: editorBackground, - hcLight: editorBackground -}, localize('tabActiveBackground', "Active tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); - -export const TAB_UNFOCUSED_ACTIVE_BACKGROUND = registerColor('tab.unfocusedActiveBackground', { - dark: TAB_ACTIVE_BACKGROUND, - light: TAB_ACTIVE_BACKGROUND, - hcDark: TAB_ACTIVE_BACKGROUND, - hcLight: TAB_ACTIVE_BACKGROUND, -}, localize('tabUnfocusedActiveBackground', "Active tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_ACTIVE_BACKGROUND = registerColor('tab.activeBackground', editorBackground, localize('tabActiveBackground', "Active tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_UNFOCUSED_ACTIVE_BACKGROUND = registerColor('tab.unfocusedActiveBackground', TAB_ACTIVE_BACKGROUND, localize('tabUnfocusedActiveBackground', "Active tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_INACTIVE_BACKGROUND = registerColor('tab.inactiveBackground', { dark: '#2D2D2D', @@ -49,12 +39,7 @@ export const TAB_INACTIVE_BACKGROUND = registerColor('tab.inactiveBackground', { hcLight: null, }, localize('tabInactiveBackground', "Inactive tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); -export const TAB_UNFOCUSED_INACTIVE_BACKGROUND = registerColor('tab.unfocusedInactiveBackground', { - dark: TAB_INACTIVE_BACKGROUND, - light: TAB_INACTIVE_BACKGROUND, - hcDark: TAB_INACTIVE_BACKGROUND, - hcLight: TAB_INACTIVE_BACKGROUND -}, localize('tabUnfocusedInactiveBackground', "Inactive tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_UNFOCUSED_INACTIVE_BACKGROUND = registerColor('tab.unfocusedInactiveBackground', TAB_INACTIVE_BACKGROUND, localize('tabUnfocusedInactiveBackground', "Inactive tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion @@ -92,12 +77,7 @@ export const TAB_UNFOCUSED_INACTIVE_FOREGROUND = registerColor('tab.unfocusedIna //#region Tab Hover Foreground/Background -export const TAB_HOVER_BACKGROUND = registerColor('tab.hoverBackground', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('tabHoverBackground', "Tab background color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_HOVER_BACKGROUND = registerColor('tab.hoverBackground', null, localize('tabHoverBackground', "Tab background color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_HOVER_BACKGROUND = registerColor('tab.unfocusedHoverBackground', { dark: transparent(TAB_HOVER_BACKGROUND, 0.5), @@ -106,12 +86,7 @@ export const TAB_UNFOCUSED_HOVER_BACKGROUND = registerColor('tab.unfocusedHoverB hcLight: null }, localize('tabUnfocusedHoverBackground', "Tab background color in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); -export const TAB_HOVER_FOREGROUND = registerColor('tab.hoverForeground', { - dark: null, - light: null, - hcDark: null, - hcLight: null, -}, localize('tabHoverForeground', "Tab foreground color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_HOVER_FOREGROUND = registerColor('tab.hoverForeground', null, localize('tabHoverForeground', "Tab foreground color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_HOVER_FOREGROUND = registerColor('tab.unfocusedHoverForeground', { dark: transparent(TAB_HOVER_FOREGROUND, 0.5), @@ -138,12 +113,7 @@ export const TAB_LAST_PINNED_BORDER = registerColor('tab.lastPinnedBorder', { hcLight: contrastBorder }, localize('lastPinnedTabBorder', "Border to separate pinned tabs from other tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); -export const TAB_ACTIVE_BORDER = registerColor('tab.activeBorder', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('tabActiveBorder', "Border on the bottom of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_ACTIVE_BORDER = registerColor('tab.activeBorder', null, localize('tabActiveBorder', "Border on the bottom of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_ACTIVE_BORDER = registerColor('tab.unfocusedActiveBorder', { dark: transparent(TAB_ACTIVE_BORDER, 0.5), @@ -166,34 +136,14 @@ export const TAB_UNFOCUSED_ACTIVE_BORDER_TOP = registerColor('tab.unfocusedActiv hcLight: '#B5200D' }, localize('tabActiveUnfocusedBorderTop', "Border to the top of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); -export const TAB_SELECTED_BORDER_TOP = registerColor('tab.selectedBorderTop', { - dark: TAB_ACTIVE_BORDER_TOP, - light: TAB_ACTIVE_BORDER_TOP, - hcDark: TAB_ACTIVE_BORDER_TOP, - hcLight: TAB_ACTIVE_BORDER_TOP -}, localize('tabSelectedBorderTop', "Border to the top of a selected tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); - -export const TAB_SELECTED_BACKGROUND = registerColor('tab.selectedBackground', { - dark: TAB_ACTIVE_BACKGROUND, - light: TAB_ACTIVE_BACKGROUND, - hcDark: TAB_ACTIVE_BACKGROUND, - hcLight: TAB_ACTIVE_BACKGROUND -}, localize('tabSelectedBackground', "Background of a selected tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); - -export const TAB_SELECTED_FOREGROUND = registerColor('tab.selectedForeground', { - dark: TAB_ACTIVE_FOREGROUND, - light: TAB_ACTIVE_FOREGROUND, - hcDark: TAB_ACTIVE_FOREGROUND, - hcLight: TAB_ACTIVE_FOREGROUND -}, localize('tabSelectedForeground', "Foreground of a selected tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_SELECTED_BORDER_TOP = registerColor('tab.selectedBorderTop', TAB_ACTIVE_BORDER_TOP, localize('tabSelectedBorderTop', "Border to the top of a selected tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_SELECTED_BACKGROUND = registerColor('tab.selectedBackground', TAB_ACTIVE_BACKGROUND, localize('tabSelectedBackground', "Background of a selected tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); -export const TAB_HOVER_BORDER = registerColor('tab.hoverBorder', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('tabHoverBorder', "Border to highlight tabs when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_SELECTED_FOREGROUND = registerColor('tab.selectedForeground', TAB_ACTIVE_FOREGROUND, localize('tabSelectedForeground', "Foreground of a selected tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + + +export const TAB_HOVER_BORDER = registerColor('tab.hoverBorder', null, localize('tabHoverBorder', "Border to highlight tabs when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_HOVER_BORDER = registerColor('tab.unfocusedHoverBorder', { dark: transparent(TAB_HOVER_BORDER, 0.5), @@ -249,19 +199,9 @@ export const TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER = registerColor('tab.unfocus // < --- Editors --- > -export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', { - dark: editorBackground, - light: editorBackground, - hcDark: editorBackground, - hcLight: editorBackground -}, localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout.")); +export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', editorBackground, localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout.")); -export const EDITOR_GROUP_EMPTY_BACKGROUND = registerColor('editorGroup.emptyBackground', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('editorGroupEmptyBackground', "Background color of an empty editor group. Editor groups are the containers of editors.")); +export const EDITOR_GROUP_EMPTY_BACKGROUND = registerColor('editorGroup.emptyBackground', null, localize('editorGroupEmptyBackground', "Background color of an empty editor group. Editor groups are the containers of editors.")); export const EDITOR_GROUP_FOCUSED_EMPTY_BORDER = registerColor('editorGroup.focusedEmptyBorder', { dark: null, @@ -277,19 +217,9 @@ export const EDITOR_GROUP_HEADER_TABS_BACKGROUND = registerColor('editorGroupHea hcLight: null }, localize('tabsContainerBackground', "Background color of the editor group title header when tabs are enabled. Editor groups are the containers of editors.")); -export const EDITOR_GROUP_HEADER_TABS_BORDER = registerColor('editorGroupHeader.tabsBorder', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('tabsContainerBorder', "Border color of the editor group title header when tabs are enabled. Editor groups are the containers of editors.")); +export const EDITOR_GROUP_HEADER_TABS_BORDER = registerColor('editorGroupHeader.tabsBorder', null, localize('tabsContainerBorder', "Border color of the editor group title header when tabs are enabled. Editor groups are the containers of editors.")); -export const EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND = registerColor('editorGroupHeader.noTabsBackground', { - dark: editorBackground, - light: editorBackground, - hcDark: editorBackground, - hcLight: editorBackground -}, localize('editorGroupHeaderBackground', "Background color of the editor group title header when (`\"workbench.editor.showTabs\": \"single\"`). Editor groups are the containers of editors.")); +export const EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND = registerColor('editorGroupHeader.noTabsBackground', editorBackground, localize('editorGroupHeaderBackground', "Background color of the editor group title header when (`\"workbench.editor.showTabs\": \"single\"`). Editor groups are the containers of editors.")); export const EDITOR_GROUP_HEADER_BORDER = registerColor('editorGroupHeader.border', { dark: null, @@ -312,19 +242,9 @@ export const EDITOR_DRAG_AND_DROP_BACKGROUND = registerColor('editorGroup.dropBa hcLight: Color.fromHex('#0F4A85').transparent(0.50) }, localize('editorDragAndDropBackground', "Background color when dragging editors around. The color should have transparency so that the editor contents can still shine through.")); -export const EDITOR_DROP_INTO_PROMPT_FOREGROUND = registerColor('editorGroup.dropIntoPromptForeground', { - dark: editorWidgetForeground, - light: editorWidgetForeground, - hcDark: editorWidgetForeground, - hcLight: editorWidgetForeground -}, localize('editorDropIntoPromptForeground', "Foreground color of text shown over editors when dragging files. This text informs the user that they can hold shift to drop into the editor.")); +export const EDITOR_DROP_INTO_PROMPT_FOREGROUND = registerColor('editorGroup.dropIntoPromptForeground', editorWidgetForeground, localize('editorDropIntoPromptForeground', "Foreground color of text shown over editors when dragging files. This text informs the user that they can hold shift to drop into the editor.")); -export const EDITOR_DROP_INTO_PROMPT_BACKGROUND = registerColor('editorGroup.dropIntoPromptBackground', { - dark: editorWidgetBackground, - light: editorWidgetBackground, - hcDark: editorWidgetBackground, - hcLight: editorWidgetBackground -}, localize('editorDropIntoPromptBackground', "Background color of text shown over editors when dragging files. This text informs the user that they can hold shift to drop into the editor.")); +export const EDITOR_DROP_INTO_PROMPT_BACKGROUND = registerColor('editorGroup.dropIntoPromptBackground', editorWidgetBackground, localize('editorDropIntoPromptBackground', "Background color of text shown over editors when dragging files. This text informs the user that they can hold shift to drop into the editor.")); export const EDITOR_DROP_INTO_PROMPT_BORDER = registerColor('editorGroup.dropIntoPromptBorder', { dark: null, @@ -333,28 +253,13 @@ export const EDITOR_DROP_INTO_PROMPT_BORDER = registerColor('editorGroup.dropInt hcLight: contrastBorder }, localize('editorDropIntoPromptBorder', "Border color of text shown over editors when dragging files. This text informs the user that they can hold shift to drop into the editor.")); -export const SIDE_BY_SIDE_EDITOR_HORIZONTAL_BORDER = registerColor('sideBySideEditor.horizontalBorder', { - dark: EDITOR_GROUP_BORDER, - light: EDITOR_GROUP_BORDER, - hcDark: EDITOR_GROUP_BORDER, - hcLight: EDITOR_GROUP_BORDER -}, localize('sideBySideEditor.horizontalBorder', "Color to separate two editors from each other when shown side by side in an editor group from top to bottom.")); +export const SIDE_BY_SIDE_EDITOR_HORIZONTAL_BORDER = registerColor('sideBySideEditor.horizontalBorder', EDITOR_GROUP_BORDER, localize('sideBySideEditor.horizontalBorder', "Color to separate two editors from each other when shown side by side in an editor group from top to bottom.")); -export const SIDE_BY_SIDE_EDITOR_VERTICAL_BORDER = registerColor('sideBySideEditor.verticalBorder', { - dark: EDITOR_GROUP_BORDER, - light: EDITOR_GROUP_BORDER, - hcDark: EDITOR_GROUP_BORDER, - hcLight: EDITOR_GROUP_BORDER -}, localize('sideBySideEditor.verticalBorder', "Color to separate two editors from each other when shown side by side in an editor group from left to right.")); +export const SIDE_BY_SIDE_EDITOR_VERTICAL_BORDER = registerColor('sideBySideEditor.verticalBorder', EDITOR_GROUP_BORDER, localize('sideBySideEditor.verticalBorder', "Color to separate two editors from each other when shown side by side in an editor group from left to right.")); // < --- Panels --- > -export const PANEL_BACKGROUND = registerColor('panel.background', { - dark: editorBackground, - light: editorBackground, - hcDark: editorBackground, - hcLight: editorBackground -}, localize('panelBackground', "Panel background color. Panels are shown below the editor area and contain views like output and integrated terminal.")); +export const PANEL_BACKGROUND = registerColor('panel.background', editorBackground, localize('panelBackground', "Panel background color. Panels are shown below the editor area and contain views like output and integrated terminal.")); export const PANEL_BORDER = registerColor('panel.border', { dark: Color.fromHex('#808080').transparent(0.35), @@ -391,19 +296,9 @@ export const PANEL_INPUT_BORDER = registerColor('panelInput.border', { hcLight: inputBorder }, localize('panelInputBorder', "Input box border for inputs in the panel.")); -export const PANEL_DRAG_AND_DROP_BORDER = registerColor('panel.dropBorder', { - dark: PANEL_ACTIVE_TITLE_FOREGROUND, - light: PANEL_ACTIVE_TITLE_FOREGROUND, - hcDark: PANEL_ACTIVE_TITLE_FOREGROUND, - hcLight: PANEL_ACTIVE_TITLE_FOREGROUND -}, localize('panelDragAndDropBorder', "Drag and drop feedback color for the panel titles. Panels are shown below the editor area and contain views like output and integrated terminal.")); +export const PANEL_DRAG_AND_DROP_BORDER = registerColor('panel.dropBorder', PANEL_ACTIVE_TITLE_FOREGROUND, localize('panelDragAndDropBorder', "Drag and drop feedback color for the panel titles. Panels are shown below the editor area and contain views like output and integrated terminal.")); -export const PANEL_SECTION_DRAG_AND_DROP_BACKGROUND = registerColor('panelSection.dropBackground', { - dark: EDITOR_DRAG_AND_DROP_BACKGROUND, - light: EDITOR_DRAG_AND_DROP_BACKGROUND, - hcDark: EDITOR_DRAG_AND_DROP_BACKGROUND, - hcLight: EDITOR_DRAG_AND_DROP_BACKGROUND -}, localize('panelSectionDragAndDropBackground', "Drag and drop feedback color for the panel sections. The color should have transparency so that the panel sections can still shine through. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); +export const PANEL_SECTION_DRAG_AND_DROP_BACKGROUND = registerColor('panelSection.dropBackground', EDITOR_DRAG_AND_DROP_BACKGROUND, localize('panelSectionDragAndDropBackground', "Drag and drop feedback color for the panel sections. The color should have transparency so that the panel sections can still shine through. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); export const PANEL_SECTION_HEADER_BACKGROUND = registerColor('panelSectionHeader.background', { dark: Color.fromHex('#808080').transparent(0.2), @@ -412,64 +307,24 @@ export const PANEL_SECTION_HEADER_BACKGROUND = registerColor('panelSectionHeader hcLight: null, }, localize('panelSectionHeaderBackground', "Panel section header background color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); -export const PANEL_SECTION_HEADER_FOREGROUND = registerColor('panelSectionHeader.foreground', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('panelSectionHeaderForeground', "Panel section header foreground color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); +export const PANEL_SECTION_HEADER_FOREGROUND = registerColor('panelSectionHeader.foreground', null, localize('panelSectionHeaderForeground', "Panel section header foreground color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); -export const PANEL_SECTION_HEADER_BORDER = registerColor('panelSectionHeader.border', { - dark: contrastBorder, - light: contrastBorder, - hcDark: contrastBorder, - hcLight: contrastBorder -}, localize('panelSectionHeaderBorder', "Panel section header border color used when multiple views are stacked vertically in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); - -export const PANEL_SECTION_BORDER = registerColor('panelSection.border', { - dark: PANEL_BORDER, - light: PANEL_BORDER, - hcDark: PANEL_BORDER, - hcLight: PANEL_BORDER -}, localize('panelSectionBorder', "Panel section border color used when multiple views are stacked horizontally in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); - -export const PANEL_STICKY_SCROLL_BACKGROUND = registerColor('panelStickyScroll.background', { - dark: PANEL_BACKGROUND, - light: PANEL_BACKGROUND, - hcDark: PANEL_BACKGROUND, - hcLight: PANEL_BACKGROUND -}, localize('panelStickyScrollBackground', "Background color of sticky scroll in the panel.")); - -export const PANEL_STICKY_SCROLL_BORDER = registerColor('panelStickyScroll.border', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('panelStickyScrollBorder', "Border color of sticky scroll in the panel.")); +export const PANEL_SECTION_HEADER_BORDER = registerColor('panelSectionHeader.border', contrastBorder, localize('panelSectionHeaderBorder', "Panel section header border color used when multiple views are stacked vertically in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); + +export const PANEL_SECTION_BORDER = registerColor('panelSection.border', PANEL_BORDER, localize('panelSectionBorder', "Panel section border color used when multiple views are stacked horizontally in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); -export const PANEL_STICKY_SCROLL_SHADOW = registerColor('panelStickyScroll.shadow', { - dark: scrollbarShadow, - light: scrollbarShadow, - hcDark: scrollbarShadow, - hcLight: scrollbarShadow -}, localize('panelStickyScrollShadow', "Shadow color of sticky scroll in the panel.")); +export const PANEL_STICKY_SCROLL_BACKGROUND = registerColor('panelStickyScroll.background', PANEL_BACKGROUND, localize('panelStickyScrollBackground', "Background color of sticky scroll in the panel.")); + +export const PANEL_STICKY_SCROLL_BORDER = registerColor('panelStickyScroll.border', null, localize('panelStickyScrollBorder', "Border color of sticky scroll in the panel.")); + +export const PANEL_STICKY_SCROLL_SHADOW = registerColor('panelStickyScroll.shadow', scrollbarShadow, localize('panelStickyScrollShadow', "Shadow color of sticky scroll in the panel.")); // < --- Output Editor --> -const OUTPUT_VIEW_BACKGROUND = registerColor('outputView.background', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('outputViewBackground', "Output view background color.")); +const OUTPUT_VIEW_BACKGROUND = registerColor('outputView.background', null, localize('outputViewBackground', "Output view background color.")); -registerColor('outputViewStickyScroll.background', { - dark: OUTPUT_VIEW_BACKGROUND, - light: OUTPUT_VIEW_BACKGROUND, - hcDark: OUTPUT_VIEW_BACKGROUND, - hcLight: OUTPUT_VIEW_BACKGROUND -}, localize('outputViewStickyScrollBackground', "Output view sticky scroll background color.")); +registerColor('outputViewStickyScroll.background', OUTPUT_VIEW_BACKGROUND, localize('outputViewStickyScrollBackground', "Output view sticky scroll background color.")); // < --- Banner --- > @@ -481,19 +336,9 @@ export const BANNER_BACKGROUND = registerColor('banner.background', { hcLight: listActiveSelectionBackground }, localize('banner.background', "Banner background color. The banner is shown under the title bar of the window.")); -export const BANNER_FOREGROUND = registerColor('banner.foreground', { - dark: listActiveSelectionForeground, - light: listActiveSelectionForeground, - hcDark: listActiveSelectionForeground, - hcLight: listActiveSelectionForeground -}, localize('banner.foreground', "Banner foreground color. The banner is shown under the title bar of the window.")); +export const BANNER_FOREGROUND = registerColor('banner.foreground', listActiveSelectionForeground, localize('banner.foreground', "Banner foreground color. The banner is shown under the title bar of the window.")); -export const BANNER_ICON_FOREGROUND = registerColor('banner.iconForeground', { - dark: editorInfoForeground, - light: editorInfoForeground, - hcDark: editorInfoForeground, - hcLight: editorInfoForeground -}, localize('banner.iconForeground', "Banner icon color. The banner is shown under the title bar of the window.")); +export const BANNER_ICON_FOREGROUND = registerColor('banner.iconForeground', editorInfoForeground, localize('banner.iconForeground', "Banner icon color. The banner is shown under the title bar of the window.")); // < --- Status --- > @@ -504,12 +349,7 @@ export const STATUS_BAR_FOREGROUND = registerColor('statusBar.foreground', { hcLight: editorForeground }, localize('statusBarForeground', "Status bar foreground color when a workspace or folder is opened. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_NO_FOLDER_FOREGROUND = registerColor('statusBar.noFolderForeground', { - dark: STATUS_BAR_FOREGROUND, - light: STATUS_BAR_FOREGROUND, - hcDark: STATUS_BAR_FOREGROUND, - hcLight: STATUS_BAR_FOREGROUND -}, localize('statusBarNoFolderForeground', "Status bar foreground color when no folder is opened. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_NO_FOLDER_FOREGROUND = registerColor('statusBar.noFolderForeground', STATUS_BAR_FOREGROUND, localize('statusBarNoFolderForeground', "Status bar foreground color when no folder is opened. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_BACKGROUND = registerColor('statusBar.background', { dark: '#007ACC', @@ -539,12 +379,7 @@ export const STATUS_BAR_FOCUS_BORDER = registerColor('statusBar.focusBorder', { hcLight: STATUS_BAR_FOREGROUND }, localize('statusBarFocusBorder', "Status bar border color when focused on keyboard navigation. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_NO_FOLDER_BORDER = registerColor('statusBar.noFolderBorder', { - dark: STATUS_BAR_BORDER, - light: STATUS_BAR_BORDER, - hcDark: STATUS_BAR_BORDER, - hcLight: STATUS_BAR_BORDER -}, localize('statusBarNoFolderBorder', "Status bar border color separating to the sidebar and editor when no folder is opened. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_NO_FOLDER_BORDER = registerColor('statusBar.noFolderBorder', STATUS_BAR_BORDER, localize('statusBarNoFolderBorder', "Status bar border color separating to the sidebar and editor when no folder is opened. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_ITEM_ACTIVE_BACKGROUND = registerColor('statusBarItem.activeBackground', { dark: Color.white.transparent(0.18), @@ -567,12 +402,7 @@ export const STATUS_BAR_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.hov hcLight: Color.black.transparent(0.12) }, localize('statusBarItemHoverBackground', "Status bar item background color when hovering. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.hoverForeground', { - dark: STATUS_BAR_FOREGROUND, - light: STATUS_BAR_FOREGROUND, - hcDark: STATUS_BAR_FOREGROUND, - hcLight: STATUS_BAR_FOREGROUND -}, localize('statusBarItemHoverForeground', "Status bar item foreground color when hovering. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.hoverForeground', STATUS_BAR_FOREGROUND, localize('statusBarItemHoverForeground', "Status bar item foreground color when hovering. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_ITEM_COMPACT_HOVER_BACKGROUND = registerColor('statusBarItem.compactHoverBackground', { dark: Color.white.transparent(0.20), @@ -581,26 +411,11 @@ export const STATUS_BAR_ITEM_COMPACT_HOVER_BACKGROUND = registerColor('statusBar hcLight: Color.black.transparent(0.20) }, localize('statusBarItemCompactHoverBackground', "Status bar item background color when hovering an item that contains two hovers. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_PROMINENT_ITEM_FOREGROUND = registerColor('statusBarItem.prominentForeground', { - dark: STATUS_BAR_FOREGROUND, - light: STATUS_BAR_FOREGROUND, - hcDark: STATUS_BAR_FOREGROUND, - hcLight: STATUS_BAR_FOREGROUND -}, localize('statusBarProminentItemForeground', "Status bar prominent items foreground color. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); - -export const STATUS_BAR_PROMINENT_ITEM_BACKGROUND = registerColor('statusBarItem.prominentBackground', { - dark: Color.black.transparent(0.5), - light: Color.black.transparent(0.5), - hcDark: Color.black.transparent(0.5), - hcLight: Color.black.transparent(0.5), -}, localize('statusBarProminentItemBackground', "Status bar prominent items background color. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); - -export const STATUS_BAR_PROMINENT_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.prominentHoverForeground', { - dark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - light: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcDark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcLight: STATUS_BAR_ITEM_HOVER_FOREGROUND -}, localize('statusBarProminentItemHoverForeground', "Status bar prominent items foreground color when hovering. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_PROMINENT_ITEM_FOREGROUND = registerColor('statusBarItem.prominentForeground', STATUS_BAR_FOREGROUND, localize('statusBarProminentItemForeground', "Status bar prominent items foreground color. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); + +export const STATUS_BAR_PROMINENT_ITEM_BACKGROUND = registerColor('statusBarItem.prominentBackground', Color.black.transparent(0.5), localize('statusBarProminentItemBackground', "Status bar prominent items background color. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); + +export const STATUS_BAR_PROMINENT_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.prominentHoverForeground', STATUS_BAR_ITEM_HOVER_FOREGROUND, localize('statusBarProminentItemHoverForeground', "Status bar prominent items foreground color when hovering. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.prominentHoverBackground', { dark: Color.black.transparent(0.3), @@ -616,26 +431,11 @@ export const STATUS_BAR_ERROR_ITEM_BACKGROUND = registerColor('statusBarItem.err hcLight: '#B5200D' }, localize('statusBarErrorItemBackground', "Status bar error items background color. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_ERROR_ITEM_FOREGROUND = registerColor('statusBarItem.errorForeground', { - dark: Color.white, - light: Color.white, - hcDark: Color.white, - hcLight: Color.white -}, localize('statusBarErrorItemForeground', "Status bar error items foreground color. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_ERROR_ITEM_FOREGROUND = registerColor('statusBarItem.errorForeground', Color.white, localize('statusBarErrorItemForeground', "Status bar error items foreground color. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_ERROR_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.errorHoverForeground', { - dark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - light: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcDark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcLight: STATUS_BAR_ITEM_HOVER_FOREGROUND -}, localize('statusBarErrorItemHoverForeground', "Status bar error items foreground color when hovering. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_ERROR_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.errorHoverForeground', STATUS_BAR_ITEM_HOVER_FOREGROUND, localize('statusBarErrorItemHoverForeground', "Status bar error items foreground color when hovering. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_ERROR_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.errorHoverBackground', { - dark: STATUS_BAR_ITEM_HOVER_BACKGROUND, - light: STATUS_BAR_ITEM_HOVER_BACKGROUND, - hcDark: STATUS_BAR_ITEM_HOVER_BACKGROUND, - hcLight: STATUS_BAR_ITEM_HOVER_BACKGROUND -}, localize('statusBarErrorItemHoverBackground', "Status bar error items background color when hovering. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_ERROR_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.errorHoverBackground', STATUS_BAR_ITEM_HOVER_BACKGROUND, localize('statusBarErrorItemHoverBackground', "Status bar error items background color when hovering. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_WARNING_ITEM_BACKGROUND = registerColor('statusBarItem.warningBackground', { dark: darken(editorWarningForeground, .4), @@ -644,26 +444,11 @@ export const STATUS_BAR_WARNING_ITEM_BACKGROUND = registerColor('statusBarItem.w hcLight: '#895503' }, localize('statusBarWarningItemBackground', "Status bar warning items background color. Warning items stand out from other status bar entries to indicate warning conditions. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_WARNING_ITEM_FOREGROUND = registerColor('statusBarItem.warningForeground', { - dark: Color.white, - light: Color.white, - hcDark: Color.white, - hcLight: Color.white -}, localize('statusBarWarningItemForeground', "Status bar warning items foreground color. Warning items stand out from other status bar entries to indicate warning conditions. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_WARNING_ITEM_FOREGROUND = registerColor('statusBarItem.warningForeground', Color.white, localize('statusBarWarningItemForeground', "Status bar warning items foreground color. Warning items stand out from other status bar entries to indicate warning conditions. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_WARNING_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.warningHoverForeground', { - dark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - light: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcDark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcLight: STATUS_BAR_ITEM_HOVER_FOREGROUND -}, localize('statusBarWarningItemHoverForeground', "Status bar warning items foreground color when hovering. Warning items stand out from other status bar entries to indicate warning conditions. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_WARNING_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.warningHoverForeground', STATUS_BAR_ITEM_HOVER_FOREGROUND, localize('statusBarWarningItemHoverForeground', "Status bar warning items foreground color when hovering. Warning items stand out from other status bar entries to indicate warning conditions. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_WARNING_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.warningHoverBackground', { - dark: STATUS_BAR_ITEM_HOVER_BACKGROUND, - light: STATUS_BAR_ITEM_HOVER_BACKGROUND, - hcDark: STATUS_BAR_ITEM_HOVER_BACKGROUND, - hcLight: STATUS_BAR_ITEM_HOVER_BACKGROUND -}, localize('statusBarWarningItemHoverBackground', "Status bar warning items background color when hovering. Warning items stand out from other status bar entries to indicate warning conditions. The status bar is shown in the bottom of the window.")); +export const STATUS_BAR_WARNING_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.warningHoverBackground', STATUS_BAR_ITEM_HOVER_BACKGROUND, localize('statusBarWarningItemHoverBackground', "Status bar warning items background color when hovering. Warning items stand out from other status bar entries to indicate warning conditions. The status bar is shown in the bottom of the window.")); // < --- Activity Bar --- > @@ -710,12 +495,7 @@ export const ACTIVITY_BAR_ACTIVE_FOCUS_BORDER = registerColor('activityBar.activ hcLight: '#B5200D' }, localize('activityBarActiveFocusBorder', "Activity bar focus border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); -export const ACTIVITY_BAR_ACTIVE_BACKGROUND = registerColor('activityBar.activeBackground', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('activityBarActiveBackground', "Activity bar background color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +export const ACTIVITY_BAR_ACTIVE_BACKGROUND = registerColor('activityBar.activeBackground', null, localize('activityBarActiveBackground', "Activity bar background color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_DRAG_AND_DROP_BORDER = registerColor('activityBar.dropBorder', { dark: ACTIVITY_BAR_FOREGROUND, @@ -731,12 +511,7 @@ export const ACTIVITY_BAR_BADGE_BACKGROUND = registerColor('activityBarBadge.bac hcLight: '#0F4A85' }, localize('activityBarBadgeBackground', "Activity notification badge background color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); -export const ACTIVITY_BAR_BADGE_FOREGROUND = registerColor('activityBarBadge.foreground', { - dark: Color.white, - light: Color.white, - hcDark: Color.white, - hcLight: Color.white -}, localize('activityBarBadgeForeground', "Activity notification badge foreground color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +export const ACTIVITY_BAR_BADGE_FOREGROUND = registerColor('activityBarBadge.foreground', Color.white, localize('activityBarBadgeForeground', "Activity notification badge foreground color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_TOP_FOREGROUND = registerColor('activityBarTop.foreground', { dark: '#E7E7E7', @@ -752,12 +527,7 @@ export const ACTIVITY_BAR_TOP_ACTIVE_BORDER = registerColor('activityBarTop.acti hcLight: '#B5200D' }, localize('activityBarTopActiveFocusBorder', "Focus border color for the active item in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); -export const ACTIVITY_BAR_TOP_ACTIVE_BACKGROUND = registerColor('activityBarTop.activeBackground', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('activityBarTopActiveBackground', "Background color for the active item in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); +export const ACTIVITY_BAR_TOP_ACTIVE_BACKGROUND = registerColor('activityBarTop.activeBackground', null, localize('activityBarTopActiveBackground', "Background color for the active item in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); export const ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND = registerColor('activityBarTop.inactiveForeground', { dark: transparent(ACTIVITY_BAR_TOP_FOREGROUND, 0.6), @@ -766,19 +536,9 @@ export const ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND = registerColor('activityBarTo hcLight: editorForeground }, localize('activityBarTopInActiveForeground', "Inactive foreground color of the item in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); -export const ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER = registerColor('activityBarTop.dropBorder', { - dark: ACTIVITY_BAR_TOP_FOREGROUND, - light: ACTIVITY_BAR_TOP_FOREGROUND, - hcDark: ACTIVITY_BAR_TOP_FOREGROUND, - hcLight: ACTIVITY_BAR_TOP_FOREGROUND -}, localize('activityBarTopDragAndDropBorder', "Drag and drop feedback color for the items in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); +export const ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER = registerColor('activityBarTop.dropBorder', ACTIVITY_BAR_TOP_FOREGROUND, localize('activityBarTopDragAndDropBorder', "Drag and drop feedback color for the items in the Activity bar when it is on top / bottom. The activity allows to switch between views of the side bar.")); -export const ACTIVITY_BAR_TOP_BACKGROUND = registerColor('activityBarTop.background', { - dark: null, - light: null, - hcDark: null, - hcLight: null, -}, localize('activityBarTopBackground', "Background color of the activity bar when set to top / bottom.")); +export const ACTIVITY_BAR_TOP_BACKGROUND = registerColor('activityBarTop.background', null, localize('activityBarTopBackground', "Background color of the activity bar when set to top / bottom.")); // < --- Profiles --- > @@ -799,26 +559,11 @@ export const PROFILE_BADGE_FOREGROUND = registerColor('profileBadge.foreground', // < --- Remote --- > -export const STATUS_BAR_REMOTE_ITEM_BACKGROUND = registerColor('statusBarItem.remoteBackground', { - dark: ACTIVITY_BAR_BADGE_BACKGROUND, - light: ACTIVITY_BAR_BADGE_BACKGROUND, - hcDark: ACTIVITY_BAR_BADGE_BACKGROUND, - hcLight: ACTIVITY_BAR_BADGE_BACKGROUND -}, localize('statusBarItemHostBackground', "Background color for the remote indicator on the status bar.")); - -export const STATUS_BAR_REMOTE_ITEM_FOREGROUND = registerColor('statusBarItem.remoteForeground', { - dark: ACTIVITY_BAR_BADGE_FOREGROUND, - light: ACTIVITY_BAR_BADGE_FOREGROUND, - hcDark: ACTIVITY_BAR_BADGE_FOREGROUND, - hcLight: ACTIVITY_BAR_BADGE_FOREGROUND -}, localize('statusBarItemHostForeground', "Foreground color for the remote indicator on the status bar.")); - -export const STATUS_BAR_REMOTE_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.remoteHoverForeground', { - dark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - light: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcDark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcLight: STATUS_BAR_ITEM_HOVER_FOREGROUND -}, localize('statusBarRemoteItemHoverForeground', "Foreground color for the remote indicator on the status bar when hovering.")); +export const STATUS_BAR_REMOTE_ITEM_BACKGROUND = registerColor('statusBarItem.remoteBackground', ACTIVITY_BAR_BADGE_BACKGROUND, localize('statusBarItemHostBackground', "Background color for the remote indicator on the status bar.")); + +export const STATUS_BAR_REMOTE_ITEM_FOREGROUND = registerColor('statusBarItem.remoteForeground', ACTIVITY_BAR_BADGE_FOREGROUND, localize('statusBarItemHostForeground', "Foreground color for the remote indicator on the status bar.")); + +export const STATUS_BAR_REMOTE_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.remoteHoverForeground', STATUS_BAR_ITEM_HOVER_FOREGROUND, localize('statusBarRemoteItemHoverForeground', "Foreground color for the remote indicator on the status bar when hovering.")); export const STATUS_BAR_REMOTE_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.remoteHoverBackground', { dark: STATUS_BAR_ITEM_HOVER_BACKGROUND, @@ -827,26 +572,11 @@ export const STATUS_BAR_REMOTE_ITEM_HOVER_BACKGROUND = registerColor('statusBarI hcLight: null }, localize('statusBarRemoteItemHoverBackground', "Background color for the remote indicator on the status bar when hovering.")); -export const STATUS_BAR_OFFLINE_ITEM_BACKGROUND = registerColor('statusBarItem.offlineBackground', { - dark: '#6c1717', - light: '#6c1717', - hcDark: '#6c1717', - hcLight: '#6c1717' -}, localize('statusBarItemOfflineBackground', "Status bar item background color when the workbench is offline.")); - -export const STATUS_BAR_OFFLINE_ITEM_FOREGROUND = registerColor('statusBarItem.offlineForeground', { - dark: STATUS_BAR_REMOTE_ITEM_FOREGROUND, - light: STATUS_BAR_REMOTE_ITEM_FOREGROUND, - hcDark: STATUS_BAR_REMOTE_ITEM_FOREGROUND, - hcLight: STATUS_BAR_REMOTE_ITEM_FOREGROUND -}, localize('statusBarItemOfflineForeground', "Status bar item foreground color when the workbench is offline.")); - -export const STATUS_BAR_OFFLINE_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.offlineHoverForeground', { - dark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - light: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcDark: STATUS_BAR_ITEM_HOVER_FOREGROUND, - hcLight: STATUS_BAR_ITEM_HOVER_FOREGROUND -}, localize('statusBarOfflineItemHoverForeground', "Status bar item foreground hover color when the workbench is offline.")); +export const STATUS_BAR_OFFLINE_ITEM_BACKGROUND = registerColor('statusBarItem.offlineBackground', '#6c1717', localize('statusBarItemOfflineBackground', "Status bar item background color when the workbench is offline.")); + +export const STATUS_BAR_OFFLINE_ITEM_FOREGROUND = registerColor('statusBarItem.offlineForeground', STATUS_BAR_REMOTE_ITEM_FOREGROUND, localize('statusBarItemOfflineForeground', "Status bar item foreground color when the workbench is offline.")); + +export const STATUS_BAR_OFFLINE_ITEM_HOVER_FOREGROUND = registerColor('statusBarItem.offlineHoverForeground', STATUS_BAR_ITEM_HOVER_FOREGROUND, localize('statusBarOfflineItemHoverForeground', "Status bar item foreground hover color when the workbench is offline.")); export const STATUS_BAR_OFFLINE_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.offlineHoverBackground', { dark: STATUS_BAR_ITEM_HOVER_BACKGROUND, @@ -855,19 +585,9 @@ export const STATUS_BAR_OFFLINE_ITEM_HOVER_BACKGROUND = registerColor('statusBar hcLight: null }, localize('statusBarOfflineItemHoverBackground', "Status bar item background hover color when the workbench is offline.")); -export const EXTENSION_BADGE_REMOTE_BACKGROUND = registerColor('extensionBadge.remoteBackground', { - dark: ACTIVITY_BAR_BADGE_BACKGROUND, - light: ACTIVITY_BAR_BADGE_BACKGROUND, - hcDark: ACTIVITY_BAR_BADGE_BACKGROUND, - hcLight: ACTIVITY_BAR_BADGE_BACKGROUND -}, localize('extensionBadge.remoteBackground', "Background color for the remote badge in the extensions view.")); +export const EXTENSION_BADGE_REMOTE_BACKGROUND = registerColor('extensionBadge.remoteBackground', ACTIVITY_BAR_BADGE_BACKGROUND, localize('extensionBadge.remoteBackground', "Background color for the remote badge in the extensions view.")); -export const EXTENSION_BADGE_REMOTE_FOREGROUND = registerColor('extensionBadge.remoteForeground', { - dark: ACTIVITY_BAR_BADGE_FOREGROUND, - light: ACTIVITY_BAR_BADGE_FOREGROUND, - hcDark: ACTIVITY_BAR_BADGE_FOREGROUND, - hcLight: ACTIVITY_BAR_BADGE_FOREGROUND -}, localize('extensionBadge.remoteForeground', "Foreground color for the remote badge in the extensions view.")); +export const EXTENSION_BADGE_REMOTE_FOREGROUND = registerColor('extensionBadge.remoteForeground', ACTIVITY_BAR_BADGE_FOREGROUND, localize('extensionBadge.remoteForeground', "Foreground color for the remote badge in the extensions view.")); // < --- Side Bar --- > @@ -879,12 +599,7 @@ export const SIDE_BAR_BACKGROUND = registerColor('sideBar.background', { hcLight: '#FFFFFF' }, localize('sideBarBackground', "Side bar background color. The side bar is the container for views like explorer and search.")); -export const SIDE_BAR_FOREGROUND = registerColor('sideBar.foreground', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('sideBarForeground', "Side bar foreground color. The side bar is the container for views like explorer and search.")); +export const SIDE_BAR_FOREGROUND = registerColor('sideBar.foreground', null, localize('sideBarForeground', "Side bar foreground color. The side bar is the container for views like explorer and search.")); export const SIDE_BAR_BORDER = registerColor('sideBar.border', { dark: null, @@ -893,26 +608,11 @@ export const SIDE_BAR_BORDER = registerColor('sideBar.border', { hcLight: contrastBorder }, localize('sideBarBorder', "Side bar border color on the side separating to the editor. The side bar is the container for views like explorer and search.")); -export const SIDE_BAR_TITLE_BACKGROUND = registerColor('sideBarTitle.background', { - dark: SIDE_BAR_BACKGROUND, - light: SIDE_BAR_BACKGROUND, - hcDark: SIDE_BAR_BACKGROUND, - hcLight: SIDE_BAR_BACKGROUND -}, localize('sideBarTitleBackground', "Side bar title background color. The side bar is the container for views like explorer and search.")); - -export const SIDE_BAR_TITLE_FOREGROUND = registerColor('sideBarTitle.foreground', { - dark: SIDE_BAR_FOREGROUND, - light: SIDE_BAR_FOREGROUND, - hcDark: SIDE_BAR_FOREGROUND, - hcLight: SIDE_BAR_FOREGROUND -}, localize('sideBarTitleForeground', "Side bar title foreground color. The side bar is the container for views like explorer and search.")); - -export const SIDE_BAR_DRAG_AND_DROP_BACKGROUND = registerColor('sideBar.dropBackground', { - dark: EDITOR_DRAG_AND_DROP_BACKGROUND, - light: EDITOR_DRAG_AND_DROP_BACKGROUND, - hcDark: EDITOR_DRAG_AND_DROP_BACKGROUND, - hcLight: EDITOR_DRAG_AND_DROP_BACKGROUND -}, localize('sideBarDragAndDropBackground', "Drag and drop feedback color for the side bar sections. The color should have transparency so that the side bar sections can still shine through. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); +export const SIDE_BAR_TITLE_BACKGROUND = registerColor('sideBarTitle.background', SIDE_BAR_BACKGROUND, localize('sideBarTitleBackground', "Side bar title background color. The side bar is the container for views like explorer and search.")); + +export const SIDE_BAR_TITLE_FOREGROUND = registerColor('sideBarTitle.foreground', SIDE_BAR_FOREGROUND, localize('sideBarTitleForeground', "Side bar title foreground color. The side bar is the container for views like explorer and search.")); + +export const SIDE_BAR_DRAG_AND_DROP_BACKGROUND = registerColor('sideBar.dropBackground', EDITOR_DRAG_AND_DROP_BACKGROUND, localize('sideBarDragAndDropBackground', "Drag and drop feedback color for the side bar sections. The color should have transparency so that the side bar sections can still shine through. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); export const SIDE_BAR_SECTION_HEADER_BACKGROUND = registerColor('sideBarSectionHeader.background', { dark: Color.fromHex('#808080').transparent(0.2), @@ -921,47 +621,17 @@ export const SIDE_BAR_SECTION_HEADER_BACKGROUND = registerColor('sideBarSectionH hcLight: null }, localize('sideBarSectionHeaderBackground', "Side bar section header background color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); -export const SIDE_BAR_SECTION_HEADER_FOREGROUND = registerColor('sideBarSectionHeader.foreground', { - dark: SIDE_BAR_FOREGROUND, - light: SIDE_BAR_FOREGROUND, - hcDark: SIDE_BAR_FOREGROUND, - hcLight: SIDE_BAR_FOREGROUND -}, localize('sideBarSectionHeaderForeground', "Side bar section header foreground color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); +export const SIDE_BAR_SECTION_HEADER_FOREGROUND = registerColor('sideBarSectionHeader.foreground', SIDE_BAR_FOREGROUND, localize('sideBarSectionHeaderForeground', "Side bar section header foreground color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); -export const SIDE_BAR_SECTION_HEADER_BORDER = registerColor('sideBarSectionHeader.border', { - dark: contrastBorder, - light: contrastBorder, - hcDark: contrastBorder, - hcLight: contrastBorder -}, localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); - -export const ACTIVITY_BAR_TOP_BORDER = registerColor('sideBarActivityBarTop.border', { - dark: SIDE_BAR_SECTION_HEADER_BORDER, - light: SIDE_BAR_SECTION_HEADER_BORDER, - hcDark: SIDE_BAR_SECTION_HEADER_BORDER, - hcLight: SIDE_BAR_SECTION_HEADER_BORDER -}, localize('sideBarActivityBarTopBorder', "Border color between the activity bar at the top/bottom and the views.")); - -export const SIDE_BAR_STICKY_SCROLL_BACKGROUND = registerColor('sideBarStickyScroll.background', { - dark: SIDE_BAR_BACKGROUND, - light: SIDE_BAR_BACKGROUND, - hcDark: SIDE_BAR_BACKGROUND, - hcLight: SIDE_BAR_BACKGROUND -}, localize('sideBarStickyScrollBackground', "Background color of sticky scroll in the side bar.")); - -export const SIDE_BAR_STICKY_SCROLL_BORDER = registerColor('sideBarStickyScroll.border', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('sideBarStickyScrollBorder', "Border color of sticky scroll in the side bar.")); +export const SIDE_BAR_SECTION_HEADER_BORDER = registerColor('sideBarSectionHeader.border', contrastBorder, localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); + +export const ACTIVITY_BAR_TOP_BORDER = registerColor('sideBarActivityBarTop.border', SIDE_BAR_SECTION_HEADER_BORDER, localize('sideBarActivityBarTopBorder', "Border color between the activity bar at the top/bottom and the views.")); + +export const SIDE_BAR_STICKY_SCROLL_BACKGROUND = registerColor('sideBarStickyScroll.background', SIDE_BAR_BACKGROUND, localize('sideBarStickyScrollBackground', "Background color of sticky scroll in the side bar.")); -export const SIDE_BAR_STICKY_SCROLL_SHADOW = registerColor('sideBarStickyScroll.shadow', { - dark: scrollbarShadow, - light: scrollbarShadow, - hcDark: scrollbarShadow, - hcLight: scrollbarShadow -}, localize('sideBarStickyScrollShadow', "Shadow color of sticky scroll in the side bar.")); +export const SIDE_BAR_STICKY_SCROLL_BORDER = registerColor('sideBarStickyScroll.border', null, localize('sideBarStickyScrollBorder', "Border color of sticky scroll in the side bar.")); + +export const SIDE_BAR_STICKY_SCROLL_SHADOW = registerColor('sideBarStickyScroll.shadow', scrollbarShadow, localize('sideBarStickyScrollShadow', "Shadow color of sticky scroll in the side bar.")); // < --- Title Bar --- > @@ -1002,12 +672,7 @@ export const TITLE_BAR_BORDER = registerColor('titleBar.border', { // < --- Menubar --- > -export const MENUBAR_SELECTION_FOREGROUND = registerColor('menubar.selectionForeground', { - dark: TITLE_BAR_ACTIVE_FOREGROUND, - light: TITLE_BAR_ACTIVE_FOREGROUND, - hcDark: TITLE_BAR_ACTIVE_FOREGROUND, - hcLight: TITLE_BAR_ACTIVE_FOREGROUND, -}, localize('menubarSelectionForeground', "Foreground color of the selected menu item in the menubar.")); +export const MENUBAR_SELECTION_FOREGROUND = registerColor('menubar.selectionForeground', TITLE_BAR_ACTIVE_FOREGROUND, localize('menubarSelectionForeground', "Foreground color of the selected menu item in the menubar.")); export const MENUBAR_SELECTION_BACKGROUND = registerColor('menubar.selectionBackground', { dark: toolbarHoverBackground, @@ -1028,19 +693,19 @@ export const MENUBAR_SELECTION_BORDER = registerColor('menubar.selectionBorder', // foreground (inactive and active) export const COMMAND_CENTER_FOREGROUND = registerColor( 'commandCenter.foreground', - { dark: TITLE_BAR_ACTIVE_FOREGROUND, hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: TITLE_BAR_ACTIVE_FOREGROUND, hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, + TITLE_BAR_ACTIVE_FOREGROUND, localize('commandCenter-foreground', "Foreground color of the command center"), false ); export const COMMAND_CENTER_ACTIVEFOREGROUND = registerColor( 'commandCenter.activeForeground', - { dark: MENUBAR_SELECTION_FOREGROUND, hcDark: MENUBAR_SELECTION_FOREGROUND, light: MENUBAR_SELECTION_FOREGROUND, hcLight: MENUBAR_SELECTION_FOREGROUND }, + MENUBAR_SELECTION_FOREGROUND, localize('commandCenter-activeForeground', "Active foreground color of the command center"), false ); export const COMMAND_CENTER_INACTIVEFOREGROUND = registerColor( 'commandCenter.inactiveForeground', - { dark: TITLE_BAR_INACTIVE_FOREGROUND, hcDark: TITLE_BAR_INACTIVE_FOREGROUND, light: TITLE_BAR_INACTIVE_FOREGROUND, hcLight: TITLE_BAR_INACTIVE_FOREGROUND }, + TITLE_BAR_INACTIVE_FOREGROUND, localize('commandCenter-inactiveForeground', "Foreground color of the command center when the window is inactive"), false ); @@ -1070,7 +735,7 @@ export const COMMAND_CENTER_ACTIVEBORDER = registerColor( ); // border: defaults to active background export const COMMAND_CENTER_INACTIVEBORDER = registerColor( - 'commandCenter.inactiveBorder', { dark: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcDark: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), light: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcLight: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25) }, + 'commandCenter.inactiveBorder', transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), localize('commandCenter-inactiveBorder', "Border color of the command center when the window is inactive"), false ); @@ -1092,33 +757,13 @@ export const NOTIFICATIONS_TOAST_BORDER = registerColor('notificationToast.borde hcLight: contrastBorder }, localize('notificationToastBorder', "Notification toast border color. Notifications slide in from the bottom right of the window.")); -export const NOTIFICATIONS_FOREGROUND = registerColor('notifications.foreground', { - dark: editorWidgetForeground, - light: editorWidgetForeground, - hcDark: editorWidgetForeground, - hcLight: editorWidgetForeground -}, localize('notificationsForeground', "Notifications foreground color. Notifications slide in from the bottom right of the window.")); - -export const NOTIFICATIONS_BACKGROUND = registerColor('notifications.background', { - dark: editorWidgetBackground, - light: editorWidgetBackground, - hcDark: editorWidgetBackground, - hcLight: editorWidgetBackground -}, localize('notificationsBackground', "Notifications background color. Notifications slide in from the bottom right of the window.")); - -export const NOTIFICATIONS_LINKS = registerColor('notificationLink.foreground', { - dark: textLinkForeground, - light: textLinkForeground, - hcDark: textLinkForeground, - hcLight: textLinkForeground -}, localize('notificationsLink', "Notification links foreground color. Notifications slide in from the bottom right of the window.")); - -export const NOTIFICATIONS_CENTER_HEADER_FOREGROUND = registerColor('notificationCenterHeader.foreground', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('notificationCenterHeaderForeground', "Notifications center header foreground color. Notifications slide in from the bottom right of the window.")); +export const NOTIFICATIONS_FOREGROUND = registerColor('notifications.foreground', editorWidgetForeground, localize('notificationsForeground', "Notifications foreground color. Notifications slide in from the bottom right of the window.")); + +export const NOTIFICATIONS_BACKGROUND = registerColor('notifications.background', editorWidgetBackground, localize('notificationsBackground', "Notifications background color. Notifications slide in from the bottom right of the window.")); + +export const NOTIFICATIONS_LINKS = registerColor('notificationLink.foreground', textLinkForeground, localize('notificationsLink', "Notification links foreground color. Notifications slide in from the bottom right of the window.")); + +export const NOTIFICATIONS_CENTER_HEADER_FOREGROUND = registerColor('notificationCenterHeader.foreground', null, localize('notificationCenterHeaderForeground', "Notifications center header foreground color. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_CENTER_HEADER_BACKGROUND = registerColor('notificationCenterHeader.background', { dark: lighten(NOTIFICATIONS_BACKGROUND, 0.3), @@ -1127,33 +772,13 @@ export const NOTIFICATIONS_CENTER_HEADER_BACKGROUND = registerColor('notificatio hcLight: NOTIFICATIONS_BACKGROUND }, localize('notificationCenterHeaderBackground', "Notifications center header background color. Notifications slide in from the bottom right of the window.")); -export const NOTIFICATIONS_BORDER = registerColor('notifications.border', { - dark: NOTIFICATIONS_CENTER_HEADER_BACKGROUND, - light: NOTIFICATIONS_CENTER_HEADER_BACKGROUND, - hcDark: NOTIFICATIONS_CENTER_HEADER_BACKGROUND, - hcLight: NOTIFICATIONS_CENTER_HEADER_BACKGROUND -}, localize('notificationsBorder', "Notifications border color separating from other notifications in the notifications center. Notifications slide in from the bottom right of the window.")); - -export const NOTIFICATIONS_ERROR_ICON_FOREGROUND = registerColor('notificationsErrorIcon.foreground', { - dark: editorErrorForeground, - light: editorErrorForeground, - hcDark: editorErrorForeground, - hcLight: editorErrorForeground -}, localize('notificationsErrorIconForeground', "The color used for the icon of error notifications. Notifications slide in from the bottom right of the window.")); - -export const NOTIFICATIONS_WARNING_ICON_FOREGROUND = registerColor('notificationsWarningIcon.foreground', { - dark: editorWarningForeground, - light: editorWarningForeground, - hcDark: editorWarningForeground, - hcLight: editorWarningForeground -}, localize('notificationsWarningIconForeground', "The color used for the icon of warning notifications. Notifications slide in from the bottom right of the window.")); - -export const NOTIFICATIONS_INFO_ICON_FOREGROUND = registerColor('notificationsInfoIcon.foreground', { - dark: editorInfoForeground, - light: editorInfoForeground, - hcDark: editorInfoForeground, - hcLight: editorInfoForeground -}, localize('notificationsInfoIconForeground', "The color used for the icon of info notifications. Notifications slide in from the bottom right of the window.")); +export const NOTIFICATIONS_BORDER = registerColor('notifications.border', NOTIFICATIONS_CENTER_HEADER_BACKGROUND, localize('notificationsBorder', "Notifications border color separating from other notifications in the notifications center. Notifications slide in from the bottom right of the window.")); + +export const NOTIFICATIONS_ERROR_ICON_FOREGROUND = registerColor('notificationsErrorIcon.foreground', editorErrorForeground, localize('notificationsErrorIconForeground', "The color used for the icon of error notifications. Notifications slide in from the bottom right of the window.")); + +export const NOTIFICATIONS_WARNING_ICON_FOREGROUND = registerColor('notificationsWarningIcon.foreground', editorWarningForeground, localize('notificationsWarningIconForeground', "The color used for the icon of warning notifications. Notifications slide in from the bottom right of the window.")); + +export const NOTIFICATIONS_INFO_ICON_FOREGROUND = registerColor('notificationsInfoIcon.foreground', editorInfoForeground, localize('notificationsInfoIconForeground', "The color used for the icon of info notifications. Notifications slide in from the bottom right of the window.")); export const WINDOW_ACTIVE_BORDER = registerColor('window.activeBorder', { dark: null, diff --git a/patched-vscode/src/vs/workbench/common/views.ts b/patched-vscode/src/vs/workbench/common/views.ts index 7d0bb72c..91a6686f 100644 --- a/patched-vscode/src/vs/workbench/common/views.ts +++ b/patched-vscode/src/vs/workbench/common/views.ts @@ -832,8 +832,8 @@ export class NoTreeViewError extends Error { constructor(treeViewId: string) { super(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)); } - static is(err: Error): err is NoTreeViewError { - return err.name === 'NoTreeViewError'; + static is(err: unknown): err is NoTreeViewError { + return !!err && (err as Error).name === 'NoTreeViewError'; } } diff --git a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 1d163de1..25f14671 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -25,6 +25,8 @@ export const accessibleViewOnLastLine = new RawContextKey('accessibleVi export const accessibleViewCurrentProviderId = new RawContextKey('accessibleViewCurrentProviderId', undefined, undefined); export const accessibleViewInCodeBlock = new RawContextKey('accessibleViewInCodeBlock', undefined, undefined); export const accessibleViewContainsCodeBlocks = new RawContextKey('accessibleViewContainsCodeBlocks', undefined, undefined); +export const accessibleViewHasUnassignedKeybindings = new RawContextKey('accessibleViewHasUnassignedKeybindings', undefined, undefined); +export const accessibleViewHasAssignedKeybindings = new RawContextKey('accessibleViewHasAssignedKeybindings', undefined, undefined); /** * Miscellaneous settings tagged with accessibility and implemented in the accessibility contrib but @@ -56,8 +58,10 @@ export const enum AccessibilityVerbositySettingId { Hover = 'accessibility.verbosity.hover', Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', + ReplInputHint = 'accessibility.verbosity.replInputHint', Comments = 'accessibility.verbosity.comments', - DiffEditorActive = 'accessibility.verbosity.diffEditorActive' + DiffEditorActive = 'accessibility.verbosity.diffEditorActive', + Debug = 'accessibility.verbosity.debug', } const baseVerbosityProperty: IConfigurationPropertySchema = { @@ -158,6 +162,10 @@ const configuration: IConfigurationNode = { description: localize('verbosity.emptyEditorHint', 'Provide information about relevant actions in an empty text editor.'), ...baseVerbosityProperty }, + [AccessibilityVerbositySettingId.ReplInputHint]: { + description: localize('verbosity.replInputHint', 'Provide information about relevant actions For the Repl input.'), + ...baseVerbosityProperty + }, [AccessibilityVerbositySettingId.Comments]: { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), ...baseVerbosityProperty @@ -166,123 +174,86 @@ const configuration: IConfigurationNode = { description: localize('verbosity.diffEditorActive', 'Indicate when a diff editor becomes the active editor.'), ...baseVerbosityProperty }, + [AccessibilityVerbositySettingId.Debug]: { + description: localize('verbosity.debug', 'Provide information about how to access the debug console accessibility help dialog when the debug console or run and debug viewlet is focused. Note that a reload of the window is required for this to take effect.'), + ...baseVerbosityProperty + }, [AccessibilityWorkbenchSettingId.AccessibleViewCloseOnKeyPress]: { markdownDescription: localize('terminal.integrated.accessibleView.closeOnKeyPress', "On keypress, close the Accessible View and focus the element from which it was invoked."), type: 'boolean', default: true }, - 'accessibility.signalOptions': { - description: 'Configures the behavior of signals (audio cues and announcements) in the workbench. Includes volume, debounce position changes, and delays for different types of signals.', - type: 'object', - additionalProperties: false, - properties: { - 'volume': { - 'description': localize('accessibility.signalOptions.volume', "The volume of the sounds in percent (0-100)."), + 'accessibility.signalOptions.volume': { + 'description': localize('accessibility.signalOptions.volume', "The volume of the sounds in percent (0-100)."), + 'type': 'number', + 'minimum': 0, + 'maximum': 100, + 'default': 70, + 'tags': ['accessibility'] + }, + 'accessibility.signalOptions.debouncePositionChanges': { + 'description': localize('accessibility.signalOptions.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'type': 'boolean', + 'default': false, + 'tags': ['accessibility'] + }, + 'accessibility.signalOptions.experimental.delays.general': { + 'type': 'object', + 'description': 'Delays for all signals besides error and warning at position', + 'additionalProperties': false, + 'properties': { + 'announcement': { + 'description': localize('accessibility.signalOptions.delays.general.announcement', "The delay in milliseconds before an announcement is made."), 'type': 'number', 'minimum': 0, - 'maximum': 100, - 'default': 70, - }, - 'debouncePositionChanges': { - 'description': localize('accessibility.signalOptions.debouncePositionChanges', "Whether or not position changes should be debounced"), - 'type': 'boolean', - 'default': false, - }, - 'experimental.delays': { - 'type': 'object', - 'additionalProperties': false, - 'properties': { - 'general': { - 'type': 'object', - 'description': 'Delays for all signals besides error and warning at position', - 'additionalProperties': false, - 'properties': { - 'announcement': { - 'description': localize('accessibility.signalOptions.delays.general.announcement', "The delay in milliseconds before an announcement is made."), - 'type': 'number', - 'minimum': 0, - 'default': 3000 - }, - 'sound': { - 'description': localize('accessibility.signalOptions.delays.general.sound', "The delay in milliseconds before a sound is played."), - 'type': 'number', - 'minimum': 0, - 'default': 400 - } - }, - }, - 'warningAtPosition': { - 'type': 'object', - 'additionalProperties': false, - 'properties': { - 'announcement': { - 'description': localize('accessibility.signalOptions.delays.warningAtPosition.announcement', "The delay in milliseconds before an announcement is made when there's a warning at the position."), - 'type': 'number', - 'minimum': 0, - 'default': 3000 - }, - 'sound': { - 'description': localize('accessibility.signalOptions.delays.warningAtPosition.sound', "The delay in milliseconds before a sound is played when there's a warning at the position."), - 'type': 'number', - 'minimum': 0, - 'default': 1000 - } - }, - }, - 'errorAtPosition': { - 'type': 'object', - 'additionalProperties': false, - 'properties': { - 'announcement': { - 'description': localize('accessibility.signalOptions.delays.errorAtPosition.announcement', "The delay in milliseconds before an announcement is made when there's an error at the position."), - 'type': 'number', - 'minimum': 0, - 'default': 3000 - }, - 'sound': { - 'description': localize('accessibility.signalOptions.delays.errorAtPosition.sound', "The delay in milliseconds before a sound is played when there's an error at the position."), - 'type': 'number', - 'minimum': 0, - 'default': 1000 - } - }, - }, - }, - 'default': { - 'general': { - 'announcement': 3000, - 'sound': 400 - }, - 'warningAtPosition': { - 'announcement': 3000, - 'sound': 1000 - }, - 'errorAtPosition': { - 'announcement': 3000, - 'sound': 1000 - } - } + 'default': 3000 }, + 'sound': { + 'description': localize('accessibility.signalOptions.delays.general.sound', "The delay in milliseconds before a sound is played."), + 'type': 'number', + 'minimum': 0, + 'default': 400 + } }, - 'default': { - 'volume': 70, - 'debouncePositionChanges': false, - 'experimental.delays': { - 'general': { - 'announcement': 3000, - 'sound': 400 - }, - 'warningAtPosition': { - 'announcement': 3000, - 'sound': 1000 - }, - 'errorAtPosition': { - 'announcement': 3000, - 'sound': 1000 - } + 'tags': ['accessibility'] + }, + 'accessibility.signalOptions.experimental.delays.warningAtPosition': { + 'type': 'object', + 'additionalProperties': false, + 'properties': { + 'announcement': { + 'description': localize('accessibility.signalOptions.delays.warningAtPosition.announcement', "The delay in milliseconds before an announcement is made when there's a warning at the position."), + 'type': 'number', + 'minimum': 0, + 'default': 3000 + }, + 'sound': { + 'description': localize('accessibility.signalOptions.delays.warningAtPosition.sound', "The delay in milliseconds before a sound is played when there's a warning at the position."), + 'type': 'number', + 'minimum': 0, + 'default': 1000 + } + }, + 'tags': ['accessibility'] + }, + 'accessibility.signalOptions.experimental.delays.errorAtPosition': { + 'type': 'object', + 'additionalProperties': false, + 'properties': { + 'announcement': { + 'description': localize('accessibility.signalOptions.delays.errorAtPosition.announcement', "The delay in milliseconds before an announcement is made when there's an error at the position."), + 'type': 'number', + 'minimum': 0, + 'default': 3000 + }, + 'sound': { + 'description': localize('accessibility.signalOptions.delays.errorAtPosition.sound', "The delay in milliseconds before a sound is played when there's an error at the position."), + 'type': 'number', + 'minimum': 0, + 'default': 1000 } }, - tags: ['accessibility'] + 'tags': ['accessibility'] }, 'accessibility.signals.lineHasBreakpoint': { ...signalFeatureBase, @@ -698,6 +669,16 @@ const configuration: IConfigurationNode = { 'announcement': 'never' } }, + 'accessibility.underlineLinks': { + 'type': 'boolean', + 'description': localize('accessibility.underlineLinks', "Controls whether links should be underlined in the workbench."), + 'default': false, + }, + 'accessibility.debugWatchVariableAnnouncements': { + 'type': 'boolean', + 'description': localize('accessibility.debugWatchVariableAnnouncements', "Controls whether variable changes should be announced in the debug watch view."), + 'default': true, + }, } }; @@ -782,9 +763,15 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen 'enumItemLabels': languagesSorted.map(key => languages[key].name) }, [AccessibilityVoiceSettingId.AutoSynthesize]: { - 'type': 'boolean', + 'type': 'string', + 'enum': ['on', 'off', 'auto'], + 'enumDescriptions': [ + localize('accessibility.voice.autoSynthesize.on', "Enable the feature. When a screen reader is enabled, note that this will disable aria updates."), + localize('accessibility.voice.autoSynthesize.off', "Disable the feature."), + localize('accessibility.voice.autoSynthesize.auto', "When a screen reader is detected, disable the feature. Otherwise, enable the feature.") + ], 'markdownDescription': localize('autoSynthesize', "Whether a textual response should automatically be read out aloud when speech was used as input. For example in a chat session, a response is automatically synthesized when voice was used as chat request."), - 'default': this.productService.quality !== 'stable', // TODO@bpasero decide on a default + 'default': this.productService.quality !== 'stable' ? 'auto' : 'off', // TODO@bpasero decide on a default 'tags': ['accessibility'] } } @@ -804,10 +791,9 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'audioCues.volume', - migrateFn: (volume, accessor) => { - const debouncePositionChanges = getDebouncePositionChangesFromConfig(accessor); + migrateFn: (value, accessor) => { return [ - ['accessibility.signalOptions', { value: debouncePositionChanges !== undefined ? { volume, debouncePositionChanges } : { volume } }], + ['accessibility.signalOptions.volume', { value }], ['audioCues.volume', { value: undefined }] ]; } @@ -816,10 +802,9 @@ Registry.as(WorkbenchExtensions.ConfigurationMi Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'audioCues.debouncePositionChanges', - migrateFn: (debouncePositionChanges, accessor) => { - const volume = getVolumeFromConfig(accessor); + migrateFn: (value) => { return [ - ['accessibility.signalOptions', { value: volume !== undefined ? { volume, debouncePositionChanges } : { debouncePositionChanges } }], + ['accessibility.signalOptions.debouncePositionChanges', { value }], ['audioCues.debouncePositionChanges', { value: undefined }] ]; } @@ -829,13 +814,29 @@ Registry.as(WorkbenchExtensions.ConfigurationMi .registerConfigurationMigrations([{ key: 'accessibility.signalOptions', migrateFn: (value, accessor) => { - const delays = value.delays; - if (!delays) { - return []; + const delayGeneral = getDelaysFromConfig(accessor, 'general'); + const delayError = getDelaysFromConfig(accessor, 'errorAtPosition'); + const delayWarning = getDelaysFromConfig(accessor, 'warningAtPosition'); + const volume = getVolumeFromConfig(accessor); + const debouncePositionChanges = getDebouncePositionChangesFromConfig(accessor); + const result: [key: string, { value: any }][] = []; + if (!!volume) { + result.push(['accessibility.signalOptions.volume', { value: volume }]); } - return [ - ['accessibility.signalOptions', { value: { ...value, 'experimental.delays': delays, 'delays': undefined } }], - ]; + if (!!delayGeneral) { + result.push(['accessibility.signalOptions.experimental.delays.general', { value: delayGeneral }]); + } + if (!!delayError) { + result.push(['accessibility.signalOptions.experimental.delays.errorAtPosition', { value: delayError }]); + } + if (!!delayWarning) { + result.push(['accessibility.signalOptions.experimental.delays.warningAtPosition', { value: delayWarning }]); + } + if (!!debouncePositionChanges) { + result.push(['accessibility.signalOptions.debouncePositionChanges', { value: debouncePositionChanges }]); + } + result.push(['accessibility.signalOptions', { value: undefined }]); + return result; } }]); @@ -843,10 +844,9 @@ Registry.as(WorkbenchExtensions.ConfigurationMi Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'accessibility.signals.sounds.volume', - migrateFn: (volume, accessor) => { - const debouncePositionChanges = getDebouncePositionChangesFromConfig(accessor); + migrateFn: (value) => { return [ - ['accessibility.signalOptions', { value: debouncePositionChanges !== undefined ? { volume, debouncePositionChanges } : { volume } }], + ['accessibility.signalOptions.volume', { value }], ['accessibility.signals.sounds.volume', { value: undefined }] ]; } @@ -855,23 +855,44 @@ Registry.as(WorkbenchExtensions.ConfigurationMi Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'accessibility.signals.debouncePositionChanges', - migrateFn: (debouncePositionChanges, accessor) => { - const volume = getVolumeFromConfig(accessor); + migrateFn: (value) => { return [ - ['accessibility.signalOptions', { value: volume !== undefined ? { volume, debouncePositionChanges } : { debouncePositionChanges } }], + ['accessibility.signalOptions.debouncePositionChanges', { value }], ['accessibility.signals.debouncePositionChanges', { value: undefined }] ]; } }]); +function getDelaysFromConfig(accessor: (key: string) => any, type: 'general' | 'errorAtPosition' | 'warningAtPosition'): { announcement: number; sound: number } | undefined { + return accessor(`accessibility.signalOptions.experimental.delays.${type}`) || accessor('accessibility.signalOptions')?.['experimental.delays']?.[`${type}`] || accessor('accessibility.signalOptions')?.['delays']?.[`${type}`]; +} + function getVolumeFromConfig(accessor: (key: string) => any): string | undefined { - return accessor('accessibility.signalOptions')?.volume || accessor('accessibility.signals.sounds.volume') || accessor('audioCues.volume'); + return accessor('accessibility.signalOptions.volume') || accessor('accessibility.signalOptions')?.volume || accessor('accessibility.signals.sounds.volume') || accessor('audioCues.volume'); } function getDebouncePositionChangesFromConfig(accessor: (key: string) => any): number | undefined { - return accessor('accessibility.signalOptions')?.debouncePositionChanges || accessor('accessibility.signals.debouncePositionChanges') || accessor('audioCues.debouncePositionChanges'); + return accessor('accessibility.signalOptions.debouncePositionChanges') || accessor('accessibility.signalOptions')?.debouncePositionChanges || accessor('accessibility.signals.debouncePositionChanges') || accessor('audioCues.debouncePositionChanges'); } +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: AccessibilityVoiceSettingId.AutoSynthesize, + migrateFn: (value: boolean) => { + let newValue: string | undefined; + if (value === true) { + newValue = 'on'; + } else if (value === false) { + newValue = 'off'; + } else { + return []; + } + return [ + [AccessibilityVoiceSettingId.AutoSynthesize, { value: newValue }], + ]; + } + }]); + Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'accessibility.signals.chatResponsePending', diff --git a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index d4bd1f89..8af09f26 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -11,7 +11,7 @@ import { IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { marked } from 'vs/base/common/marked/marked'; +import * as marked from 'vs/base/common/marked/marked'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; @@ -24,7 +24,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; import { localize } from 'vs/nls'; -import { AccessibleViewProviderId, AccessibleViewType, AdvancedContentProvider, ExtensionContentProvider, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider, ExtensionContentProvider, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/platform/accessibility/browser/accessibleView'; import { ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -40,7 +40,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { AccessibilityVerbositySettingId, AccessibilityWorkbenchSettingId, accessibilityHelpIsShown, accessibleViewContainsCodeBlocks, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewInCodeBlock, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibilityWorkbenchSettingId, accessibilityHelpIsShown, accessibleViewContainsCodeBlocks, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewHasAssignedKeybindings, accessibleViewHasUnassignedKeybindings, accessibleViewInCodeBlock, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { resolveContentAndKeybindingItems } from 'vs/workbench/contrib/accessibility/browser/accessibleViewKeybindingResolver'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { IChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -51,7 +51,7 @@ const enum DIMENSIONS { MAX_WIDTH = 600 } -export type AccesibleViewContentProvider = AdvancedContentProvider | ExtensionContentProvider; +export type AccesibleViewContentProvider = AccessibleContentProvider | ExtensionContentProvider; interface ICodeBlock { startLine: number; @@ -72,6 +72,9 @@ export class AccessibleView extends Disposable { private _accessibleViewCurrentProviderId: IContextKey; private _accessibleViewInCodeBlock: IContextKey; private _accessibleViewContainsCodeBlocks: IContextKey; + private _hasUnassignedKeybindings: IContextKey; + private _hasAssignedKeybindings: IContextKey; + private _codeBlocks?: ICodeBlock[]; private _inQuickPick: boolean = false; @@ -85,6 +88,9 @@ export class AccessibleView extends Disposable { private _lastProvider: AccesibleViewContentProvider | undefined; + private _viewContainer: HTMLElement | undefined; + + constructor( @IOpenerService private readonly _openerService: IOpenerService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -112,6 +118,8 @@ export class AccessibleView extends Disposable { this._accessibleViewInCodeBlock = accessibleViewInCodeBlock.bindTo(this._contextKeyService); this._accessibleViewContainsCodeBlocks = accessibleViewContainsCodeBlocks.bindTo(this._contextKeyService); this._onLastLine = accessibleViewOnLastLine.bindTo(this._contextKeyService); + this._hasUnassignedKeybindings = accessibleViewHasUnassignedKeybindings.bindTo(this._contextKeyService); + this._hasAssignedKeybindings = accessibleViewHasAssignedKeybindings.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('accessible-view'); @@ -140,6 +148,7 @@ export class AccessibleView extends Disposable { lineDecorationsWidth: 6, dragAndDrop: false, cursorWidth: 1, + wordWrap: 'off', wrappingStrategy: 'advanced', wrappingIndent: 'none', padding: { top: 2, bottom: 2 }, @@ -156,7 +165,7 @@ export class AccessibleView extends Disposable { } })); this._register(this._configurationService.onDidChangeConfiguration(e => { - if (this._currentProvider instanceof AdvancedContentProvider && e.affectsConfiguration(this._currentProvider.verbositySettingKey)) { + if (this._currentProvider instanceof AccessibleContentProvider && e.affectsConfiguration(this._currentProvider.verbositySettingKey)) { if (this._accessiblityHelpIsShown.get()) { this.show(this._currentProvider); } @@ -187,6 +196,8 @@ export class AccessibleView extends Disposable { this._accessibleViewVerbosityEnabled.reset(); this._accessibleViewGoToSymbolSupported.reset(); this._accessibleViewCurrentProviderId.reset(); + this._hasAssignedKeybindings.reset(); + this._hasUnassignedKeybindings.reset(); } getPosition(id?: AccessibleViewProviderId): Position | undefined { @@ -196,11 +207,17 @@ export class AccessibleView extends Disposable { return this._editorWidget.getPosition() || undefined; } - setPosition(position: Position, reveal?: boolean): void { + setPosition(position: Position, reveal?: boolean, select?: boolean): void { this._editorWidget.setPosition(position); if (reveal) { this._editorWidget.revealPosition(position); } + if (select) { + const lineLength = this._editorWidget.getModel()?.getLineLength(position.lineNumber) ?? 0; + if (lineLength) { + this._editorWidget.setSelection({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: lineLength + 1 }); + } + } } getCodeBlockContext(): ICodeBlockActionContext | undefined { @@ -247,17 +264,17 @@ export class AccessibleView extends Disposable { return; } provider.onOpen?.(); - let viewContainer: HTMLElement | undefined; const delegate: IContextViewDelegate = { getAnchor: () => { return { x: (getActiveWindow().innerWidth / 2) - ((Math.min(this._layoutService.activeContainerDimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.activeContainerOffset.quickPickTop }; }, render: (container) => { - viewContainer = container; - viewContainer.classList.add('accessible-view-container'); + this._viewContainer = container; + this._viewContainer.classList.add('accessible-view-container'); return this._render(provider, container, showAccessibleViewHelp); }, onHide: () => { if (!showAccessibleViewHelp) { this._updateLastProvider(); + this._currentProvider?.dispose(); this._currentProvider = undefined; this._resetContextKeys(); } @@ -276,7 +293,7 @@ export class AccessibleView extends Disposable { if (symbol && this._currentProvider) { this.showSymbol(this._currentProvider, symbol); } - if (provider instanceof AdvancedContentProvider && provider.onDidRequestClearLastProvider) { + if (provider instanceof AccessibleContentProvider && provider.onDidRequestClearLastProvider) { this._register(provider.onDidRequestClearLastProvider((id: string) => { if (this._lastProvider?.options.id === id) { this._lastProvider = undefined; @@ -295,24 +312,32 @@ export class AccessibleView extends Disposable { } if (provider.onDidChangeContent) { this._register(provider.onDidChangeContent(() => { - if (viewContainer) { this._render(provider, viewContainer, showAccessibleViewHelp); } + if (this._viewContainer) { this._render(provider, this._viewContainer, showAccessibleViewHelp); } })); } } previous(): void { - this._currentProvider?.previous?.(); + const newContent = this._currentProvider?.providePreviousContent?.(); + if (!this._currentProvider || !this._viewContainer || !newContent) { + return; + } + this._render(this._currentProvider, this._viewContainer, undefined, newContent); } next(): void { - this._currentProvider?.next?.(); + const newContent = this._currentProvider?.provideNextContent?.(); + if (!this._currentProvider || !this._viewContainer || !newContent) { + return; + } + this._render(this._currentProvider, this._viewContainer, undefined, newContent); } private _verbosityEnabled(): boolean { if (!this._currentProvider) { return false; } - return this._currentProvider instanceof AdvancedContentProvider ? this._configurationService.getValue(this._currentProvider.verbositySettingKey) === true : this._storageService.getBoolean(`${ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX}${this._currentProvider.id}`, StorageScope.APPLICATION, false); + return this._currentProvider instanceof AccessibleContentProvider ? this._configurationService.getValue(this._currentProvider.verbositySettingKey) === true : this._storageService.getBoolean(`${ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX}${this._currentProvider.id}`, StorageScope.APPLICATION, false); } goToSymbol(): void { @@ -322,7 +347,10 @@ export class AccessibleView extends Disposable { this._instantiationService.createInstance(AccessibleViewSymbolQuickPick, this).show(this._currentProvider); } - calculateCodeBlocks(markdown: string): void { + calculateCodeBlocks(markdown?: string): void { + if (!markdown) { + return; + } if (this._currentProvider?.id !== AccessibleViewProviderId.Chat) { return; } @@ -352,7 +380,7 @@ export class AccessibleView extends Disposable { } getSymbols(): IAccessibleViewSymbol[] | undefined { - const provider = this._currentProvider instanceof AdvancedContentProvider ? this._currentProvider : undefined; + const provider = this._currentProvider instanceof AccessibleContentProvider ? this._currentProvider : undefined; if (!this._currentContent || !provider) { return; } @@ -364,7 +392,7 @@ export class AccessibleView extends Disposable { // Symbols haven't been provided and we cannot parse this language return; } - const markdownTokens: marked.TokensList | undefined = marked.lexer(this._currentContent); + const markdownTokens: marked.TokensList | undefined = marked.marked.lexer(this._currentContent); if (!markdownTokens) { return; } @@ -379,33 +407,33 @@ export class AccessibleView extends Disposable { this._openerService.open(URI.parse(this._currentProvider.options.readMoreUrl)); } - configureKeybindings(): void { + configureKeybindings(unassigned: boolean): void { this._inQuickPick = true; const provider = this._updateLastProvider(); - const items = provider?.options?.configureKeybindingItems; + const items = unassigned ? provider?.options?.configureKeybindingItems : provider?.options?.configuredKeybindingItems; if (!items) { return; } - const quickPick: IQuickPick = this._quickInputService.createQuickPick(); - this._register(quickPick); + const disposables = this._register(new DisposableStore()); + const quickPick: IQuickPick = disposables.add(this._quickInputService.createQuickPick()); quickPick.items = items; quickPick.title = localize('keybindings', 'Configure keybindings'); quickPick.placeholder = localize('selectKeybinding', 'Select a command ID to configure a keybinding for it'); quickPick.show(); - quickPick.onDidAccept(async () => { + disposables.add(quickPick.onDidAccept(async () => { const item = quickPick.selectedItems[0]; if (item) { await this._commandService.executeCommand('workbench.action.openGlobalKeybindings', item.id); } quickPick.dispose(); - }); - quickPick.onDidHide(() => { + })); + disposables.add(quickPick.onDidHide(() => { if (!quickPick.selectedItems.length && provider) { this.show(provider); } - quickPick.dispose(); + disposables.dispose(); this._inQuickPick = false; - }); + })); } private _convertTokensToSymbols(tokens: marked.TokensList, symbols: IAccessibleViewSymbol[]): void { @@ -420,12 +448,12 @@ export class AccessibleView extends Disposable { label = token.text; break; case 'list': { - const firstItem = token.items?.[0]; + const firstItem = (token as marked.Tokens.List).items[0]; if (!firstItem) { break; } firstListItem = `- ${firstItem.text}`; - label = token.items?.map(i => i.text).join(', '); + label = (token as marked.Tokens.List).items.map(i => i.text).join(', '); break; } } @@ -464,7 +492,7 @@ export class AccessibleView extends Disposable { } disableHint(): void { - if (!(this._currentProvider instanceof AdvancedContentProvider)) { + if (!(this._currentProvider instanceof AccessibleContentProvider)) { return; } this._configurationService.updateValue(this._currentProvider?.verbositySettingKey, false); @@ -479,50 +507,52 @@ export class AccessibleView extends Disposable { this._accessibleViewIsShown.set(shown); this._accessiblityHelpIsShown.reset(); } - this._accessibleViewSupportsNavigation.set(provider.next !== undefined || provider.previous !== undefined); + this._accessibleViewSupportsNavigation.set(provider.provideNextContent !== undefined || provider.providePreviousContent !== undefined); this._accessibleViewVerbosityEnabled.set(this._verbosityEnabled()); this._accessibleViewGoToSymbolSupported.set(this._goToSymbolsSupported() ? this.getSymbols()?.length! > 0 : false); } - private _render(provider: AccesibleViewContentProvider, container: HTMLElement, showAccessibleViewHelp?: boolean): IDisposable { - this._currentProvider = provider; - this._accessibleViewCurrentProviderId.set(provider.id); - const verbose = this._verbosityEnabled(); - const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\n\nOpen a browser window with more information related to accessibility.", AccessibilityCommandId.AccessibilityHelpOpenHelpLink) : ''; - let disableHelpHint = ''; - if (provider instanceof AdvancedContentProvider && provider.options.type === AccessibleViewType.Help && verbose) { - disableHelpHint = this._getDisableVerbosityHint(); + private _updateContent(provider: AccesibleViewContentProvider, updatedContent?: string): void { + let content = updatedContent ?? provider.provideContent(); + if (provider.options.type === AccessibleViewType.View) { + this._currentContent = content; + this._hasUnassignedKeybindings.reset(); + this._hasAssignedKeybindings.reset(); + return; } - const accessibilitySupport = this._accessibilityService.isScreenReaderOptimized(); - let message = ''; - if (provider.options.type === AccessibleViewType.Help) { - const turnOnMessage = ( - isMacintosh - ? AccessibilityHelpNLS.changeConfigToOnMac - : AccessibilityHelpNLS.changeConfigToOnWinLinux - ); - if (accessibilitySupport && provider instanceof AdvancedContentProvider && provider.verbositySettingKey === AccessibilityVerbositySettingId.Editor) { - message = AccessibilityHelpNLS.auto_on; - message += '\n'; - } else if (!accessibilitySupport) { - message = AccessibilityHelpNLS.auto_off + '\n' + turnOnMessage; - message += '\n'; + const readMoreLinkHint = this._readMoreHint(provider); + const disableHelpHint = this._disableVerbosityHint(provider); + const screenReaderModeHint = this._screenReaderModeHint(provider); + const exitThisDialogHint = this._exitDialogHint(provider); + let configureKbHint = ''; + let configureAssignedKbHint = ''; + const resolvedContent = resolveContentAndKeybindingItems(this._keybindingService, screenReaderModeHint + content + readMoreLinkHint + disableHelpHint + exitThisDialogHint); + if (resolvedContent) { + content = resolvedContent.content.value; + if (resolvedContent.configureKeybindingItems) { + provider.options.configureKeybindingItems = resolvedContent.configureKeybindingItems; + this._hasUnassignedKeybindings.set(true); + configureKbHint = this._configureUnassignedKbHint(); + } else { + this._hasAssignedKeybindings.reset(); } - } - const exitThisDialogHint = verbose && !provider.options.position ? localize('exit', '\n\nExit this dialog (Escape).') : ''; - let content = provider.provideContent(); - if (provider.options.type === AccessibleViewType.Help) { - const resolvedContent = resolveContentAndKeybindingItems(this._keybindingService, content + readMoreLink + disableHelpHint + exitThisDialogHint); - if (resolvedContent) { - content = resolvedContent.content.value; - if (resolvedContent.configureKeybindingItems) { - provider.options.configureKeybindingItems = resolvedContent.configureKeybindingItems; - } + if (resolvedContent.configuredKeybindingItems) { + provider.options.configuredKeybindingItems = resolvedContent.configuredKeybindingItems; + this._hasAssignedKeybindings.set(true); + configureAssignedKbHint = this._configureAssignedKbHint(); + } else { + this._hasAssignedKeybindings.reset(); } } - const newContent = message + content; - this.calculateCodeBlocks(newContent); - this._currentContent = newContent; + this._currentContent = content + configureKbHint + configureAssignedKbHint; + } + + private _render(provider: AccesibleViewContentProvider, container: HTMLElement, showAccessibleViewHelp?: boolean, updatedContent?: string): IDisposable { + this._currentProvider = provider; + this._accessibleViewCurrentProviderId.set(provider.id); + const verbose = this._verbosityEnabled(); + this._updateContent(provider, updatedContent); + this.calculateCodeBlocks(this._currentContent); this._updateContextKeys(provider, true); const widgetIsFocused = this._editorWidget.hasTextFocus() || this._editorWidget.hasWidgetFocus(); this._getTextModel(URI.from({ path: `accessible-view-${provider.id}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => { @@ -579,6 +609,8 @@ export class AccessibleView extends Disposable { this._updateContextKeys(provider, false); this._lastProvider = undefined; this._currentContent = undefined; + this._currentProvider?.dispose(); + this._currentProvider = undefined; }; const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyDown((e) => { @@ -593,7 +625,7 @@ export class AccessibleView extends Disposable { e.preventDefault(); e.stopPropagation(); } - if (provider instanceof AdvancedContentProvider) { + if (provider instanceof AccessibleContentProvider) { provider.onKeyDown?.(e); } })); @@ -649,7 +681,7 @@ export class AccessibleView extends Disposable { if (!this._currentProvider) { return false; } - return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || (this._currentProvider instanceof AdvancedContentProvider && !!this._currentProvider.getSymbols?.()); + return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || (this._currentProvider instanceof AccessibleContentProvider && !!this._currentProvider.getSymbols?.()); } private _updateLastProvider(): AccesibleViewContentProvider | undefined { @@ -657,7 +689,7 @@ export class AccessibleView extends Disposable { if (!provider) { return; } - const lastProvider = provider instanceof AdvancedContentProvider ? new AdvancedContentProvider( + const lastProvider = provider instanceof AccessibleContentProvider ? new AccessibleContentProvider( provider.id, provider.options, provider.provideContent.bind(provider), @@ -665,8 +697,8 @@ export class AccessibleView extends Disposable { provider.verbositySettingKey, provider.onOpen?.bind(provider), provider.actions, - provider.next?.bind(provider), - provider.previous?.bind(provider), + provider.provideNextContent?.bind(provider), + provider.providePreviousContent?.bind(provider), provider.onDidChangeContent?.bind(provider), provider.onKeyDown?.bind(provider), provider.getSymbols?.bind(provider), @@ -676,8 +708,8 @@ export class AccessibleView extends Disposable { provider.provideContent.bind(provider), provider.onClose.bind(provider), provider.onOpen?.bind(provider), - provider.next?.bind(provider), - provider.previous?.bind(provider), + provider.provideNextContent?.bind(provider), + provider.providePreviousContent?.bind(provider), provider.actions, provider.onDidChangeContent?.bind(provider), ); @@ -689,26 +721,41 @@ export class AccessibleView extends Disposable { if (!lastProvider) { return; } - - const accessibleViewHelpProvider = { - id: lastProvider.id, - provideContent: () => lastProvider.options.customHelp ? lastProvider?.options.customHelp() : this._getAccessibleViewHelpDialogContent(this._goToSymbolsSupported()), - onClose: () => { - this._contextViewService.hideContextView(); - // HACK: Delay to allow the context view to hide #207638 - queueMicrotask(() => this.show(lastProvider)); - }, - options: { type: AccessibleViewType.Help }, - verbositySettingKey: lastProvider instanceof AdvancedContentProvider ? lastProvider.verbositySettingKey : undefined - }; + let accessibleViewHelpProvider; + if (lastProvider instanceof AccessibleContentProvider) { + accessibleViewHelpProvider = new AccessibleContentProvider( + lastProvider.id as AccessibleViewProviderId, + { type: AccessibleViewType.Help }, + () => lastProvider.options.customHelp ? lastProvider?.options.customHelp() : this._accessibleViewHelpDialogContent(this._goToSymbolsSupported()), + () => { + this._contextViewService.hideContextView(); + // HACK: Delay to allow the context view to hide #207638 + queueMicrotask(() => this.show(lastProvider)); + }, + lastProvider.verbositySettingKey as AccessibilityVerbositySettingId + ); + } else { + accessibleViewHelpProvider = new ExtensionContentProvider( + lastProvider.id as AccessibleViewProviderId, + { type: AccessibleViewType.Help }, + () => lastProvider.options.customHelp ? lastProvider?.options.customHelp() : this._accessibleViewHelpDialogContent(this._goToSymbolsSupported()), + () => { + this._contextViewService.hideContextView(); + // HACK: Delay to allow the context view to hide #207638 + queueMicrotask(() => this.show(lastProvider)); + }, + ); + } this._contextViewService.hideContextView(); // HACK: Delay to allow the context view to hide #186514 - queueMicrotask(() => this.show(accessibleViewHelpProvider, undefined, true)); + if (accessibleViewHelpProvider) { + queueMicrotask(() => this.show(accessibleViewHelpProvider, undefined, true)); + } } - private _getAccessibleViewHelpDialogContent(providerHasSymbols?: boolean): string { - const navigationHint = this._getNavigationHint(); - const goToSymbolHint = this._getGoToSymbolHint(providerHasSymbols); + private _accessibleViewHelpDialogContent(providerHasSymbols?: boolean): string { + const navigationHint = this._navigationHint(); + const goToSymbolHint = this._goToSymbolHint(providerHasSymbols); const toolbarHint = localize('toolbar', "Navigate to the toolbar (Shift+Tab)."); const chatHints = this._getChatHints(); @@ -732,24 +779,65 @@ export class AccessibleView extends Disposable { if (this._currentProvider?.id !== AccessibleViewProviderId.Chat) { return; } - return [localize('insertAtCursor', " - Insert the code block at the cursor."), - localize('insertIntoNewFile', " - Insert the code block into a new file."), - localize('runInTerminal', " - Run the code block in the terminal.\n")].join('\n'); + return [localize('insertAtCursor', " - Insert the code block at the cursor{0}.", ''), + localize('insertIntoNewFile', " - Insert the code block into a new file{0}.", ''), + localize('runInTerminal', " - Run the code block in the terminal{0}.\n", '')].join('\n'); } - private _getNavigationHint(): string { - return localize('accessibleViewNextPreviousHint', "Show the next item or previous item.", AccessibilityCommandId.ShowNext, AccessibilityCommandId.ShowPrevious); + private _navigationHint(): string { + return localize('accessibleViewNextPreviousHint', "Show the next item{0} or previous item{1}.", ``); } - private _getDisableVerbosityHint(): string { - return localize('acessibleViewDisableHint', "\n\nDisable accessibility verbosity for this feature.", AccessibilityCommandId.DisableVerbosityHint); + private _disableVerbosityHint(provider: AccesibleViewContentProvider): string { + if (provider.options.type === AccessibleViewType.Help && this._verbosityEnabled()) { + return localize('acessibleViewDisableHint', "\nDisable accessibility verbosity for this feature{0}.", ``); + } + return ''; } - private _getGoToSymbolHint(providerHasSymbols?: boolean): string | undefined { + private _goToSymbolHint(providerHasSymbols?: boolean): string | undefined { if (!providerHasSymbols) { return; } - return localize('goToSymbolHint', 'Go to a symbol.', AccessibilityCommandId.GoToSymbol); + return localize('goToSymbolHint', 'Go to a symbol{0}.', ``); + } + + private _configureUnassignedKbHint(): string { + const configureKb = this._keybindingService.lookupKeybinding(AccessibilityCommandId.AccessibilityHelpConfigureKeybindings)?.getAriaLabel(); + const keybindingToConfigureQuickPick = configureKb ? '(' + configureKb + ')' : 'by assigning a keybinding to the command Accessibility Help Configure Unassigned Keybindings.'; + return localize('configureKb', '\nConfigure keybindings for commands that lack them {0}.', keybindingToConfigureQuickPick); + } + + private _configureAssignedKbHint(): string { + const configureKb = this._keybindingService.lookupKeybinding(AccessibilityCommandId.AccessibilityHelpConfigureAssignedKeybindings)?.getAriaLabel(); + const keybindingToConfigureQuickPick = configureKb ? '(' + configureKb + ')' : 'by assigning a keybinding to the command Accessibility Help Configure Assigned Keybindings.'; + return localize('configureKbAssigned', '\nConfigure keybindings for commands that already have assignments {0}.', keybindingToConfigureQuickPick); + } + + private _screenReaderModeHint(provider: AccesibleViewContentProvider): string { + const accessibilitySupport = this._accessibilityService.isScreenReaderOptimized(); + let screenReaderModeHint = ''; + const turnOnMessage = ( + isMacintosh + ? AccessibilityHelpNLS.changeConfigToOnMac + : AccessibilityHelpNLS.changeConfigToOnWinLinux + ); + if (accessibilitySupport && provider.id === AccessibleViewProviderId.Editor) { + screenReaderModeHint = AccessibilityHelpNLS.auto_on; + screenReaderModeHint += '\n'; + } else if (!accessibilitySupport) { + screenReaderModeHint = AccessibilityHelpNLS.auto_off + '\n' + turnOnMessage; + screenReaderModeHint += '\n'; + } + return screenReaderModeHint; + } + + private _exitDialogHint(provider: AccesibleViewContentProvider): string { + return this._verbosityEnabled() && !provider.options.position ? localize('exit', '\nExit this dialog (Escape).') : ''; + } + + private _readMoreHint(provider: AccesibleViewContentProvider): string { + return provider.options.readMoreUrl ? localize("openDoc", "\nOpen a browser window with more information related to accessibility{0}.", ``) : ''; } } @@ -771,8 +859,8 @@ export class AccessibleViewService extends Disposable implements IAccessibleView } this._accessibleView.show(provider, undefined, undefined, position); } - configureKeybindings(): void { - this._accessibleView?.configureKeybindings(); + configureKeybindings(unassigned: boolean): void { + this._accessibleView?.configureKeybindings(unassigned); } openHelpLink(): void { this._accessibleView?.openHelpLink(); @@ -815,12 +903,8 @@ export class AccessibleViewService extends Disposable implements IAccessibleView const lastLine = this._accessibleView?.editorWidget.getModel()?.getLineCount(); return lastLine !== undefined && lastLine > 0 ? new Position(lastLine, 1) : undefined; } - setPosition(position: Position, reveal?: boolean): void { - const editorWidget = this._accessibleView?.editorWidget; - editorWidget?.setPosition(position); - if (reveal) { - editorWidget?.revealLine(position.lineNumber); - } + setPosition(position: Position, reveal?: boolean, select?: boolean): void { + this._accessibleView?.setPosition(position, reveal, select); } getCodeBlockContext(): ICodeBlockActionContext | undefined { return this._accessibleView?.getCodeBlockContext(); @@ -835,7 +919,8 @@ class AccessibleViewSymbolQuickPick { } show(provider: AccesibleViewContentProvider): void { - const quickPick = this._quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const quickPick = disposables.add(this._quickInputService.createQuickPick()); quickPick.placeholder = localize('accessibleViewSymbolQuickPickPlaceholder', "Type to search symbols"); quickPick.title = localize('accessibleViewSymbolQuickPickTitle', "Go to Symbol Accessible View"); const picks = []; @@ -852,16 +937,17 @@ class AccessibleViewSymbolQuickPick { quickPick.canSelectMany = false; quickPick.items = symbols; quickPick.show(); - quickPick.onDidAccept(() => { + disposables.add(quickPick.onDidAccept(() => { this._accessibleView.showSymbol(provider, quickPick.selectedItems[0]); quickPick.hide(); - }); - quickPick.onDidHide(() => { + })); + disposables.add(quickPick.onDidHide(() => { if (quickPick.selectedItems.length === 0) { // this was escaped, so refocus the accessible view this._accessibleView.show(provider); } - }); + disposables.dispose(); + })); } } diff --git a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts index 7aba4fa5..c106847a 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts @@ -11,10 +11,10 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; -import { accessibilityHelpIsShown, accessibleViewContainsCodeBlocks, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { accessibilityHelpIsShown, accessibleViewContainsCodeBlocks, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewHasAssignedKeybindings, accessibleViewHasUnassignedKeybindings, accessibleViewIsShown, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewProviderId, IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; const accessibleViewMenu = { id: MenuId.AccessibleView, @@ -62,7 +62,6 @@ class AccessibleViewNextCodeBlockAction extends Action2 { mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, }, weight: KeybindingWeight.WorkbenchContrib, }, - // to icon: Codicon.arrowRight, menu: { @@ -233,20 +232,56 @@ class AccessibilityHelpConfigureKeybindingsAction extends Action2 { constructor() { super({ id: AccessibilityCommandId.AccessibilityHelpConfigureKeybindings, - precondition: ContextKeyExpr.and(accessibilityHelpIsShown), + precondition: ContextKeyExpr.and(accessibilityHelpIsShown, accessibleViewHasUnassignedKeybindings), + icon: Codicon.key, keybinding: { primary: KeyMod.Alt | KeyCode.KeyK, weight: KeybindingWeight.WorkbenchContrib }, - title: localize('editor.action.accessibilityHelpConfigureKeybindings', "Accessibility Help Configure Keybindings") + menu: [ + { + id: MenuId.AccessibleView, + group: 'navigation', + order: 3, + when: accessibleViewHasUnassignedKeybindings, + } + ], + title: localize('editor.action.accessibilityHelpConfigureUnassignedKeybindings', "Accessibility Help Configure Unassigned Keybindings") }); } async run(accessor: ServicesAccessor): Promise { - await accessor.get(IAccessibleViewService).configureKeybindings(); + await accessor.get(IAccessibleViewService).configureKeybindings(true); } } registerAction2(AccessibilityHelpConfigureKeybindingsAction); +class AccessibilityHelpConfigureAssignedKeybindingsAction extends Action2 { + constructor() { + super({ + id: AccessibilityCommandId.AccessibilityHelpConfigureAssignedKeybindings, + precondition: ContextKeyExpr.and(accessibilityHelpIsShown, accessibleViewHasAssignedKeybindings), + icon: Codicon.key, + keybinding: { + primary: KeyMod.Alt | KeyCode.KeyA, + weight: KeybindingWeight.WorkbenchContrib + }, + menu: [ + { + id: MenuId.AccessibleView, + group: 'navigation', + order: 4, + when: accessibleViewHasAssignedKeybindings, + } + ], + title: localize('editor.action.accessibilityHelpConfigureAssignedKeybindings', "Accessibility Help Configure Assigned Keybindings") + }); + } + async run(accessor: ServicesAccessor): Promise { + await accessor.get(IAccessibleViewService).configureKeybindings(false); + } +} +registerAction2(AccessibilityHelpConfigureAssignedKeybindingsAction); + class AccessibilityHelpOpenHelpLinkAction extends Action2 { constructor() { diff --git a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewContributions.ts b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewContributions.ts index d8d07767..bc026ec3 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewContributions.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewContributions.ts @@ -6,7 +6,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { AccessibleViewType, AdvancedContentProvider, ExtensionContentProvider, IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewType, AccessibleContentProvider, ExtensionContentProvider, IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -27,12 +27,17 @@ export class AccesibleViewContributions extends Disposable { super(); AccessibleViewRegistry.getImplementations().forEach(impl => { const implementation = (accessor: ServicesAccessor) => { - const provider: AdvancedContentProvider | ExtensionContentProvider | undefined = impl.getProvider(accessor); - if (provider) { + const provider: AccessibleContentProvider | ExtensionContentProvider | undefined = impl.getProvider(accessor); + if (!provider) { + return false; + } + try { accessor.get(IAccessibleViewService).show(provider); return true; + } catch { + provider.dispose(); + return false; } - return false; }; if (impl.type === AccessibleViewType.View) { this._register(AccessibleViewAction.addImplementation(impl.priority, impl.name, implementation, impl.when)); diff --git a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewKeybindingResolver.ts b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewKeybindingResolver.ts index 88bfbd30..107f60f2 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewKeybindingResolver.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/accessibleViewKeybindingResolver.ts @@ -6,35 +6,37 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; -import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; -export function resolveContentAndKeybindingItems(keybindingService: IKeybindingService, value?: string): { content: MarkdownString; configureKeybindingItems: IPickerQuickAccessItem[] | undefined } | undefined { +export function resolveContentAndKeybindingItems(keybindingService: IKeybindingService, value?: string): { content: MarkdownString; configureKeybindingItems: IPickerQuickAccessItem[] | undefined; configuredKeybindingItems: IPickerQuickAccessItem[] | undefined } | undefined { if (!value) { return; } const configureKeybindingItems: IPickerQuickAccessItem[] = []; - const matches = value.matchAll(/\.*)\>/gm); + const configuredKeybindingItems: IPickerQuickAccessItem[] = []; + const matches = value.matchAll(/(\[^\<]*)\>)/gm); for (const match of [...matches]) { const commandId = match?.groups?.commandId; let kbLabel; if (match?.length && commandId) { const keybinding = keybindingService.lookupKeybinding(commandId)?.getAriaLabel(); if (!keybinding) { - const configureKb = keybindingService.lookupKeybinding(AccessibilityCommandId.AccessibilityHelpConfigureKeybindings)?.getAriaLabel(); - const keybindingToConfigureQuickPick = configureKb ? '(' + configureKb + ')' : 'by assigning a keybinding to the command Accessibility Help Configure Keybindings.'; - kbLabel = `, configure a keybinding ` + keybindingToConfigureQuickPick; + kbLabel = ` (unassigned keybinding)`; configureKeybindingItems.push({ label: commandId, id: commandId }); } else { kbLabel = ' (' + keybinding + ')'; + configuredKeybindingItems.push({ + label: commandId, + id: commandId + }); } value = value.replace(match[0], kbLabel); } } const content = new MarkdownString(value); content.isTrusted = true; - return { content, configureKeybindingItems: configureKeybindingItems.length ? configureKeybindingItems : undefined }; + return { content, configureKeybindingItems: configureKeybindingItems.length ? configureKeybindingItems : undefined, configuredKeybindingItems: configuredKeybindingItems.length ? configuredKeybindingItems : undefined }; } diff --git a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index 0e47093d..003ee0d1 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -39,7 +39,7 @@ export class EditorAccessibilityHelpContribution extends Disposable { } } -class EditorAccessibilityHelpProvider implements IAccessibleViewContentProvider { +class EditorAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider { id = AccessibleViewProviderId.Editor; onClose() { this._editor.focus(); @@ -51,6 +51,7 @@ class EditorAccessibilityHelpProvider implements IAccessibleViewContentProvider @IKeybindingService private readonly _keybindingService: IKeybindingService, @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { + super(); } provideContent(): string { @@ -93,7 +94,15 @@ class EditorAccessibilityHelpProvider implements IAccessibleViewContentProvider } else { content.push(AccessibilityHelpNLS.tabFocusModeOffMsg); } - return content.join('\n\n'); + content.push(AccessibilityHelpNLS.codeFolding); + content.push(AccessibilityHelpNLS.intellisense); + content.push(AccessibilityHelpNLS.showOrFocusHover); + content.push(AccessibilityHelpNLS.goToSymbol); + content.push(AccessibilityHelpNLS.startDebugging); + content.push(AccessibilityHelpNLS.setBreakpoint); + content.push(AccessibilityHelpNLS.debugExecuteSelection); + content.push(AccessibilityHelpNLS.addToWatch); + return content.join('\n'); } } diff --git a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/extensionAccesibilityHelp.contribution.ts b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/extensionAccesibilityHelp.contribution.ts index f8381417..7c189822 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibility/browser/extensionAccesibilityHelp.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibility/browser/extensionAccesibilityHelp.contribution.ts @@ -57,7 +57,6 @@ function registerAccessibilityHelpAction(keybindingService: IKeybindingService, () => viewsService.openView(viewDescriptor.id, true), ); }, - dispose: () => { }, })); disposableStore.add(keybindingService.onDidUpdateKeybindings(() => { diff --git a/patched-vscode/src/vs/workbench/contrib/accessibility/common/accessibilityCommands.ts b/patched-vscode/src/vs/workbench/contrib/accessibility/common/accessibilityCommands.ts index d689c025..7e84a29b 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibility/common/accessibilityCommands.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibility/common/accessibilityCommands.ts @@ -14,5 +14,6 @@ export const enum AccessibilityCommandId { NextCodeBlock = 'editor.action.accessibleViewNextCodeBlock', PreviousCodeBlock = 'editor.action.accessibleViewPreviousCodeBlock', AccessibilityHelpConfigureKeybindings = 'editor.action.accessibilityHelpConfigureKeybindings', + AccessibilityHelpConfigureAssignedKeybindings = 'editor.action.accessibilityHelpConfigureAssignedKeybindings', AccessibilityHelpOpenHelpLink = 'editor.action.accessibilityHelpOpenHelpLink', } diff --git a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts index cb298c79..deb77085 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts @@ -10,11 +10,11 @@ import { registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/com import { AccessibilitySignalLineDebuggerContribution } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution'; import { ShowAccessibilityAnnouncementHelp, ShowSignalSoundHelp } from 'vs/workbench/contrib/accessibilitySignals/browser/commands'; import { EditorTextPropertySignalsContribution } from 'vs/workbench/contrib/accessibilitySignals/browser/editorTextPropertySignalsContribution'; -import { wrapInReloadableClass } from 'vs/workbench/contrib/accessibilitySignals/browser/reloadableWorkbenchContribution'; +import { wrapInReloadableClass0 } from 'vs/platform/observable/common/wrapInReloadableClass'; registerSingleton(IAccessibilitySignalService, AccessibilitySignalService, InstantiationType.Delayed); -registerWorkbenchContribution2('EditorTextPropertySignalsContribution', wrapInReloadableClass(() => EditorTextPropertySignalsContribution), WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2('EditorTextPropertySignalsContribution', wrapInReloadableClass0(() => EditorTextPropertySignalsContribution), WorkbenchPhase.AfterRestored); registerWorkbenchContribution2('AccessibilitySignalLineDebuggerContribution', AccessibilitySignalLineDebuggerContribution, WorkbenchPhase.AfterRestored); registerAction2(ShowSignalSoundHelp); diff --git a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts index 336832eb..f0bc218c 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts @@ -19,7 +19,7 @@ export class AccessibilitySignalLineDebuggerContribution ) { super(); - const isEnabled = observableFromEvent( + const isEnabled = observableFromEvent(this, accessibilitySignalService.onSoundEnabledChanged(AccessibilitySignal.onDebugBreak), () => accessibilitySignalService.isSoundEnabled(AccessibilitySignal.onDebugBreak) ); diff --git a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index a20ca9f9..d6e7f159 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -13,6 +13,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export class ShowSignalSoundHelp extends Action2 { static readonly ID = 'signals.sounds.help'; @@ -44,10 +45,11 @@ export class ShowSignalSoundHelp extends Action2 { alwaysVisible: true }] : [] })).sort((a, b) => a.label.localeCompare(b.label)); - const qp = quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const qp = disposables.add(quickInputService.createQuickPick()); qp.items = items; qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.sound') !== 'never'); - qp.onDidAccept(() => { + disposables.add(qp.onDidAccept(() => { const enabledSounds = qp.selectedItems.map(i => i.signal); const disabledSounds = qp.items.map(i => (i as any).signal).filter(i => !enabledSounds.includes(i)); for (const signal of enabledSounds) { @@ -67,13 +69,14 @@ export class ShowSignalSoundHelp extends Action2 { configurationService.updateValue(signal.settingsKey, value); } qp.hide(); - }); - qp.onDidTriggerItemButton(e => { + })); + disposables.add(qp.onDidTriggerItemButton(e => { preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: e.item.signal.settingsKey, edit: true } }); - }); - qp.onDidChangeActive(() => { + })); + disposables.add(qp.onDidChangeActive(() => { accessibilitySignalService.playSound(qp.activeItems[0].signal.sound.getSound(true), true, AcknowledgeDocCommentsToken); - }); + })); + disposables.add(qp.onDidHide(() => disposables.dispose())); qp.placeholder = localize('sounds.help.placeholder', 'Select a sound to play and configure'); qp.canSelectMany = true; await qp.show(); @@ -114,11 +117,12 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { alwaysVisible: true, }] : [] })).sort((a, b) => a.label.localeCompare(b.label)); - const qp = quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const qp = disposables.add(quickInputService.createQuickPick()); qp.items = items; qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); const screenReaderOptimized = accessibilityService.isScreenReaderOptimized(); - qp.onDidAccept(() => { + disposables.add(qp.onDidAccept(() => { if (!screenReaderOptimized) { // announcements are off by default when screen reader is not active qp.hide(); @@ -139,10 +143,11 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { configurationService.updateValue(signal.settingsKey, value); } qp.hide(); - }); - qp.onDidTriggerItemButton(e => { + })); + disposables.add(qp.onDidTriggerItemButton(e => { preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: e.item.signal.settingsKey, edit: true } }); - }); + })); + disposables.add(qp.onDidHide(() => disposables.dispose())); qp.placeholder = screenReaderOptimized ? localize('announcement.help.placeholder', 'Select an announcement to configure') : localize('announcement.help.placeholder.disabled', 'Screen reader is not active, announcements are disabled by default.'); qp.canSelectMany = true; await qp.show(); diff --git a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/editorTextPropertySignalsContribution.ts b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/editorTextPropertySignalsContribution.ts index cb6eb229..f5dddf8a 100644 --- a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/editorTextPropertySignalsContribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/editorTextPropertySignalsContribution.ts @@ -35,7 +35,7 @@ export class EditorTextPropertySignalsContribution extends Disposable implements .some(signal => observableFromValueWithChangeEvent(this, this._accessibilitySignalService.getEnabledState(signal, false)).read(reader)) ); - private readonly _activeEditorObservable = observableFromEvent( + private readonly _activeEditorObservable = observableFromEvent(this, this._editorService.onDidActiveEditorChange, (_) => { const activeTextEditorControl = this._editorService.activeTextEditorControl; @@ -73,7 +73,7 @@ export class EditorTextPropertySignalsContribution extends Disposable implements let lastLine = -1; const ignoredLineSignalsForCurrentLine = new Set(); - const timeouts = new DisposableStore(); + const timeouts = store.add(new DisposableStore()); const propertySources = this._textProperties.map(p => ({ source: p.createSource(editor, editorModel), property: p })); diff --git a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/reloadableWorkbenchContribution.ts b/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/reloadableWorkbenchContribution.ts deleted file mode 100644 index 43fb32ee..00000000 --- a/patched-vscode/src/vs/workbench/contrib/accessibilitySignals/browser/reloadableWorkbenchContribution.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { isHotReloadEnabled } from 'vs/base/common/hotReload'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { autorunWithStore } from 'vs/base/common/observable'; -import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; - -/** - * Wrap a class in a reloadable wrapper. - * When the wrapper is created, the original class is created. - * When the original class changes, the instance is re-created. -*/ -export function wrapInReloadableClass(getClass: () => (new (...args: any[]) => any)): (new (...args: any[]) => any) { - if (!isHotReloadEnabled()) { - return getClass(); - } - - return class ReloadableWrapper extends BaseClass { - private _autorun: IDisposable | undefined = undefined; - - override init() { - this._autorun = autorunWithStore((reader, store) => { - const clazz = readHotReloadableExport(getClass(), reader); - store.add(this.instantiationService.createInstance(clazz)); - }); - } - - dispose(): void { - this._autorun?.dispose(); - } - }; -} - -class BaseClass { - constructor( - @IInstantiationService protected readonly instantiationService: IInstantiationService, - ) { - this.init(); - } - - init(): void { } -} diff --git a/patched-vscode/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts b/patched-vscode/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts index 8a4be09b..f7c3dcea 100644 --- a/patched-vscode/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/accountEntitlements/browser/accountsEntitlements.contribution.ts @@ -26,10 +26,8 @@ import { IRequestService, asText } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { isWeb } from 'vs/base/common/platform'; -import { isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; const accountsBadgeConfigKey = 'workbench.accounts.experimental.showEntitlements'; -const chatWelcomeViewConfigKey = 'workbench.chat.experimental.showWelcomeView'; type EntitlementEnablementClassification = { enabled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating if the entitlement is enabled' }; @@ -47,7 +45,6 @@ class EntitlementsContribution extends Disposable implements IWorkbenchContribut private isInitialized = false; private showAccountsBadgeContextKey = new RawContextKey(accountsBadgeConfigKey, false).bindTo(this.contextService); - private showChatWelcomeViewContextKey = new RawContextKey(chatWelcomeViewConfigKey, false).bindTo(this.contextService); private readonly accountsMenuBadgeDisposable = this._register(new MutableDisposable()); constructor( @@ -98,7 +95,6 @@ class EntitlementsContribution extends Disposable implements IWorkbenchContribut await this.enableEntitlements(e.event.added[0]); } else if (e.providerId === this.productService.gitHubEntitlement!.providerId && e.event.removed?.length) { this.showAccountsBadgeContextKey.set(false); - this.showChatWelcomeViewContextKey.set(false); this.accountsMenuBadgeDisposable.clear(); } })); @@ -156,29 +152,19 @@ class EntitlementsContribution extends Disposable implements IWorkbenchContribut return; } - const isInternal = isInternalTelemetry(this.productService, this.configurationService); const showAccountsBadge = this.configurationService.inspect(accountsBadgeConfigKey).value ?? false; - const showWelcomeView = this.configurationService.inspect(chatWelcomeViewConfigKey).value ?? false; const [enabled, org] = await this.getEntitlementsInfo(session); - if (enabled) { - if (isInternal && showWelcomeView) { - this.showChatWelcomeViewContextKey.set(true); - this.telemetryService.publicLog2<{ enabled: boolean }, EntitlementEnablementClassification>(chatWelcomeViewConfigKey, { enabled: true }); - } - if (showAccountsBadge) { - this.createAccountsBadge(org); - this.showAccountsBadgeContextKey.set(showAccountsBadge); - this.telemetryService.publicLog2<{ enabled: boolean }, EntitlementEnablementClassification>(accountsBadgeConfigKey, { enabled: true }); - } + if (enabled && showAccountsBadge) { + this.createAccountsBadge(org); + this.showAccountsBadgeContextKey.set(showAccountsBadge); + this.telemetryService.publicLog2<{ enabled: boolean }, EntitlementEnablementClassification>(accountsBadgeConfigKey, { enabled: true }); } } private disableEntitlements() { this.storageService.store(accountsBadgeConfigKey, false, StorageScope.APPLICATION, StorageTarget.MACHINE); - this.storageService.store(chatWelcomeViewConfigKey, false, StorageScope.APPLICATION, StorageTarget.MACHINE); this.showAccountsBadgeContextKey.set(false); - this.showChatWelcomeViewContextKey.set(false); this.accountsMenuBadgeDisposable.clear(); } @@ -260,17 +246,4 @@ configurationRegistry.registerConfiguration({ } }); -configurationRegistry.registerConfiguration({ - ...applicationConfigurationNodeBase, - properties: { - 'workbench.chat.experimental.showWelcomeView': { - scope: ConfigurationScope.MACHINE, - type: 'boolean', - default: false, - tags: ['experimental'], - description: localize('workbench.chat.showWelcomeView', "When enabled, the chat panel welcome view will be shown.") - } - } -}); - registerWorkbenchContribution2('workbench.contrib.entitlements', EntitlementsContribution, WorkbenchPhase.BlockRestore); diff --git a/patched-vscode/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts b/patched-vscode/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts index 3c3d4b9a..1c205fd5 100644 --- a/patched-vscode/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts +++ b/patched-vscode/src/vs/workbench/contrib/authentication/browser/actions/manageTrustedExtensionsForAccountAction.ts @@ -8,7 +8,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize, localize2 } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IAuthenticationAccessService } from 'vs/workbench/services/authentication/browser/authenticationAccessService'; @@ -26,23 +26,51 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, options?: { providerId: string; accountLabel: string }): Promise { - const productService = accessor.get(IProductService); - const extensionService = accessor.get(IExtensionService); - const dialogService = accessor.get(IDialogService); - const quickInputService = accessor.get(IQuickInputService); - const authenticationService = accessor.get(IAuthenticationService); - const authenticationUsageService = accessor.get(IAuthenticationUsageService); - const authenticationAccessService = accessor.get(IAuthenticationAccessService); + override run(accessor: ServicesAccessor, options?: { providerId: string; accountLabel: string }): Promise { + const instantiationService = accessor.get(IInstantiationService); + return instantiationService.createInstance(ManageTrustedExtensionsForAccountActionImpl).run(options); + } +} - let providerId = options?.providerId; - let accountLabel = options?.accountLabel; +interface TrustedExtensionsQuickPickItem extends IQuickPickItem { + extension: AllowedExtension; + lastUsed?: number; +} +class ManageTrustedExtensionsForAccountActionImpl { + constructor( + @IProductService private readonly _productService: IProductService, + @IExtensionService private readonly _extensionService: IExtensionService, + @IDialogService private readonly _dialogService: IDialogService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + @IAuthenticationService private readonly _authenticationService: IAuthenticationService, + @IAuthenticationUsageService private readonly _authenticationUsageService: IAuthenticationUsageService, + @IAuthenticationAccessService private readonly _authenticationAccessService: IAuthenticationAccessService + ) { } + + async run(options?: { providerId: string; accountLabel: string }) { + const { providerId, accountLabel } = await this._resolveProviderAndAccountLabel(options?.providerId, options?.accountLabel); + if (!providerId || !accountLabel) { + return; + } + + const items = await this._getItems(providerId, accountLabel); + if (!items.length) { + return; + } + const disposables = new DisposableStore(); + const picker = this._createQuickPick(disposables, providerId, accountLabel); + picker.items = items; + picker.selectedItems = items.filter((i): i is TrustedExtensionsQuickPickItem => i.type !== 'separator' && !!i.picked); + picker.show(); + } + + private async _resolveProviderAndAccountLabel(providerId: string | undefined, accountLabel: string | undefined) { if (!providerId || !accountLabel) { const accounts = new Array<{ providerId: string; providerLabel: string; accountLabel: string }>(); - for (const id of authenticationService.getProviderIds()) { - const providerLabel = authenticationService.getProvider(id).label; - const sessions = await authenticationService.getSessions(id); + for (const id of this._authenticationService.getProviderIds()) { + const providerLabel = this._authenticationService.getProvider(id).label; + const sessions = await this._authenticationService.getSessions(id); const uniqueAccountLabels = new Set(); for (const session of sessions) { if (!uniqueAccountLabels.has(session.account.label)) { @@ -52,7 +80,7 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { } } - const pick = await quickInputService.pick( + const pick = await this._quickInputService.pick( accounts.map(account => ({ providerId: account.providerId, label: account.accountLabel, @@ -68,12 +96,15 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { providerId = pick.providerId; accountLabel = pick.label; } else { - return; + return { providerId: undefined, accountLabel: undefined }; } } + return { providerId, accountLabel }; + } - const allowedExtensions = authenticationAccessService.readAllowedExtensions(providerId, accountLabel); - const trustedExtensionAuthAccess = productService.trustedExtensionAuthAccess; + private async _getItems(providerId: string, accountLabel: string) { + const allowedExtensions = this._authenticationAccessService.readAllowedExtensions(providerId, accountLabel); + const trustedExtensionAuthAccess = this._productService.trustedExtensionAuthAccess; const trustedExtensionIds = // Case 1: trustedExtensionAuthAccess is an array Array.isArray(trustedExtensionAuthAccess) @@ -86,7 +117,7 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { const allowedExtension = allowedExtensions.find(ext => ext.id === extensionId); if (!allowedExtension) { // Add the extension to the allowedExtensions list - const extension = await extensionService.getExtension(extensionId); + const extension = await this._extensionService.getExtension(extensionId); if (extension) { allowedExtensions.push({ id: extensionId, @@ -103,21 +134,11 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { } if (!allowedExtensions.length) { - dialogService.info(localize('noTrustedExtensions', "This account has not been used by any extensions.")); - return; + this._dialogService.info(localize('noTrustedExtensions', "This account has not been used by any extensions.")); + return []; } - interface TrustedExtensionsQuickPickItem extends IQuickPickItem { - extension: AllowedExtension; - lastUsed?: number; - } - - const disposableStore = new DisposableStore(); - const quickPick = disposableStore.add(quickInputService.createQuickPick()); - quickPick.canSelectMany = true; - quickPick.customButton = true; - quickPick.customLabel = localize('manageTrustedExtensions.cancel', 'Cancel'); - const usages = authenticationUsageService.readAccountUsages(providerId, accountLabel); + const usages = this._authenticationUsageService.readAccountUsages(providerId, accountLabel); const trustedExtensions = []; const otherExtensions = []; for (const extension of allowedExtensions) { @@ -131,34 +152,43 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { } const sortByLastUsed = (a: AllowedExtension, b: AllowedExtension) => (b.lastUsed || 0) - (a.lastUsed || 0); - const toQuickPickItem = function (extension: AllowedExtension): IQuickPickItem & { extension: AllowedExtension } { - const lastUsed = extension.lastUsed; - const description = lastUsed - ? localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true)) - : localize('notUsed', "Has not used this account"); - let tooltip: string | undefined; - let disabled: boolean | undefined; - if (extension.trusted) { - tooltip = localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account"); - disabled = true; - } - return { - label: extension.name, - extension, - description, - tooltip, - disabled, - picked: extension.allowed === undefined || extension.allowed - }; - }; - const items: Array = [ - ...otherExtensions.sort(sortByLastUsed).map(toQuickPickItem), - { type: 'separator', label: localize('trustedExtensions', "Trusted by Microsoft") }, - ...trustedExtensions.sort(sortByLastUsed).map(toQuickPickItem) + + const items = [ + ...otherExtensions.sort(sortByLastUsed).map(this._toQuickPickItem), + { type: 'separator', label: localize('trustedExtensions', "Trusted by Microsoft") } satisfies IQuickPickSeparator, + ...trustedExtensions.sort(sortByLastUsed).map(this._toQuickPickItem) ]; - quickPick.items = items; - quickPick.selectedItems = items.filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator' && (item.extension.allowed === undefined || item.extension.allowed)); + return items; + } + + private _toQuickPickItem(extension: AllowedExtension): TrustedExtensionsQuickPickItem { + const lastUsed = extension.lastUsed; + const description = lastUsed + ? localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true)) + : localize('notUsed', "Has not used this account"); + let tooltip: string | undefined; + let disabled: boolean | undefined; + if (extension.trusted) { + tooltip = localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account"); + disabled = true; + } + return { + label: extension.name, + extension, + description, + tooltip, + disabled, + picked: extension.allowed === undefined || extension.allowed + }; + } + + private _createQuickPick(disposableStore: DisposableStore, providerId: string, accountLabel: string) { + const quickPick = disposableStore.add(this._quickInputService.createQuickPick({ useSeparators: true })); + quickPick.canSelectMany = true; + quickPick.customButton = true; + quickPick.customLabel = localize('manageTrustedExtensions.cancel', 'Cancel'); + quickPick.title = localize('manageTrustedExtensions', "Manage Trusted Extensions"); quickPick.placeholder = localize('manageExtensions', "Choose which extensions can access this account"); @@ -171,7 +201,7 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { updatedAllowedList.forEach(extension => { extension.allowed = allowedExtensionsSet.has(extension); }); - authenticationAccessService.updateAllowedExtensions(providerId, accountLabel, updatedAllowedList); + this._authenticationAccessService.updateAllowedExtensions(providerId, accountLabel, updatedAllowedList); quickPick.hide(); })); @@ -182,8 +212,6 @@ export class ManageTrustedExtensionsForAccountAction extends Action2 { disposableStore.add(quickPick.onDidCustom(() => { quickPick.hide(); })); - - quickPick.show(); + return quickPick; } - } diff --git a/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 36ef1504..a24720d5 100644 --- a/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -3,44 +3,44 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./bulkEdit'; -import { WorkbenchAsyncDataTree, IOpenEvent } from 'vs/platform/list/browser/listService'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter, compareBulkFileOperations } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree'; +import { ButtonBar } from 'vs/base/browser/ui/button/button'; +import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { CachedFunction, LRUCachedFunction } from 'vs/base/common/cache'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { FuzzyScore } from 'vs/base/common/filters'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { Mutable } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import 'vs/css!./bulkEdit'; +import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; +import { IMultiDiffEditorOptions, IMultiDiffResourceId } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; +import { IRange } from 'vs/editor/common/core/range'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { localize } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IOpenEvent, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; -import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { Mutable } from 'vs/base/common/types'; -import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; -import { IMultiDiffEditorOptions, IMultiDiffResourceId } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; -import { IRange } from 'vs/editor/common/core/range'; -import { CachedFunction, LRUCachedFunction } from 'vs/base/common/cache'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ResourceLabels } from 'vs/workbench/browser/labels'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IMultiDiffEditorResource, IResourceDiffEditorInput } from 'vs/workbench/common/editor'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; +import { BulkEditAccessibilityProvider, BulkEditDataSource, BulkEditDelegate, BulkEditElement, BulkEditIdentityProvider, BulkEditNaviLabelProvider, BulkEditSorter, CategoryElement, CategoryElementRenderer, compareBulkFileOperations, FileElement, FileElementRenderer, TextEditElement, TextEditElementRenderer } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; const enum State { Data = 'data', @@ -56,7 +56,7 @@ export class BulkEditPane extends ViewPane { static readonly ctxGroupByFile = new RawContextKey('refactorPreview.groupByFile', true); static readonly ctxHasCheckedChanges = new RawContextKey('refactorPreview.hasCheckedChanges', true); - private static readonly _memGroupByFile = `${BulkEditPane.ID}.groupByFile`; + private static readonly _memGroupByFile = `${this.ID}.groupByFile`; private _tree!: WorkbenchAsyncDataTree; private _treeDataSource!: BulkEditDataSource; @@ -120,7 +120,7 @@ export class BulkEditPane extends ViewPane { const resourceLabels = this._instaService.createInstance( ResourceLabels, - { onDidChangeVisibility: this.onDidChangeBodyVisibility } + { onDidChangeVisibility: this.onDidChangeBodyVisibility } ); this._disposables.add(resourceLabels); @@ -369,16 +369,20 @@ export class BulkEditPane extends ViewPane { }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } - private readonly _computeResourceDiffEditorInputs = new LRUCachedFunction(async (fileOperations: BulkFileOperation[]) => { - const computeDiffEditorInput = new CachedFunction>(async (fileOperation) => { + private readonly _computeResourceDiffEditorInputs = new LRUCachedFunction< + BulkFileOperation[], + Promise<{ resources: IMultiDiffEditorResource[]; getResourceDiffEditorInputIdOfOperation: (operation: BulkFileOperation) => Promise }> + >(async (fileOperations) => { + const computeDiffEditorInput = new CachedFunction>(async (fileOperation) => { const fileOperationUri = fileOperation.uri; const previewUri = this._currentProvider!.asPreviewUri(fileOperationUri); // delete if (fileOperation.type & BulkFileOperationType.Delete) { return { original: { resource: URI.revive(previewUri) }, - modified: { resource: undefined } - }; + modified: { resource: undefined }, + goToFileResource: fileOperation.uri, + } satisfies IMultiDiffEditorResource; } // rename, create, edits @@ -392,8 +396,9 @@ export class BulkEditPane extends ViewPane { } return { original: { resource: URI.revive(leftResource) }, - modified: { resource: URI.revive(previewUri) } - }; + modified: { resource: URI.revive(previewUri) }, + goToFileResource: leftResource, + } satisfies IMultiDiffEditorResource; } }); diff --git a/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index 40fbb606..44c687a6 100644 --- a/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -353,7 +353,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { private static readonly Schema = 'vscode-bulkeditpreview-editor'; - static emptyPreview = URI.from({ scheme: BulkEditPreviewProvider.Schema, fragment: 'empty' }); + static emptyPreview = URI.from({ scheme: this.Schema, fragment: 'empty' }); static fromPreviewUri(uri: URI): URI { diff --git a/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index e45a9500..45a97a0c 100644 --- a/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/patched-vscode/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -25,13 +25,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; // --- VIEW MODEL @@ -202,9 +200,7 @@ export class BulkEditDataSource implements IAsyncDataSource')); + content.push(localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view{0}.', '')); content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); - content.push(localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command.')); - content.push(localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command.')); - content.push(localize('workbench.action.chat.nextCodeBlock', 'To focus the next code block within a response, invoke the Chat: Next Code Block command.')); - content.push(localize('workbench.action.chat.nextFileTree', 'To focus the next file tree within a response, invoke the Chat: Next File Tree command.')); - content.push(localize('workbench.action.chat.clear', 'To clear the request/response list, invoke the Chat Clear command.')); + content.push(localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command{0}.', '')); + content.push(localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command{0}.', '')); + content.push(localize('workbench.action.chat.nextCodeBlock', 'To focus the next code block within a response, invoke the Chat: Next Code Block command{0}.', '')); + content.push(localize('workbench.action.chat.nextFileTree', 'To focus the next file tree within a response, invoke the Chat: Next File Tree command{0}.', '')); + content.push(localize('workbench.action.chat.newChat', 'To create a new chat session, invoke the New Chat command{0}.', '')); } else { content.push(localize('inlineChat.overview', "Inline chat occurs within a code editor and takes into account the current selection. It is useful for making changes to the current editor. For example, fixing diagnostics, documenting or refactoring code. Keep in mind that AI generated code may be incorrect.")); - content.push(localize('inlineChat.access', "It can be activated via code actions or directly using the command: Inline Chat: Start Inline Chat.")); - content.push(localize('inlineChat.requestHistory', 'In the input box, use and to navigate your request history. Edit input and use enter or the submit button to run a new request.')); - content.push(localize('inlineChat.inspectResponse', 'In the input box, inspect the response in the accessible viewview')); + content.push(localize('inlineChat.access', "It can be activated via code actions or directly using the command: Inline Chat: Start Inline Chat{0}.", '')); + content.push(localize('inlineChat.requestHistory', 'In the input box, use Show Previous{0} and Show Next{1} to navigate your request history. Edit input and use enter or the submit button to run a new request.', '', '')); + content.push(localize('inlineChat.inspectResponse', 'In the input box, inspect the response in the accessible view{0}.', '')); content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with a /. Type / to discover such ready-made commands.")); content.push(localize('inlineChat.fix', "If a fix action is invoked, a response will indicate the problem with the current code. A diff editor will be rendered and can be reached by tabbing.")); - content.push(localize('inlineChat.diff', "Once in the diff editor, enter review mode with. Use up and down arrows to navigate lines with the proposed changes.", AccessibleDiffViewerNext.id)); + content.push(localize('inlineChat.diff', "Once in the diff editor, enter review mode with{0}. Use up and down arrows to navigate lines with the proposed changes.", AccessibleDiffViewerNext.id)); content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); } content.push(localize('chat.signals', "Accessibility Signals can be changed via settings with a prefix of signals.chat. By default, if a request takes more than 4 seconds, you will hear a sound indicating that progress is still occurring.")); - return content.join('\n\n'); + return content.join('\n'); } export function getChatAccessibilityHelpProvider(accessor: ServicesAccessor, editor: ICodeEditor | undefined, type: 'panelChat' | 'inlineChat') { @@ -70,11 +70,11 @@ export function getChatAccessibilityHelpProvider(accessor: ServicesAccessor, edi const cachedPosition = inputEditor.getPosition(); inputEditor.getSupportedActions(); const helpText = getAccessibilityHelpText(type); - return { - id: type === 'panelChat' ? AccessibleViewProviderId.Chat : AccessibleViewProviderId.InlineChat, - verbositySettingKey: type === 'panelChat' ? AccessibilityVerbositySettingId.Chat : AccessibilityVerbositySettingId.InlineChat, - provideContent: () => helpText, - onClose: () => { + return new AccessibleContentProvider( + type === 'panelChat' ? AccessibleViewProviderId.Chat : AccessibleViewProviderId.InlineChat, + { type: AccessibleViewType.Help }, + () => helpText, + () => { if (type === 'panelChat' && cachedPosition) { inputEditor.setPosition(cachedPosition); inputEditor.focus(); @@ -86,6 +86,6 @@ export function getChatAccessibilityHelpProvider(accessor: ServicesAccessor, edi } }, - options: { type: AccessibleViewType.Help } - }; + type === 'panelChat' ? AccessibilityVerbositySettingId.Chat : AccessibilityVerbositySettingId.InlineChat, + ); } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index bf426bec..d2e60118 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { coalesce } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; +import { fromNowByDay } from 'vs/base/common/date'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -14,17 +16,17 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IsLinuxContext, IsWindowsContext } from 'vs/platform/contextkey/common/contextkeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IQuickInputButton, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; +import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { clearChatEditor } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; import { CHAT_VIEW_ID, IChatWidgetService, showChatView } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; -import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_CHAT_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatDetail, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; @@ -102,10 +104,9 @@ class OpenChatGlobalAction extends Action2 { } } -class ChatHistoryAction extends ViewAction { +class ChatHistoryAction extends Action2 { constructor() { super({ - viewId: CHAT_VIEW_ID, id: `workbench.action.chat.history`, title: localize2('chat.history.label', "Show Chats..."), menu: { @@ -121,59 +122,97 @@ class ChatHistoryAction extends ViewAction { }); } - async runInView(accessor: ServicesAccessor, view: ChatViewPane) { + async run(accessor: ServicesAccessor) { const chatService = accessor.get(IChatService); const quickInputService = accessor.get(IQuickInputService); const viewsService = accessor.get(IViewsService); const editorService = accessor.get(IEditorService); - const items = chatService.getHistory(); - const openInEditorButton: IQuickInputButton = { - iconClass: ThemeIcon.asClassName(Codicon.file), - tooltip: localize('interactiveSession.history.editor', "Open in Editor"), - }; - const deleteButton: IQuickInputButton = { - iconClass: ThemeIcon.asClassName(Codicon.x), - tooltip: localize('interactiveSession.history.delete', "Delete"), - }; + const showPicker = () => { + const openInEditorButton: IQuickInputButton = { + iconClass: ThemeIcon.asClassName(Codicon.file), + tooltip: localize('interactiveSession.history.editor', "Open in Editor"), + }; + const deleteButton: IQuickInputButton = { + iconClass: ThemeIcon.asClassName(Codicon.x), + tooltip: localize('interactiveSession.history.delete', "Delete"), + }; + const renameButton: IQuickInputButton = { + iconClass: ThemeIcon.asClassName(Codicon.pencil), + tooltip: localize('chat.history.rename', "Rename"), + }; - interface IChatPickerItem extends IQuickPickItem { - chat: IChatDetail; - } - const picks: IChatPickerItem[] = items.map((i): IChatPickerItem => ({ - label: i.title, - chat: i, - buttons: [ - openInEditorButton, - deleteButton - ] - })); - const store = new DisposableStore(); - const picker = store.add(quickInputService.createQuickPick()); - picker.placeholder = localize('interactiveSession.history.pick', "Switch to chat"); - picker.items = picks; - store.add(picker.onDidTriggerItemButton(context => { - if (context.button === openInEditorButton) { - editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId: context.item.chat.sessionId }, pinned: true } }, ACTIVE_GROUP); - picker.hide(); - } else if (context.button === deleteButton) { - chatService.removeHistoryEntry(context.item.chat.sessionId); - picker.items = picks.filter(i => i !== context.item); - } - })); - store.add(picker.onDidAccept(async () => { - try { - const item = picker.selectedItems[0]; - const sessionId = item.chat.sessionId; - const view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; - view.loadSession(sessionId); - } finally { - picker.hide(); + interface IChatPickerItem extends IQuickPickItem { + chat: IChatDetail; } - })); - store.add(picker.onDidHide(() => store.dispose())); - picker.show(); + const getPicks = () => { + const items = chatService.getHistory(); + items.sort((a, b) => (b.lastMessageDate ?? 0) - (a.lastMessageDate ?? 0)); + + let lastDate: string | undefined = undefined; + const picks = items.flatMap((i): [IQuickPickSeparator | undefined, IChatPickerItem] => { + const timeAgoStr = fromNowByDay(i.lastMessageDate, true, true); + const separator: IQuickPickSeparator | undefined = timeAgoStr !== lastDate ? { + type: 'separator', label: timeAgoStr, + } : undefined; + lastDate = timeAgoStr; + return [ + separator, + { + label: i.title, + description: i.isActive ? `(${localize('currentChatLabel', 'current')})` : '', + chat: i, + buttons: i.isActive ? [renameButton] : [ + renameButton, + openInEditorButton, + deleteButton, + ] + } + ]; + }); + + return coalesce(picks); + }; + + const store = new DisposableStore(); + const picker = store.add(quickInputService.createQuickPick({ useSeparators: true })); + picker.placeholder = localize('interactiveSession.history.pick', "Switch to chat"); + const picks = getPicks(); + picker.items = picks; + store.add(picker.onDidTriggerItemButton(async context => { + if (context.button === openInEditorButton) { + const options: IChatEditorOptions = { target: { sessionId: context.item.chat.sessionId }, pinned: true }; + editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }, ACTIVE_GROUP); + picker.hide(); + } else if (context.button === deleteButton) { + chatService.removeHistoryEntry(context.item.chat.sessionId); + picker.items = getPicks(); + } else if (context.button === renameButton) { + const title = await quickInputService.input({ title: localize('newChatTitle', "New chat title"), value: context.item.chat.title }); + if (title) { + chatService.setChatSessionTitle(context.item.chat.sessionId, title); + } + + // The quick input hides the picker, it gets disposed, so we kick it off from scratch + showPicker(); + } + })); + store.add(picker.onDidAccept(async () => { + try { + const item = picker.selectedItems[0]; + const sessionId = item.chat.sessionId; + const view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; + view.loadSession(sessionId); + } finally { + picker.hide(); + } + })); + store.add(picker.onDidHide(() => store.dispose())); + + picker.show(); + }; + showPicker(); } } @@ -226,8 +265,26 @@ export function registerChatActions() { }); } async run(accessor: ServicesAccessor, ...args: any[]) { + const editorGroupsService = accessor.get(IEditorGroupsService); + const viewsService = accessor.get(IViewsService); + const chatService = accessor.get(IChatService); chatService.clearAllHistoryEntries(); + + const chatView = viewsService.getViewWithId(CHAT_VIEW_ID) as ChatViewPane | undefined; + if (chatView) { + chatView.widget.clear(); + } + + // Clear all chat editors. Have to go this route because the chat editor may be in the background and + // not have a ChatEditorInput. + editorGroupsService.groups.forEach(group => { + group.editors.forEach(editor => { + if (editor instanceof ChatEditorInput) { + clearChatEditor(accessor, editor); + } + }); + }); } }); @@ -236,7 +293,7 @@ export function registerChatActions() { super({ id: 'chat.action.focus', title: localize2('actions.interactiveSession.focus', 'Focus Chat List'), - precondition: ContextKeyExpr.and(CONTEXT_IN_CHAT_INPUT, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel)), + precondition: ContextKeyExpr.and(CONTEXT_IN_CHAT_INPUT), category: CHAT_CATEGORY, keybinding: [ // On mac, require that the cursor is at the top of the input, to avoid stealing cmd+up to move the cursor to the top @@ -288,6 +345,6 @@ export function stringifyItem(item: IChatRequestViewModel | IChatResponseViewMod if (isRequestVM(item)) { return (includeName ? `${item.username}: ` : '') + item.messageText; } else { - return (includeName ? `${item.username}: ` : '') + item.response.asString(); + return (includeName ? `${item.username}: ` : '') + item.response.toString(); } } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts index 00ffcaed..cd85340c 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts @@ -6,18 +6,22 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export async function clearChatEditor(accessor: ServicesAccessor): Promise { +export async function clearChatEditor(accessor: ServicesAccessor, chatEditorInput?: ChatEditorInput): Promise { const editorService = accessor.get(IEditorService); - const editorGroupsService = accessor.get(IEditorGroupsService); - const chatEditorInput = editorService.activeEditor; + if (!chatEditorInput) { + const editorInput = editorService.activeEditor; + chatEditorInput = editorInput instanceof ChatEditorInput ? editorInput : undefined; + } + if (chatEditorInput instanceof ChatEditorInput) { + // A chat editor can only be open in one group + const identifier = editorService.findEditors(chatEditorInput.resource)[0]; await editorService.replaceEditors([{ editor: chatEditorInput, replacement: { resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } satisfies IChatEditorOptions } - }], editorGroupsService.activeGroup); + }], identifier.groupId); } } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index b74d1b38..1c6572f3 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -81,7 +81,7 @@ export function registerNewChatActions() { if (isChatViewTitleActionContext(context)) { // Is running in the Chat view title announceChatCleared(accessibilitySignalService); - context.chatView.clear(); + context.chatView.widget.clear(); context.chatView.widget.focusInput(); } else { // Is running from f1 or keybinding diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 6d9b8c58..8aab036d 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -6,23 +6,26 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { isEqual } from 'vs/base/common/resources'; +import { IActiveCodeEditor, ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { DocumentContextItem, WorkspaceEdit } from 'vs/editor/common/languages'; +import { DocumentContextItem, IWorkspaceFileEdit, IWorkspaceTextEdit } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CopyAction } from 'vs/editor/contrib/clipboard/browser/clipboard'; -import { localize2 } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; import { accessibleViewInCodeBlock } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; @@ -38,6 +41,11 @@ import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/comm import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import * as strings from 'vs/base/common/strings'; +import { CharCode } from 'vs/base/common/charCode'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { coalesce } from 'vs/base/common/arrays'; +import { AsyncIterableObject } from 'vs/base/common/async'; export interface IChatCodeBlockActionContext extends ICodeBlockActionContext { element: IChatResponseViewModel; @@ -81,6 +89,214 @@ abstract class ChatCodeBlockAction extends Action2 { abstract runWithContext(accessor: ServicesAccessor, context: ICodeBlockActionContext): any; } +interface IComputeEditsResult { + readonly edits: Array; + readonly codeMapper?: string; +} + +abstract class InsertCodeBlockAction extends ChatCodeBlockAction { + + override async runWithContext(accessor: ServicesAccessor, context: ICodeBlockActionContext) { + const editorService = accessor.get(IEditorService); + const textFileService = accessor.get(ITextFileService); + + if (isResponseFiltered(context)) { + // When run from command palette + return; + } + + if (editorService.activeEditorPane?.getId() === NOTEBOOK_EDITOR_ID) { + return this.handleNotebookEditor(accessor, editorService.activeEditorPane.getControl() as INotebookEditor, context); + } + + let activeEditorControl = editorService.activeTextEditorControl; + if (isDiffEditor(activeEditorControl)) { + activeEditorControl = activeEditorControl.getOriginalEditor().hasTextFocus() ? activeEditorControl.getOriginalEditor() : activeEditorControl.getModifiedEditor(); + } + + if (!isCodeEditor(activeEditorControl)) { + return; + } + + if (!activeEditorControl.hasModel()) { + return; + } + const activeModelUri = activeEditorControl.getModel().uri; + + // Check if model is editable, currently only support untitled and text file + const activeTextModel = textFileService.files.get(activeModelUri) ?? textFileService.untitled.get(activeModelUri); + if (!activeTextModel || activeTextModel.isReadonly()) { + return; + } + + await this.handleTextEditor(accessor, activeEditorControl, context); + } + + private async handleNotebookEditor(accessor: ServicesAccessor, notebookEditor: INotebookEditor, context: ICodeBlockActionContext) { + if (!notebookEditor.hasModel()) { + return; + } + + if (notebookEditor.isReadOnly) { + return; + } + + if (notebookEditor.activeCodeEditor?.hasTextFocus()) { + const codeEditor = notebookEditor.activeCodeEditor; + if (codeEditor.hasModel()) { + return this.handleTextEditor(accessor, codeEditor, context); + } + } + + const languageService = accessor.get(ILanguageService); + const chatService = accessor.get(IChatService); + + const focusRange = notebookEditor.getFocus(); + const next = Math.max(focusRange.end - 1, 0); + insertCell(languageService, notebookEditor, next, CellKind.Code, 'below', context.code, true); + this.notifyUserAction(chatService, context); + } + + protected async computeEdits(accessor: ServicesAccessor, codeEditor: IActiveCodeEditor, codeBlockActionContext: ICodeBlockActionContext): Promise { + const activeModel = codeEditor.getModel(); + const range = codeEditor.getSelection() ?? new Range(activeModel.getLineCount(), 1, activeModel.getLineCount(), 1); + const text = reindent(codeBlockActionContext.code, activeModel, range.startLineNumber); + return { edits: [new ResourceTextEdit(activeModel.uri, { range, text })] }; + } + + protected get showPreview() { + return false; + } + + private async handleTextEditor(accessor: ServicesAccessor, codeEditor: IActiveCodeEditor, codeBlockActionContext: ICodeBlockActionContext) { + const bulkEditService = accessor.get(IBulkEditService); + const codeEditorService = accessor.get(ICodeEditorService); + const chatService = accessor.get(IChatService); + + const result = await this.computeEdits(accessor, codeEditor, codeBlockActionContext); + this.notifyUserAction(chatService, codeBlockActionContext, result); + + if (this.showPreview) { + const showWithPreview = await this.applyWithInlinePreview(codeEditorService, result.edits, codeEditor); + if (!showWithPreview) { + await bulkEditService.apply(result.edits, { showPreview: true }); + const activeModel = codeEditor.getModel(); + codeEditorService.listCodeEditors().find(editor => editor.getModel()?.uri.toString() === activeModel.uri.toString())?.focus(); + } + } else { + await bulkEditService.apply(result.edits); + const activeModel = codeEditor.getModel(); + codeEditorService.listCodeEditors().find(editor => editor.getModel()?.uri.toString() === activeModel.uri.toString())?.focus(); + } + } + + private async applyWithInlinePreview(codeEditorService: ICodeEditorService, edits: Array, codeEditor: IActiveCodeEditor) { + const firstEdit = edits[0]; + if (!ResourceTextEdit.is(firstEdit)) { + return false; + } + const resource = firstEdit.resource; + const textEdits = coalesce(edits.map(edit => ResourceTextEdit.is(edit) && isEqual(resource, edit.resource) ? edit.textEdit : undefined)); + if (textEdits.length !== edits.length) { // more than one file has changed + return false; + } + const editorToApply = await codeEditorService.openCodeEditor({ resource }, codeEditor); + if (editorToApply) { + const inlineChatController = InlineChatController.get(editorToApply); + if (inlineChatController) { + const cancellationTokenSource = new CancellationTokenSource(); + try { + return await inlineChatController.reviewEdits(textEdits[0].range, AsyncIterableObject.fromArray(textEdits), cancellationTokenSource.token); + } finally { + cancellationTokenSource.dispose(); + } + } + } + return false; + } + + private notifyUserAction(chatService: IChatService, context: ICodeBlockActionContext, result?: IComputeEditsResult) { + if (isResponseVM(context.element)) { + chatService.notifyUserAction({ + agentId: context.element.agent?.id, + command: context.element.slashCommand?.name, + sessionId: context.element.sessionId, + requestId: context.element.requestId, + result: context.element.result, + action: { + kind: 'insert', + codeBlockIndex: context.codeBlockIndex, + totalCharacters: context.code.length, + userAction: this.desc.id, + codeMapper: result?.codeMapper, + } + }); + } + } + +} + +function reindent(codeBlockContent: string, model: ITextModel, seletionStartLine: number) { + const newContent = strings.splitLines(codeBlockContent); + if (newContent.length === 0) { + return codeBlockContent; + } + + const formattingOptions = model.getFormattingOptions(); + const codeIndentLevel = computeIndentation(model.getLineContent(seletionStartLine), formattingOptions.tabSize).level; + + const indents = newContent.map(line => computeIndentation(line, formattingOptions.tabSize)); + + // find the smallest indent level in the code block + const newContentIndentLevel = indents.reduce((min, indent, index) => { + if (indent.length !== newContent[index].length) { // ignore empty lines + return Math.min(indent.level, min); + } + return min; + }, Number.MAX_VALUE); + + if (newContentIndentLevel === Number.MAX_VALUE || newContentIndentLevel === codeIndentLevel) { + // all lines are empty or the indent is already correct + return codeBlockContent; + } + const newLines = []; + for (let i = 0; i < newContent.length; i++) { + const { level, length } = indents[i]; + const newLevel = Math.max(0, codeIndentLevel + level - newContentIndentLevel); + const newIndentation = formattingOptions.insertSpaces ? ' '.repeat(formattingOptions.tabSize * newLevel) : '\t'.repeat(newLevel); + newLines.push(newIndentation + newContent[i].substring(length)); + } + return newLines.join('\n'); +} + +// TODO: Merge with `computeIndentLevel` from `vs/editor/common/model/utils.ts` +function computeIndentation(line: string, tabSize: number): { level: number; length: number } { + let nSpaces = 0; + let level = 0; + let i = 0; + let length = 0; + const len = line.length; + while (i < len) { + const chCode = line.charCodeAt(i); + if (chCode === CharCode.Space) { + nSpaces++; + if (nSpaces === tabSize) { + level++; + nSpaces = 0; + length = i + 1; + } + } else if (chCode === CharCode.Tab) { + level++; + nSpaces = 0; + length = i + 1; + } else { + break; + } + i++; + } + return { level, length }; +} + export function registerChatCodeBlockActions() { registerAction2(class CopyCodeBlockAction extends Action2 { constructor() { @@ -92,7 +308,8 @@ export function registerChatCodeBlockActions() { icon: Codicon.copy, menu: { id: MenuId.ChatCodeBlock, - group: 'navigation' + group: 'navigation', + order: 30 } }); } @@ -110,6 +327,7 @@ export function registerChatCodeBlockActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ agentId: context.element.agent?.id, + command: context.element.slashCommand?.name, sessionId: context.element.sessionId, requestId: context.element.requestId, result: context.element.result, @@ -155,6 +373,7 @@ export function registerChatCodeBlockActions() { if (element) { chatService.notifyUserAction({ agentId: element.agent?.id, + command: element.slashCommand?.name, sessionId: element.sessionId, requestId: element.requestId, result: element.result, @@ -178,19 +397,20 @@ export function registerChatCodeBlockActions() { return false; }); - registerAction2(class InsertCodeBlockAction extends ChatCodeBlockAction { + registerAction2(class SmartApplyInEditorAction extends InsertCodeBlockAction { constructor() { super({ - id: 'workbench.action.chat.insertCodeBlock', - title: localize2('interactive.insertCodeBlock.label', "Insert at Cursor"), + id: 'workbench.action.chat.applyInEditor', + title: localize2('interactive.applyInEditor.label', "Apply in Editor"), precondition: CONTEXT_CHAT_ENABLED, f1: true, category: CHAT_CATEGORY, - icon: Codicon.insert, + icon: Codicon.sparkle, menu: { id: MenuId.ChatCodeBlock, group: 'navigation', - when: CONTEXT_IN_CHAT_SESSION + when: CONTEXT_IN_CHAT_SESSION, + order: 10 }, keybinding: { when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), accessibleViewInCodeBlock), @@ -201,149 +421,103 @@ export function registerChatCodeBlockActions() { }); } - override async runWithContext(accessor: ServicesAccessor, context: ICodeBlockActionContext) { - const editorService = accessor.get(IEditorService); - const textFileService = accessor.get(ITextFileService); - - if (isResponseFiltered(context)) { - // When run from command palette - return; - } - - if (editorService.activeEditorPane?.getId() === NOTEBOOK_EDITOR_ID) { - return this.handleNotebookEditor(accessor, editorService.activeEditorPane.getControl() as INotebookEditor, context); - } - - let activeEditorControl = editorService.activeTextEditorControl; - if (isDiffEditor(activeEditorControl)) { - activeEditorControl = activeEditorControl.getOriginalEditor().hasTextFocus() ? activeEditorControl.getOriginalEditor() : activeEditorControl.getModifiedEditor(); - } - - if (!isCodeEditor(activeEditorControl)) { - return; - } - - const activeModel = activeEditorControl.getModel(); - if (!activeModel) { - return; - } - - // Check if model is editable, currently only support untitled and text file - const activeTextModel = textFileService.files.get(activeModel.uri) ?? textFileService.untitled.get(activeModel.uri); - if (!activeTextModel || activeTextModel.isReadonly()) { - return; - } - - await this.handleTextEditor(accessor, activeEditorControl, activeModel, context); - } - - private async handleNotebookEditor(accessor: ServicesAccessor, notebookEditor: INotebookEditor, context: ICodeBlockActionContext) { - if (!notebookEditor.hasModel()) { - return; - } - - if (notebookEditor.isReadOnly) { - return; - } - - if (notebookEditor.activeCodeEditor?.hasTextFocus()) { - const codeEditor = notebookEditor.activeCodeEditor; - const textModel = codeEditor.getModel(); - - if (textModel) { - return this.handleTextEditor(accessor, codeEditor, textModel, context); - } - } + protected override async computeEdits(accessor: ServicesAccessor, codeEditor: IActiveCodeEditor, codeBlockActionContext: ICodeBlockActionContext): Promise { - const languageService = accessor.get(ILanguageService); - const focusRange = notebookEditor.getFocus(); - const next = Math.max(focusRange.end - 1, 0); - insertCell(languageService, notebookEditor, next, CellKind.Code, 'below', context.code, true); - this.notifyUserAction(accessor, context); - } + const progressService = accessor.get(IProgressService); + const notificationService = accessor.get(INotificationService); - private async handleTextEditor(accessor: ServicesAccessor, codeEditor: ICodeEditor, activeModel: ITextModel, codeBlockActionContext: ICodeBlockActionContext) { - this.notifyUserAction(accessor, codeBlockActionContext); - - const bulkEditService = accessor.get(IBulkEditService); - const codeEditorService = accessor.get(ICodeEditorService); + const activeModel = codeEditor.getModel(); const mappedEditsProviders = accessor.get(ILanguageFeaturesService).mappedEditsProvider.ordered(activeModel); - - // try applying workspace edit that was returned by a MappedEditsProvider, else simply insert at selection - - let mappedEdits: WorkspaceEdit | null = null; - if (mappedEditsProviders.length > 0) { - const mostRelevantProvider = mappedEditsProviders[0]; // TODO@ulugbekna: should we try all providers? // 0th sub-array - editor selections array if there are any selections // 1st sub-array - array with documents used to get the chat reply const docRefs: DocumentContextItem[][] = []; - if (codeEditor.hasModel()) { - const model = codeEditor.getModel(); - const currentDocUri = model.uri; - const currentDocVersion = model.getVersionId(); - const selections = codeEditor.getSelections(); - if (selections.length > 0) { - docRefs.push([ - { - uri: currentDocUri, - version: currentDocVersion, - ranges: selections, - } - ]); - } + const currentDocUri = activeModel.uri; + const currentDocVersion = activeModel.getVersionId(); + const selections = codeEditor.getSelections(); + if (selections.length > 0) { + docRefs.push([ + { + uri: currentDocUri, + version: currentDocVersion, + ranges: selections, + } + ]); } - const usedDocuments = getUsedDocuments(codeBlockActionContext); if (usedDocuments) { docRefs.push(usedDocuments); } const cancellationTokenSource = new CancellationTokenSource(); + try { + const edits = await progressService.withProgress( + { location: ProgressLocation.Notification, delay: 500, sticky: true, cancellable: true }, + async progress => { + for (const provider of mappedEditsProviders) { + progress.report({ message: localize('applyCodeBlock.progress', "Applying code block using {0}...", provider.displayName) }); + const mappedEdits = await provider.provideMappedEdits( + activeModel, + [codeBlockActionContext.code], + { documents: docRefs }, + cancellationTokenSource.token + ); + if (mappedEdits) { + return { edits: mappedEdits.edits, codeMapper: provider.displayName }; + } + } + return undefined; + }, + () => cancellationTokenSource.cancel() + ); + if (edits) { + return edits; + } + } catch (e) { + notificationService.notify({ severity: Severity.Error, message: localize('applyCodeBlock.error', "Failed to apply code block: {0}", e.message) }); - mappedEdits = await mostRelevantProvider.provideMappedEdits( - activeModel, - [codeBlockActionContext.code], - { documents: docRefs }, - cancellationTokenSource.token); - } - - if (mappedEdits) { - await bulkEditService.apply(mappedEdits); - } else { - const activeSelection = codeEditor.getSelection() ?? new Range(activeModel.getLineCount(), 1, activeModel.getLineCount(), 1); - await bulkEditService.apply([ - new ResourceTextEdit(activeModel.uri, { - range: activeSelection, - text: codeBlockActionContext.code, - }), - ]); + } finally { + cancellationTokenSource.dispose(); + } } - codeEditorService.listCodeEditors().find(editor => editor.getModel()?.uri.toString() === activeModel.uri.toString())?.focus(); + // fall back to inserting the code block as is + return super.computeEdits(accessor, codeEditor, codeBlockActionContext); } - private notifyUserAction(accessor: ServicesAccessor, context: ICodeBlockActionContext) { - if (isResponseVM(context.element)) { - const chatService = accessor.get(IChatService); - chatService.notifyUserAction({ - agentId: context.element.agent?.id, - sessionId: context.element.sessionId, - requestId: context.element.requestId, - result: context.element.result, - action: { - kind: 'insert', - codeBlockIndex: context.codeBlockIndex, - totalCharacters: context.code.length, - } - }); - } + protected override get showPreview() { + return true; } }); + registerAction2(class SmartApplyInEditorAction extends InsertCodeBlockAction { + constructor() { + super({ + id: 'workbench.action.chat.insertCodeBlock', + title: localize2('interactive.insertCodeBlock.label', "Insert At Cursor"), + precondition: CONTEXT_CHAT_ENABLED, + f1: true, + category: CHAT_CATEGORY, + icon: Codicon.insert, + menu: { + id: MenuId.ChatCodeBlock, + group: 'navigation', + when: CONTEXT_IN_CHAT_SESSION, + order: 20 + }, + keybinding: { + when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), accessibleViewInCodeBlock), + primary: KeyMod.CtrlCmd | KeyCode.Enter, + mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, + weight: KeybindingWeight.ExternalExtension + 1 + }, + }); + } + }); + registerAction2(class InsertIntoNewFileAction extends ChatCodeBlockAction { constructor() { super({ @@ -356,7 +530,8 @@ export function registerChatCodeBlockActions() { menu: { id: MenuId.ChatCodeBlock, group: 'navigation', - isHiddenByDefault: true + isHiddenByDefault: true, + order: 40, } }); } @@ -375,6 +550,7 @@ export function registerChatCodeBlockActions() { if (isResponseVM(context.element)) { chatService.notifyUserAction({ agentId: context.element.agent?.id, + command: context.element.slashCommand?.name, sessionId: context.element.sessionId, requestId: context.element.requestId, result: context.element.result, @@ -382,7 +558,8 @@ export function registerChatCodeBlockActions() { kind: 'insert', codeBlockIndex: context.codeBlockIndex, totalCharacters: context.code.length, - newFile: true + newFile: true, + userAction: this.desc.id, } }); } @@ -467,6 +644,7 @@ export function registerChatCodeBlockActions() { if (isResponseVM(context.element)) { chatService.notifyUserAction({ agentId: context.element.agent?.id, + command: context.element.slashCommand?.name, sessionId: context.element.sessionId, requestId: context.element.requestId, result: context.element.result, @@ -497,7 +675,7 @@ export function registerChatCodeBlockActions() { const currentResponse = curCodeBlockInfo ? curCodeBlockInfo.element : (focusedResponse ?? widget.viewModel?.getItems().reverse().find((item): item is IChatResponseViewModel => isResponseVM(item))); - if (!currentResponse) { + if (!currentResponse || !isResponseVM(currentResponse)) { return; } @@ -610,7 +788,8 @@ export function registerChatCodeCompareBlockActions() { precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, CONTEXT_CHAT_EDIT_APPLIED.negate()), menu: { id: MenuId.ChatCompareBlock, - group: 'navigation' + group: 'navigation', + order: 1, } }); } @@ -629,4 +808,28 @@ export function registerChatCodeCompareBlockActions() { }); } }); + + registerAction2(class DiscardEditsCompareBlockAction extends ChatCompareCodeBlockAction { + constructor() { + super({ + id: 'workbench.action.chat.discardCompareEdits', + title: localize2('interactive.compare.discard', "Discard Edits"), + f1: false, + category: CHAT_CATEGORY, + icon: Codicon.trash, + precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, CONTEXT_CHAT_EDIT_APPLIED.negate()), + menu: { + id: MenuId.ChatCompareBlock, + group: 'navigation', + order: 2, + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): Promise { + const instaService = accessor.get(IInstantiationService); + const editor = instaService.createInstance(DefaultChatTextEditor); + editor.discard(context.element, context.edit); + } + }); } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 01a8f050..8126b4fd 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -7,35 +7,42 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Schemas } from 'vs/base/common/network'; -import { IRange } from 'vs/editor/common/core/range'; +import { compare } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { IRange } from 'vs/editor/common/core/range'; +import { EditorType } from 'vs/editor/common/editorCommon'; import { Command } from 'vs/editor/common/languages'; import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from 'vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess'; import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { AnythingQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess'; import { IQuickInputService, IQuickPickItem, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; +import { isQuickChat } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ChatContextAttachments } from 'vs/workbench/contrib/chat/browser/contrib/chatContextAttachments'; import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_QUICK_CHAT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatRequestVariableEntry } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export function registerChatContextActions() { registerAction2(AttachContextAction); + registerAction2(AttachFileAction); + registerAction2(AttachSelectionAction); } -export type IChatContextQuickPickItem = IFileQuickPickItem | IDynamicVariableQuickPickItem | IStaticVariableQuickPickItem | IGotoSymbolQuickPickItem | ISymbolQuickPickItem | IQuickAccessQuickPickItem; +export type IChatContextQuickPickItem = IFileQuickPickItem | IDynamicVariableQuickPickItem | IStaticVariableQuickPickItem | IGotoSymbolQuickPickItem | ISymbolQuickPickItem | IQuickAccessQuickPickItem | IToolQuickPickItem; export interface IFileQuickPickItem extends IQuickPickItem { kind: 'file'; @@ -58,6 +65,13 @@ export interface IDynamicVariableQuickPickItem extends IQuickPickItem { command?: Command; } +export interface IToolQuickPickItem extends IQuickPickItem { + kind: 'tool'; + id: string; + name?: string; + icon?: ThemeIcon; +} + export interface IStaticVariableQuickPickItem extends IQuickPickItem { kind: 'static'; id: string; @@ -77,16 +91,77 @@ export interface IQuickAccessQuickPickItem extends IQuickPickItem { prefix: string; } +class AttachFileAction extends Action2 { + + static readonly ID = 'workbench.action.chat.attachFile'; + + constructor() { + super({ + id: AttachFileAction.ID, + title: localize2('workbench.action.chat.attachFile.label', "Attach File"), + category: CHAT_CATEGORY, + f1: false + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const variablesService = accessor.get(IChatVariablesService); + const textEditorService = accessor.get(IEditorService); + + const activeUri = textEditorService.activeEditor?.resource; + if (textEditorService.activeTextEditorControl?.getEditorType() === EditorType.ICodeEditor && activeUri && [Schemas.file, Schemas.vscodeRemote, Schemas.untitled].includes(activeUri.scheme)) { + variablesService.attachContext('file', activeUri, ChatAgentLocation.Panel); + } + } +} + +class AttachSelectionAction extends Action2 { + + static readonly ID = 'workbench.action.chat.attachSelection'; + + constructor() { + super({ + id: AttachSelectionAction.ID, + title: localize2('workbench.action.chat.attachSelection.label', "Add Selection to Chat"), + category: CHAT_CATEGORY, + f1: false + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const variablesService = accessor.get(IChatVariablesService); + const textEditorService = accessor.get(IEditorService); + + const activeEditor = textEditorService.activeTextEditorControl; + const activeUri = textEditorService.activeEditor?.resource; + if (textEditorService.activeTextEditorControl?.getEditorType() === EditorType.ICodeEditor && activeUri && [Schemas.file, Schemas.vscodeRemote, Schemas.untitled].includes(activeUri.scheme)) { + const selection = activeEditor?.getSelection(); + if (selection) { + variablesService.attachContext('file', { uri: activeUri, range: selection }, ChatAgentLocation.Panel); + } + } + } +} + class AttachContextAction extends Action2 { static readonly ID = 'workbench.action.chat.attachContext'; + // used to enable/disable the keybinding and defined menu containment + private static _cdt = ContextKeyExpr.or( + ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel)), + ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor), ContextKeyExpr.equals('config.chat.experimental.variables.editor', true)), + ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Notebook), ContextKeyExpr.equals('config.chat.experimental.variables.notebook', true)), + ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Terminal), ContextKeyExpr.equals('config.chat.experimental.variables.terminal', true)), + ); + constructor() { super({ id: AttachContextAction.ID, title: localize2('workbench.action.chat.attachContext.label', "Attach Context"), icon: Codicon.attach, category: CHAT_CATEGORY, + precondition: AttachContextAction._cdt, keybinding: { when: CONTEXT_IN_CHAT_INPUT, primary: KeyMod.CtrlCmd | KeyCode.Slash, @@ -94,7 +169,7 @@ class AttachContextAction extends Action2 { }, menu: [ { - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_IN_QUICK_CHAT.isEqualTo(false)), + when: AttachContextAction._cdt, id: MenuId.ChatExecute, group: 'navigation', }, @@ -128,7 +203,7 @@ class AttachContextAction extends Action2 { value: pick.value, name: `${typeof pick.value === 'string' && pick.value.startsWith('#') ? pick.value.slice(1) : ''}${selection}`, // Apply the original icon with the new name - fullName: `${pick.icon ? `$(${pick.icon.id}) ` : ''}${selection}` + fullName: selection }); } else if ('symbol' in pick && pick.symbol) { // Symbol @@ -161,6 +236,15 @@ class AttachContextAction extends Action2 { name: pick.symbolName!, isDynamic: true }); + } else if ('kind' in pick && pick.kind === 'tool') { + toAttach.push({ + id: pick.id, + name: pick.label, + fullName: pick.label, + value: undefined, + icon: pick.icon, + isTool: true + }); } else { // All other dynamic variables and static variables toAttach.push({ @@ -184,6 +268,8 @@ class AttachContextAction extends Action2 { const chatVariablesService = accessor.get(IChatVariablesService); const commandService = accessor.get(ICommandService); const widgetService = accessor.get(IChatWidgetService); + const languageModelToolsService = accessor.get(ILanguageModelToolsService); + const quickChatService = accessor.get(IQuickChatService); const context: { widget?: IChatWidget } | undefined = args[0]; const widget = context?.widget ?? widgetService.lastFocusedWidget; if (!widget) { @@ -193,12 +279,13 @@ class AttachContextAction extends Action2 { const usedAgent = widget.parsedInput.parts.find(p => p instanceof ChatRequestAgentPart); const slowSupported = usedAgent ? usedAgent.agent.metadata.supportsSlowVariables : true; const quickPickItems: (IChatContextQuickPickItem | QuickPickItem)[] = []; - for (const variable of chatVariablesService.getVariables()) { + for (const variable of chatVariablesService.getVariables(widget.location)) { if (variable.fullName && (!variable.isSlow || slowSupported)) { quickPickItems.push({ - label: `${variable.icon ? `$(${variable.icon.id}) ` : ''}${variable.fullName}`, + label: variable.fullName, name: variable.name, id: variable.id, + iconClass: variable.icon ? ThemeIcon.asClassName(variable.icon) : undefined, icon: variable.icon }); } @@ -211,10 +298,11 @@ class AttachContextAction extends Action2 { for (const variable of completions) { if (variable.fullName) { quickPickItems.push({ - label: `${variable.icon ? `$(${variable.icon.id}) ` : ''}${variable.fullName}`, + label: variable.fullName, id: variable.id, command: variable.command, icon: variable.icon, + iconClass: variable.icon ? ThemeIcon.asClassName(variable.icon) : undefined, value: variable.value, isDynamic: true, name: variable.name @@ -222,19 +310,54 @@ class AttachContextAction extends Action2 { } } } + } + if (!usedAgent || usedAgent.agent.supportsToolReferences) { + for (const tool of languageModelToolsService.getTools()) { + if (tool.canBeInvokedManually) { + const item: IToolQuickPickItem = { + kind: 'tool', + label: tool.displayName ?? tool.name ?? '', + id: tool.id, + icon: ThemeIcon.isThemeIcon(tool.icon) ? tool.icon : undefined // TODO need to support icon path? + }; + if (ThemeIcon.isThemeIcon(tool.icon)) { + item.iconClass = ThemeIcon.asClassName(tool.icon); + } else if (tool.icon) { + item.iconPath = tool.icon; + } + + quickPickItems.push(item); + } + } } quickPickItems.push({ - label: localize('chatContext.symbol', '{0} Symbol...', `$(${Codicon.symbolField.id})`), + label: localize('chatContext.symbol', 'Symbol...'), icon: ThemeIcon.fromId(Codicon.symbolField.id), + iconClass: ThemeIcon.asClassName(Codicon.symbolField), prefix: SymbolsQuickAccessProvider.PREFIX }); - this._show(quickInputService, commandService, widget, quickPickItems); + function extractTextFromIconLabel(label: string | undefined): string { + if (!label) { + return ''; + } + const match = label.match(/\$\([^\)]+\)\s*(.+)/); + return match ? match[1] : label; + } + + this._show(quickInputService, commandService, widget, quickChatService, quickPickItems.sort(function (a, b) { + + const first = extractTextFromIconLabel(a.label).toUpperCase(); + const second = extractTextFromIconLabel(b.label).toUpperCase(); + + return compare(first, second); + })); } - private _show(quickInputService: IQuickInputService, commandService: ICommandService, widget: IChatWidget, quickPickItems: (IChatContextQuickPickItem | QuickPickItem)[], query: string = '') { + private _show(quickInputService: IQuickInputService, commandService: ICommandService, widget: IChatWidget, quickChatService: IQuickChatService, quickPickItems: (IChatContextQuickPickItem | QuickPickItem)[], query: string = '') { + quickInputService.quickAccess.show(query, { enabledProviderPrefixes: [ AnythingQuickAccessProvider.PREFIX, @@ -245,9 +368,12 @@ class AttachContextAction extends Action2 { providerOptions: { handleAccept: (item: IChatContextQuickPickItem) => { if ('prefix' in item) { - this._show(quickInputService, commandService, widget, quickPickItems, item.prefix); + this._show(quickInputService, commandService, widget, quickChatService, quickPickItems, item.prefix); } else { this._attachContext(widget, commandService, item); + if (isQuickChat(widget)) { + quickChatService.open(); + } } }, additionPicks: quickPickItems, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts new file mode 100644 index 00000000..4ab735e2 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatDeveloperActions.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from 'vs/base/common/codicons'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { localize2 } from 'vs/nls'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; + +export function registerChatDeveloperActions() { + registerAction2(LogChatInputHistoryAction); +} + +class LogChatInputHistoryAction extends Action2 { + + static readonly ID = 'workbench.action.chat.logInputHistory'; + + constructor() { + super({ + id: LogChatInputHistoryAction.ID, + title: localize2('workbench.action.chat.logInputHistory.label', "Log Chat Input History"), + icon: Codicon.attach, + category: Categories.Developer, + f1: true + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const chatWidgetService = accessor.get(IChatWidgetService); + chatWidgetService.lastFocusedWidget?.logInputHistory(); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 5a6574db..b7d76a5a 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -161,6 +161,7 @@ export class CancelAction extends Action2 { keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Escape, + win: { primary: KeyMod.Alt | KeyCode.Backspace }, } }); } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts index b4d1afff..977cf2f7 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts @@ -96,7 +96,8 @@ export function registerChatExportActions() { throw new Error('Invalid chat session data'); } - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { data }, pinned: true } as IChatEditorOptions }); + const options: IChatEditorOptions = { target: { data }, pinned: true }; + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }); } catch (err) { throw err; } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 195ab2b3..0f960b81 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -92,12 +92,11 @@ export function registerMoveActions() { async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNewLocation, chatView?: ChatViewPane) { const widgetService = accessor.get(IChatWidgetService); - const viewService = accessor.get(IViewsService); const editorService = accessor.get(IEditorService); const widget = chatView?.widget ?? widgetService.lastFocusedWidget; if (!widget || !('viewId' in widget.viewContext)) { - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); return; } @@ -107,11 +106,11 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew } const sessionId = viewModel.sessionId; - const view = await viewService.openView(widget.viewContext.viewId) as ChatViewPane; - const viewState = view.widget.getViewState(); - view.clear(); + const viewState = widget.getViewState(); + widget.clear(); - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId }, pinned: true, viewState: viewState } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + const options: IChatEditorOptions = { target: { sessionId }, pinned: true, viewState: viewState }; + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); } async function moveToSidebar(accessor: ServicesAccessor): Promise { @@ -120,11 +119,14 @@ async function moveToSidebar(accessor: ServicesAccessor): Promise { const editorGroupService = accessor.get(IEditorGroupsService); const chatEditorInput = editorService.activeEditor; + let view: ChatViewPane; if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId) { await editorService.closeEditor({ editor: chatEditorInput, groupId: editorGroupService.activeGroup.id }); - const view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; + view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; view.loadSession(chatEditorInput.sessionId); } else { - await viewsService.openView(CHAT_VIEW_ID); + view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; } + + view.focus(); } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 4a2455ea..9b144077 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -15,14 +15,17 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatService, ChatAgentVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE, CONTEXT_VOTE_UP_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { IChatService, ChatAgentVoteDirection, ChatAgentVoteDownReason } from 'vs/workbench/contrib/chat/common/chatService'; import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { MENU_INLINE_CHAT_WIDGET_SECONDARY } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellEditType, CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +export const MarkUnhelpfulActionId = 'workbench.action.chat.markUnhelpful'; + export function registerChatTitleActions() { registerAction2(class MarkHelpfulAction extends Action2 { constructor() { @@ -33,12 +36,17 @@ export function registerChatTitleActions() { category: CHAT_CATEGORY, icon: Codicon.thumbsup, toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('up'), - menu: { + menu: [{ id: MenuId.ChatMessageTitle, group: 'navigation', order: 1, - when: CONTEXT_RESPONSE - } + when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_VOTE_UP_ENABLED, CONTEXT_RESPONSE_ERROR.negate()) + }, { + id: MENU_INLINE_CHAT_WIDGET_SECONDARY, + group: 'navigation', + order: 1, + when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_VOTE_UP_ENABLED, CONTEXT_RESPONSE_ERROR.negate()) + }] }); } @@ -51,33 +59,41 @@ export function registerChatTitleActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ agentId: item.agent?.id, + command: item.slashCommand?.name, sessionId: item.sessionId, requestId: item.requestId, result: item.result, action: { kind: 'vote', direction: ChatAgentVoteDirection.Up, + reason: undefined } }); item.setVote(ChatAgentVoteDirection.Up); + item.setVoteDownReason(undefined); } }); registerAction2(class MarkUnhelpfulAction extends Action2 { constructor() { super({ - id: 'workbench.action.chat.markUnhelpful', + id: MarkUnhelpfulActionId, title: localize2('interactive.unhelpful.label', "Unhelpful"), f1: false, category: CHAT_CATEGORY, icon: Codicon.thumbsdown, toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('down'), - menu: { + menu: [{ id: MenuId.ChatMessageTitle, group: 'navigation', order: 2, - when: CONTEXT_RESPONSE - } + when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR.negate()) + }, { + id: MENU_INLINE_CHAT_WIDGET_SECONDARY, + group: 'navigation', + order: 2, + when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR.negate()) + }] }); } @@ -87,18 +103,27 @@ export function registerChatTitleActions() { return; } + const reason = args[1]; + if (typeof reason !== 'string') { + return; + } + + item.setVote(ChatAgentVoteDirection.Down); + item.setVoteDownReason(reason as ChatAgentVoteDownReason); + const chatService = accessor.get(IChatService); chatService.notifyUserAction({ agentId: item.agent?.id, + command: item.slashCommand?.name, sessionId: item.sessionId, requestId: item.requestId, result: item.result, action: { kind: 'vote', direction: ChatAgentVoteDirection.Down, + reason: item.voteDownReason } }); - item.setVote(ChatAgentVoteDirection.Down); } }); @@ -110,12 +135,17 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.report, - menu: { + menu: [{ id: MenuId.ChatMessageTitle, group: 'navigation', order: 3, when: ContextKeyExpr.and(CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE) - } + }, { + id: MENU_INLINE_CHAT_WIDGET_SECONDARY, + group: 'navigation', + order: 3, + when: ContextKeyExpr.and(CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE) + }] }); } @@ -128,6 +158,7 @@ export function registerChatTitleActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ agentId: item.agent?.id, + command: item.slashCommand?.name, sessionId: item.sessionId, requestId: item.requestId, result: item.result, @@ -174,7 +205,7 @@ export function registerChatTitleActions() { return; } - const value = item.response.asString(); + const value = item.response.toString(); const splitContents = splitMarkdownAndCodeBlocks(value); const focusRange = notebookEditor.getFocus(); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 7d45fc86..be6c4955 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -22,7 +22,9 @@ import { ChatAccessibilityHelp } from 'vs/workbench/contrib/chat/browser/actions import { registerChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { ACTION_ID_NEW_CHAT, registerNewChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; import { registerChatCodeBlockActions, registerChatCodeCompareBlockActions } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; +import { registerChatContextActions } from 'vs/workbench/contrib/chat/browser/actions/chatContextActions'; import { registerChatCopyActions } from 'vs/workbench/contrib/chat/browser/actions/chatCopyActions'; +import { registerChatDeveloperActions } from 'vs/workbench/contrib/chat/browser/actions/chatDeveloperActions'; import { SubmitAction, registerChatExecuteActions } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/actions/chatFileTreeActions'; import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport'; @@ -34,15 +36,16 @@ import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { agentSlashCommandToMarkdown, agentToMarkdown } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; -import { ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; +import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { ChatResponseAccessibleView } from 'vs/workbench/contrib/chat/browser/chatResponseAccessibleView'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/codeBlockContextProviderService'; -import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; import 'vs/workbench/contrib/chat/browser/contrib/chatContextAttachments'; import 'vs/workbench/contrib/chat/browser/contrib/chatInputCompletions'; +import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; +import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorHover'; import { ChatAgentLocation, ChatAgentNameService, ChatAgentService, IChatAgentNameService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -52,11 +55,12 @@ import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVari import { ChatWidgetHistoryService, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { ILanguageModelsService, LanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels'; import { ILanguageModelStatsService, LanguageModelStatsService } from 'vs/workbench/contrib/chat/common/languageModelStats'; +import { ILanguageModelToolsService, LanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; +import { LanguageModelToolsExtensionPointHandler } from 'vs/workbench/contrib/chat/common/tools/languageModelToolsContribution'; import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChatService'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import '../common/chatColors'; -import { registerChatContextActions } from 'vs/workbench/contrib/chat/browser/actions/chatContextActions'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -97,10 +101,28 @@ configurationRegistry.registerConfiguration({ deprecated: true, default: false }, + 'chat.experimental.variables.editor': { + type: 'boolean', + description: nls.localize('chat.experimental.variables.editor', "Enables variables for editor chat."), + default: true + }, + 'chat.experimental.variables.notebook': { + type: 'boolean', + description: nls.localize('chat.experimental.variables.notebook', "Enables variables for notebook chat."), + default: false + }, + 'chat.experimental.variables.terminal': { + type: 'boolean', + description: nls.localize('chat.experimental.variables.terminal', "Enables variables for terminal chat."), + default: false + }, + 'chat.experimental.detectParticipant.enabled': { + type: 'boolean', + description: nls.localize('chat.experimental.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), + default: null + }, } }); - - Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( ChatEditor, @@ -159,7 +181,8 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { command: 'clear', detail: nls.localize('clear', "Start a new chat"), sortText: 'z2_clear', - executeImmediately: true + executeImmediately: true, + locations: [ChatAgentLocation.Panel] }, async () => { commandService.executeCommand(ACTION_ID_NEW_CHAT); })); @@ -167,7 +190,8 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { command: 'help', detail: '', sortText: 'z1_help', - executeImmediately: true + executeImmediately: true, + locations: [ChatAgentLocation.Panel] }, async (prompt, progress) => { const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Panel); const agents = chatAgentService.getAgents(); @@ -209,7 +233,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { } const variables = [ - ...chatVariablesService.getVariables(), + ...chatVariablesService.getVariables(ChatAgentLocation.Panel), { name: 'file', description: nls.localize('file', "Choose a file in the workspace") } ]; const variableText = variables @@ -236,6 +260,8 @@ registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribu workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlashCommandsContribution, LifecyclePhase.Eventually); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); +registerWorkbenchContribution2(LanguageModelToolsExtensionPointHandler.ID, LanguageModelToolsExtensionPointHandler, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); registerChatActions(); registerChatCopyActions(); @@ -249,6 +275,7 @@ registerChatExportActions(); registerMoveActions(); registerNewChatActions(); registerChatContextActions(); +registerChatDeveloperActions(); registerSingleton(IChatService, ChatService, InstantiationType.Delayed); registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed); @@ -261,5 +288,6 @@ registerSingleton(IChatSlashCommandService, ChatSlashCommandService, Instantiati registerSingleton(IChatAgentService, ChatAgentService, InstantiationType.Delayed); registerSingleton(IChatAgentNameService, ChatAgentNameService, InstantiationType.Delayed); registerSingleton(IChatVariablesService, ChatVariablesService, InstantiationType.Delayed); +registerSingleton(ILanguageModelToolsService, LanguageModelToolsService, InstantiationType.Delayed); registerSingleton(IVoiceChatService, VoiceChatService, InstantiationType.Delayed); registerSingleton(IChatCodeBlockContextProviderService, ChatCodeBlockContextProviderService, InstantiationType.Delayed); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chat.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chat.ts index 48844b39..52729be2 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chat.ts @@ -13,7 +13,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; -import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { IChatViewState, IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatRequestVariableEntry, IChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -34,7 +34,6 @@ export interface IChatWidgetService { readonly lastFocusedWidget: IChatWidget | undefined; getWidgetByInputUri(uri: URI): IChatWidget | undefined; - getWidgetBySessionId(sessionId: string): IChatWidget | undefined; } @@ -79,7 +78,8 @@ export interface IChatAccessibilityService { export interface IChatCodeBlockInfo { codeBlockIndex: number; - element: IChatResponseViewModel; + element: ChatTreeItem; + uri: URI | undefined; focus(): void; } @@ -92,7 +92,7 @@ export interface IChatFileTreeInfo { export type ChatTreeItem = IChatRequestViewModel | IChatResponseViewModel | IChatWelcomeMessageViewModel; export interface IChatListItemRendererOptions { - readonly renderStyle?: 'default' | 'compact'; + readonly renderStyle?: 'default' | 'compact' | 'minimal'; readonly noHeader?: boolean; readonly noPadding?: boolean; readonly editableCodeBlock?: boolean; @@ -102,13 +102,22 @@ export interface IChatListItemRendererOptions { export interface IChatWidgetViewOptions { renderInputOnTop?: boolean; renderFollowups?: boolean; - renderStyle?: 'default' | 'compact'; + renderStyle?: 'default' | 'compact' | 'minimal'; supportsFileReferences?: boolean; filter?: (item: ChatTreeItem) => boolean; rendererOptions?: IChatListItemRendererOptions; menus?: { + /** + * The menu that is inside the input editor, use for send, dictation + */ executeToolbar?: MenuId; + /** + * The menu that next to the input editor, use for close, config etc + */ inputSideToolbar?: MenuId; + /** + * The telemetry source for all commands of this widget + */ telemetrySource?: string; }; defaultElementHeight?: number; @@ -120,18 +129,19 @@ export interface IChatViewViewContext { } export interface IChatResourceViewContext { - resource: boolean; + isQuickChat?: boolean; } -export type IChatWidgetViewContext = IChatViewViewContext | IChatResourceViewContext; +export type IChatWidgetViewContext = IChatViewViewContext | IChatResourceViewContext | {}; export interface IChatWidget { readonly onDidChangeViewModel: Event; readonly onDidAcceptInput: Event; readonly onDidHide: Event; readonly onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>; + readonly onDidChangeAgent: Event<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>; readonly onDidChangeParsedInput: Event; - readonly onDidDeleteContext: Event; + readonly onDidChangeContext: Event<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }>; readonly location: ChatAgentLocation; readonly viewContext: IChatWidgetViewContext; readonly viewModel: IChatViewModel | undefined; @@ -144,10 +154,11 @@ export interface IChatWidget { getContrib(id: string): T | undefined; reveal(item: ChatTreeItem): void; focus(item: ChatTreeItem): void; - moveFocus(item: ChatTreeItem, type: 'next' | 'previous'): void; + getSibling(item: ChatTreeItem, type: 'next' | 'previous'): ChatTreeItem | undefined; getFocus(): ChatTreeItem | undefined; setInput(query?: string): void; getInput(): string; + logInputHistory(): void; acceptInput(query?: string): Promise; acceptInputWithPrefix(prefix: string): void; setInputPlaceholder(placeholder: string): void; @@ -161,10 +172,7 @@ export interface IChatWidget { getLastFocusedFileTreeForResponse(response: IChatResponseViewModel): IChatFileTreeInfo | undefined; setContext(overwrite: boolean, ...context: IChatRequestVariableEntry[]): void; clear(): void; -} - -export interface IChatViewPane { - clear(): void; + getViewState(): IChatViewState; } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts index 5eb8edf7..ecfaa165 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts @@ -62,16 +62,16 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider token.type === 'code')?.length ?? 0; + const codeBlockCount = marked.lexer(element.response.toString()).filter(token => token.type === 'code')?.length ?? 0; switch (codeBlockCount) { case 0: - label = accessibleViewHint ? localize('noCodeBlocksHint', "{0} {1} {2}", fileTreeCountHint, element.response.asString(), accessibleViewHint) : localize('noCodeBlocks', "{0} {1}", fileTreeCountHint, element.response.asString()); + label = accessibleViewHint ? localize('noCodeBlocksHint', "{0} {1} {2}", fileTreeCountHint, element.response.toString(), accessibleViewHint) : localize('noCodeBlocks', "{0} {1}", fileTreeCountHint, element.response.toString()); break; case 1: - label = accessibleViewHint ? localize('singleCodeBlockHint', "{0} 1 code block: {1} {2}", fileTreeCountHint, element.response.asString(), accessibleViewHint) : localize('singleCodeBlock', "{0} 1 code block: {1}", fileTreeCountHint, element.response.asString()); + label = accessibleViewHint ? localize('singleCodeBlockHint', "{0} 1 code block: {1} {2}", fileTreeCountHint, element.response.toString(), accessibleViewHint) : localize('singleCodeBlock', "{0} 1 code block: {1}", fileTreeCountHint, element.response.toString()); break; default: - label = accessibleViewHint ? localize('multiCodeBlockHint', "{0} {1} code blocks: {2}", fileTreeCountHint, codeBlockCount, element.response.asString(), accessibleViewHint) : localize('multiCodeBlock', "{0} {1} code blocks", fileTreeCountHint, codeBlockCount, element.response.asString()); + label = accessibleViewHint ? localize('multiCodeBlockHint', "{0} {1} code blocks: {2}", fileTreeCountHint, codeBlockCount, element.response.toString(), accessibleViewHint) : localize('multiCodeBlock', "{0} {1} code blocks", fileTreeCountHint, codeBlockCount, element.response.toString()); break; } return label; diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index d35b283a..17b9a5e9 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -12,6 +12,8 @@ import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/cha import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { AccessibilityVoiceSettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; const CHAT_RESPONSE_PENDING_ALLOWANCE_MS = 4000; export class ChatAccessibilityService extends Disposable implements IChatAccessibilityService { @@ -22,7 +24,11 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi private _requestId: number = 0; - constructor(@IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { + constructor( + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { super(); } acceptRequest(): number { @@ -34,14 +40,15 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi acceptResponse(response: IChatResponseViewModel | string | undefined, requestId: number): void { this._pendingSignalMap.deleteAndDispose(requestId); const isPanelChat = typeof response !== 'string'; - const responseContent = typeof response === 'string' ? response : response?.response.asString(); + const responseContent = typeof response === 'string' ? response : response?.response.toString(); this._accessibilitySignalService.playSignal(AccessibilitySignal.chatResponseReceived, { allowManyInParallel: true }); if (!response || !responseContent) { return; } const errorDetails = isPanelChat && response.errorDetails ? ` ${response.errorDetails.message}` : ''; const plainTextResponse = renderStringAsPlaintext(new MarkdownString(responseContent)); - status(plainTextResponse + errorDetails); + if (this._configurationService.getValue(AccessibilityVoiceSettingId.AutoSynthesize) !== 'on') { + status(plainTextResponse + errorDetails); + } } } - diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAgentHover.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAgentHover.ts index 9163fd24..78f34762 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAgentHover.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatAgentHover.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { h } from 'vs/base/browser/dom'; -import { IUpdatableHoverOptions } from 'vs/base/browser/ui/hover/hover'; +import { IManagedHoverOptions } from 'vs/base/browser/ui/hover/hover'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -29,6 +29,9 @@ export class ChatAgentHover extends Disposable { private readonly publisherName: HTMLElement; private readonly description: HTMLElement; + private readonly _onDidChangeContents = this._register(new Emitter()); + public readonly onDidChangeContents: Event = this._onDidChangeContents.event; + constructor( @IChatAgentService private readonly chatAgentService: IChatAgentService, @IExtensionsWorkbenchService private readonly extensionService: IExtensionsWorkbenchService, @@ -36,22 +39,22 @@ export class ChatAgentHover extends Disposable { ) { super(); - const hoverElement = h( + const hoverElement = dom.h( '.chat-agent-hover@root', [ - h('.chat-agent-hover-header', [ - h('.chat-agent-hover-icon@icon'), - h('.chat-agent-hover-details', [ - h('.chat-agent-hover-name@name'), - h('.chat-agent-hover-extension', [ - h('.chat-agent-hover-extension-name@extensionName'), - h('.chat-agent-hover-separator@separator'), - h('.chat-agent-hover-publisher@publisher'), + dom.h('.chat-agent-hover-header', [ + dom.h('.chat-agent-hover-icon@icon'), + dom.h('.chat-agent-hover-details', [ + dom.h('.chat-agent-hover-name@name'), + dom.h('.chat-agent-hover-extension', [ + dom.h('.chat-agent-hover-extension-name@extensionName'), + dom.h('.chat-agent-hover-separator@separator'), + dom.h('.chat-agent-hover-publisher@publisher'), ]), ]), ]), - h('.chat-agent-hover-warning@warning'), - h('span.chat-agent-hover-description@description'), + dom.h('.chat-agent-hover-warning@warning'), + dom.h('span.chat-agent-hover-description@description'), ]); this.domNode = hoverElement.root; @@ -110,13 +113,14 @@ export class ChatAgentHover extends Disposable { const extension = extensions[0]; if (extension?.publisherDomain?.verified) { this.domNode.classList.toggle('verifiedPublisher', true); + this._onDidChangeContents.fire(); } }); } } } -export function getChatAgentHoverOptions(getAgent: () => IChatAgentData | undefined, commandService: ICommandService): IUpdatableHoverOptions { +export function getChatAgentHoverOptions(getAgent: () => IChatAgentData | undefined, commandService: ICommandService): IManagedHoverOptions { return { actions: [ { diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts new file mode 100644 index 00000000..fc3c046e --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IChatRequestVariableEntry } from 'vs/workbench/contrib/chat/common/chatModel'; +import { Emitter } from 'vs/base/common/event'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ResourceLabels } from 'vs/workbench/browser/labels'; +import { URI } from 'vs/base/common/uri'; +import { FileKind } from 'vs/platform/files/common/files'; +import { Range } from 'vs/editor/common/core/range'; +import { basename, dirname } from 'vs/base/common/path'; +import { localize } from 'vs/nls'; +import { ChatResponseReferencePartStatusKind, IChatContentReference } from 'vs/workbench/contrib/chat/common/chatService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; + +export class ChatAttachmentsContentPart extends Disposable { + private readonly attachedContextDisposables = this._register(new DisposableStore()); + + private readonly _onDidChangeVisibility = this._register(new Emitter()); + private readonly _contextResourceLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event }); + + constructor( + private readonly variables: IChatRequestVariableEntry[], + private readonly contentReferences: ReadonlyArray = [], + public readonly domNode: HTMLElement = dom.$('.chat-attached-context'), + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IOpenerService private readonly openerService: IOpenerService, + ) { + super(); + + this.initAttachedContext(domNode); + } + + private initAttachedContext(container: HTMLElement) { + dom.clearNode(container); + this.attachedContextDisposables.clear(); + dom.setVisibility(Boolean(this.variables.length), this.domNode); + + this.variables.forEach((attachment) => { + const widget = dom.append(container, dom.$('.chat-attached-context-attachment.show-file-icons')); + const label = this._contextResourceLabels.create(widget, { supportIcons: true }); + const file = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; + const range = attachment.value && typeof attachment.value === 'object' && 'range' in attachment.value && Range.isIRange(attachment.value.range) ? attachment.value.range : undefined; + + const correspondingContentReference = this.contentReferences.find((ref) => typeof ref.reference === 'object' && 'variableName' in ref.reference && ref.reference.variableName === attachment.name); + const isAttachmentOmitted = correspondingContentReference?.options?.status?.kind === ChatResponseReferencePartStatusKind.Omitted; + const isAttachmentPartialOrOmitted = isAttachmentOmitted || correspondingContentReference?.options?.status?.kind === ChatResponseReferencePartStatusKind.Partial; + + if (file) { + const fileBasename = basename(file.path); + const fileDirname = dirname(file.path); + const friendlyName = `${fileBasename} ${fileDirname}`; + let ariaLabel; + if (isAttachmentOmitted) { + ariaLabel = range ? localize('chat.omittedFileAttachmentWithRange', "Omitted: {0}, line {1} to line {2}.", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.omittedFileAttachment', "Omitted: {0}.", friendlyName); + } else if (isAttachmentPartialOrOmitted) { + ariaLabel = range ? localize('chat.partialFileAttachmentWithRange', "Partially attached: {0}, line {1} to line {2}.", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.partialFileAttachment', "Partially attached: {0}.", friendlyName); + } else { + ariaLabel = range ? localize('chat.fileAttachmentWithRange3', "Attached: {0}, line {1} to line {2}.", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.fileAttachment3', "Attached: {0}.", friendlyName); + } + + label.setFile(file, { + fileKind: FileKind.FILE, + hidePath: true, + range, + title: correspondingContentReference?.options?.status?.description + }); + widget.ariaLabel = ariaLabel; + widget.tabIndex = 0; + widget.style.cursor = 'pointer'; + + this.attachedContextDisposables.add(dom.addDisposableListener(widget, dom.EventType.CLICK, async (e: MouseEvent) => { + dom.EventHelper.stop(e, true); + if (file) { + this.openerService.open( + file, + { + fromUserGesture: true, + editorOptions: { + selection: range + } as any + }); + } + })); + } else { + const attachmentLabel = attachment.fullName ?? attachment.name; + const withIcon = attachment.icon?.id ? `$(${attachment.icon.id}) ${attachmentLabel}` : attachmentLabel; + label.setLabel(withIcon, correspondingContentReference?.options?.status?.description); + + widget.ariaLabel = localize('chat.attachment3', "Attached context: {0}.", attachment.name); + widget.tabIndex = 0; + } + + if (isAttachmentPartialOrOmitted) { + widget.classList.add('warning'); + } + const description = correspondingContentReference?.options?.status?.description; + if (isAttachmentPartialOrOmitted) { + widget.ariaLabel = `${widget.ariaLabel}${description ? ` ${description}` : ''}`; + for (const selector of ['.monaco-icon-suffix-container', '.monaco-icon-name-container']) { + const element = label.element.querySelector(selector); + if (element) { + element.classList.add('warning'); + } + } + } + }); + } +} + diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCodeCitationContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCodeCitationContentPart.ts new file mode 100644 index 00000000..fedc276b --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCodeCitationContentPart.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatContentPart, IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { getCodeCitationsMessage } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatCodeCitations, IChatRendererContent } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +type ChatCodeCitationOpenedClassification = { + owner: 'roblourens'; + comment: 'Indicates when a user opens chat code citations'; +}; + +export class ChatCodeCitationContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + + constructor( + citations: IChatCodeCitations, + context: IChatContentPartRenderContext, + @IEditorService private readonly editorService: IEditorService, + @ITelemetryService private readonly telemetryService: ITelemetryService + ) { + super(); + + const label = getCodeCitationsMessage(citations.citations); + const elements = dom.h('.chat-code-citation-message@root', [ + dom.h('span.chat-code-citation-label@label'), + dom.h('.chat-code-citation-button-container@button'), + ]); + elements.label.textContent = label + ' - '; + const button = this._register(new Button(elements.button, { + buttonBackground: undefined, + buttonBorder: undefined, + buttonForeground: undefined, + buttonHoverBackground: undefined, + buttonSecondaryBackground: undefined, + buttonSecondaryForeground: undefined, + buttonSecondaryHoverBackground: undefined, + buttonSeparator: undefined + })); + button.label = localize('viewMatches', "View matches"); + this._register(button.onDidClick(() => { + const citationText = `# Code Citations\n\n` + citations.citations.map(c => `## License: ${c.license}\n${c.value.toString()}\n\n\`\`\`\n${c.snippet}\n\`\`\`\n\n`).join('\n'); + this.editorService.openEditor({ resource: undefined, contents: citationText, languageId: 'markdown' }); + this.telemetryService.publicLog2<{}, ChatCodeCitationOpenedClassification>('openedChatCodeCitations'); + })); + this.domNode = elements.root; + } + + hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { + return other.kind === 'codeCitations'; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollections.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollections.ts new file mode 100644 index 00000000..d13bbdff --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollections.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; + +export class ResourcePool extends Disposable { + private readonly pool: T[] = []; + + private _inUse = new Set; + public get inUse(): ReadonlySet { + return this._inUse; + } + + constructor( + private readonly _itemFactory: () => T, + ) { + super(); + } + + get(): T { + if (this.pool.length > 0) { + const item = this.pool.pop()!; + this._inUse.add(item); + return item; + } + + const item = this._register(this._itemFactory()); + this._inUse.add(item); + return item; + } + + release(item: T): void { + this._inUse.delete(item); + this.pool.push(item); + } +} + +export interface IDisposableReference extends IDisposable { + object: T; + isStale: () => boolean; +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCommandContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCommandContentPart.ts new file mode 100644 index 00000000..3893117f --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatCommandContentPart.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IChatContentPart, IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; +import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; + +const $ = dom.$; + +export class ChatCommandButtonContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + + constructor( + commandButton: IChatCommandButton, + context: IChatContentPartRenderContext, + @ICommandService private readonly commandService: ICommandService + ) { + super(); + + this.domNode = $('.chat-command-button'); + const enabled = !isResponseVM(context.element) || !context.element.isStale; + const tooltip = enabled ? + commandButton.command.tooltip : + localize('commandButtonDisabled', "Button not available in restored chat"); + const button = this._register(new Button(this.domNode, { ...defaultButtonStyles, supportIcons: true, title: tooltip })); + button.label = commandButton.command.title; + button.enabled = enabled; + + // TODO still need telemetry for command buttons + this._register(button.onDidClick(() => this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? [])))); + } + + hasSameContent(other: IChatProgressRenderableResponseContent): boolean { + // No other change allowed for this content type + return other.kind === 'command'; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts new file mode 100644 index 00000000..a3957178 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ChatConfirmationWidget } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget'; +import { IChatContentPart, IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatConfirmation, IChatSendRequestOptions, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; + +export class ChatConfirmationContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + + private readonly _onDidChangeHeight = this._register(new Emitter()); + public readonly onDidChangeHeight = this._onDidChangeHeight.event; + + constructor( + confirmation: IChatConfirmation, + context: IChatContentPartRenderContext, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IChatService private readonly chatService: IChatService, + ) { + super(); + + const element = context.element; + const buttons = confirmation.buttons + ? confirmation.buttons.map(button => ({ + label: button, + data: confirmation.data + })) + : [ + { label: localize('accept', "Accept"), data: confirmation.data }, + { label: localize('dismiss', "Dismiss"), data: confirmation.data, isSecondary: true }, + ]; + const confirmationWidget = this._register(this.instantiationService.createInstance(ChatConfirmationWidget, confirmation.title, confirmation.message, buttons)); + confirmationWidget.setShowButtons(!confirmation.isUsed); + + this._register(confirmationWidget.onDidClick(async e => { + if (isResponseVM(element)) { + const prompt = `${e.label}: "${confirmation.title}"`; + const data: IChatSendRequestOptions = e.isSecondary ? + { rejectedConfirmationData: [e.data] } : + { acceptedConfirmationData: [e.data] }; + data.agentId = element.agent?.id; + data.slashCommand = element.slashCommand?.name; + data.confirmation = e.label; + if (await this.chatService.sendRequest(element.sessionId, prompt, data)) { + confirmation.isUsed = true; + confirmationWidget.setShowButtons(false); + this._onDidChangeHeight.fire(); + } + } + })); + + this.domNode = confirmationWidget.domNode; + } + + hasSameContent(other: IChatProgressRenderableResponseContent): boolean { + // No other change allowed for this content type + return other.kind === 'confirmation'; + } + + addDisposable(disposable: IDisposable): void { + this._register(disposable); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatConfirmationWidget.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts similarity index 100% rename from patched-vscode/src/vs/workbench/contrib/chat/browser/chatConfirmationWidget.ts rename to patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts.ts new file mode 100644 index 00000000..80e3c44a --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable } from 'vs/base/common/lifecycle'; +import { ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatRendererContent } from 'vs/workbench/contrib/chat/common/chatViewModel'; + +export interface IChatContentPart extends IDisposable { + domNode: HTMLElement; + + /** + * Returns true if the other content is equivalent to what is already rendered in this content part. + * Returns false if a rerender is needed. + * followingContent is all the content that will be rendered after this content part (to support progress messages' behavior). + */ + hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean; +} + +export interface IChatContentPartRenderContext { + element: ChatTreeItem; + index: number; + content: ReadonlyArray; + preceedingContentParts: ReadonlyArray; +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts new file mode 100644 index 00000000..29a25733 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -0,0 +1,176 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Emitter } from 'vs/base/common/event'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { equalsIgnoreCase } from 'vs/base/common/strings'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; +import { Range } from 'vs/editor/common/core/range'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IChatCodeBlockInfo, IChatListItemRendererOptions } from 'vs/workbench/contrib/chat/browser/chat'; +import { IDisposableReference, ResourcePool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatCollections'; +import { IChatContentPart, IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { ChatMarkdownDecorationsRenderer } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; +import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; +import { CodeBlockPart, ICodeBlockData, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { IMarkdownVulnerability } from 'vs/workbench/contrib/chat/common/annotations'; +import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; +import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; + +const $ = dom.$; + +export class ChatMarkdownContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + private readonly allRefs: IDisposableReference[] = []; + + private readonly _onDidChangeHeight = this._register(new Emitter()); + public readonly onDidChangeHeight = this._onDidChangeHeight.event; + + public readonly codeblocks: IChatCodeBlockInfo[] = []; + + constructor( + private readonly markdown: IMarkdownString, + context: IChatContentPartRenderContext, + private readonly editorPool: EditorPool, + fillInIncompleteTokens = false, + codeBlockStartIndex = 0, + renderer: MarkdownRenderer, + currentWidth: number, + private readonly codeBlockModelCollection: CodeBlockModelCollection, + rendererOptions: IChatListItemRendererOptions, + @IContextKeyService contextKeyService: IContextKeyService, + @ITextModelService private readonly textModelService: ITextModelService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + + const element = context.element; + const markdownDecorationsRenderer = instantiationService.createInstance(ChatMarkdownDecorationsRenderer); + + // We release editors in order so that it's more likely that the same editor will be assigned if this element is re-rendered right away, like it often is during progressive rendering + const orderedDisposablesList: IDisposable[] = []; + let codeBlockIndex = codeBlockStartIndex; + const result = this._register(renderer.render(markdown, { + fillInIncompleteTokens, + codeBlockRendererSync: (languageId, text) => { + const index = codeBlockIndex++; + let textModel: Promise; + let range: Range | undefined; + let vulns: readonly IMarkdownVulnerability[] | undefined; + if (equalsIgnoreCase(languageId, localFileLanguageId)) { + try { + const parsedBody = parseLocalFileData(text); + range = parsedBody.range && Range.lift(parsedBody.range); + textModel = this.textModelService.createModelReference(parsedBody.uri).then(ref => ref.object); + } catch (e) { + return $('div'); + } + } else { + if (!isRequestVM(element) && !isResponseVM(element)) { + console.error('Trying to render code block in welcome', element.id, index); + return $('div'); + } + + const sessionId = isResponseVM(element) || isRequestVM(element) ? element.sessionId : ''; + const modelEntry = this.codeBlockModelCollection.getOrCreate(sessionId, element, index); + vulns = modelEntry.vulns; + textModel = modelEntry.model; + } + + const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; + const ref = this.renderCodeBlock({ languageId, textModel, codeBlockIndex: index, element, range, hideToolbar, parentContextKeyService: contextKeyService, vulns }, text, currentWidth, rendererOptions.editableCodeBlock); + this.allRefs.push(ref); + + // Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping) + // not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render) + this._register(ref.object.onDidChangeContentHeight(() => this._onDidChangeHeight.fire())); + + const info: IChatCodeBlockInfo = { + codeBlockIndex: index, + element, + focus() { + ref.object.focus(); + }, + uri: ref.object.uri + }; + this.codeblocks.push(info); + orderedDisposablesList.push(ref); + return ref.object.element; + }, + asyncRenderCallback: () => this._onDidChangeHeight.fire(), + })); + + this._register(markdownDecorationsRenderer.walkTreeAndAnnotateReferenceLinks(result.element)); + + orderedDisposablesList.reverse().forEach(d => this._register(d)); + this.domNode = result.element; + } + + private renderCodeBlock(data: ICodeBlockData, text: string, currentWidth: number, editableCodeBlock: boolean | undefined): IDisposableReference { + const ref = this.editorPool.get(); + const editorInfo = ref.object; + if (isResponseVM(data.element)) { + this.codeBlockModelCollection.update(data.element.sessionId, data.element, data.codeBlockIndex, { text, languageId: data.languageId }); + } + + editorInfo.render(data, currentWidth, editableCodeBlock); + + return ref; + } + + hasSameContent(other: IChatProgressRenderableResponseContent): boolean { + return other.kind === 'markdownContent' && other.content.value === this.markdown.value; + } + + layout(width: number): void { + this.allRefs.forEach(ref => ref.object.layout(width)); + } + + addDisposable(disposable: IDisposable): void { + this._register(disposable); + } +} + +export class EditorPool extends Disposable { + + private readonly _pool: ResourcePool; + + public inUse(): Iterable { + return this._pool.inUse; + } + + constructor( + options: ChatEditorOptions, + delegate: IChatRendererDelegate, + overflowWidgetsDomNode: HTMLElement | undefined, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + this._pool = this._register(new ResourcePool(() => { + return instantiationService.createInstance(CodeBlockPart, options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); + })); + } + + get(): IDisposableReference { + const codeBlock = this._pool.get(); + let stale = false; + return { + object: codeBlock, + isStale: () => stale, + dispose: () => { + codeBlock.reset(); + stale = true; + this._pool.release(codeBlock); + } + }; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts new file mode 100644 index 00000000..6545fdfe --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { $ } from 'vs/base/browser/dom'; +import { alert } from 'vs/base/browser/ui/aria/aria'; +import { Codicon } from 'vs/base/common/codicons'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; +import { ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatContentPart, IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { IChatProgressMessage, IChatTask } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatRendererContent, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; + +export class ChatProgressContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + + private readonly showSpinner: boolean; + + constructor( + progress: IChatProgressMessage | IChatTask, + renderer: MarkdownRenderer, + context: IChatContentPartRenderContext, + forceShowSpinner?: boolean, + forceShowMessage?: boolean + ) { + super(); + + const followingContent = context.content.slice(context.index + 1); + this.showSpinner = forceShowSpinner ?? shouldShowSpinner(followingContent, context.element); + const hideMessage = forceShowMessage !== true && followingContent.some(part => part.kind !== 'progressMessage'); + if (hideMessage) { + // Placeholder, don't show the progress message + this.domNode = $(''); + return; + } + + if (this.showSpinner) { + // TODO@roblourens is this the right place for this? + // this step is in progress, communicate it to SR users + alert(progress.content.value); + } + const codicon = this.showSpinner ? ThemeIcon.modify(Codicon.loading, 'spin').id : Codicon.check.id; + const markdown = new MarkdownString(`$(${codicon}) ${progress.content.value}`, { + supportThemeIcons: true + }); + const result = this._register(renderer.render(markdown)); + result.element.classList.add('progress-step'); + + this.domNode = result.element; + } + + hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { + // Needs rerender when spinner state changes + const showSpinner = shouldShowSpinner(followingContent, element); + return other.kind === 'progressMessage' && this.showSpinner === showSpinner; + } +} + +function shouldShowSpinner(followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { + return isResponseVM(element) && !element.isComplete && followingContent.length === 0; +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts new file mode 100644 index 00000000..774d651a --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts @@ -0,0 +1,339 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { Codicon } from 'vs/base/common/codicons'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { matchesSomeScheme, Schemas } from 'vs/base/common/network'; +import { basename } from 'vs/base/common/path'; +import { basenameOrAuthority, isEqualAuthority } from 'vs/base/common/resources'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { FileKind } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; +import { ColorScheme } from 'vs/workbench/browser/web.api'; +import { ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; +import { IDisposableReference, ResourcePool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatCollections'; +import { IChatContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { ChatResponseReferencePartStatusKind, IChatContentReference, IChatWarningMessage } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatRendererContent, IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; +import { SETTINGS_AUTHORITY } from 'vs/workbench/services/preferences/common/preferences'; + +const $ = dom.$; + +export interface IChatReferenceListItem extends IChatContentReference { + title?: string; +} + +export type IChatCollapsibleListItem = IChatReferenceListItem | IChatWarningMessage; + +export class ChatCollapsibleListContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + + private readonly _onDidChangeHeight = this._register(new Emitter()); + public readonly onDidChangeHeight = this._onDidChangeHeight.event; + + constructor( + private readonly data: ReadonlyArray, + labelOverride: string | undefined, + element: IChatResponseViewModel, + contentReferencesListPool: CollapsibleListPool, + @IOpenerService openerService: IOpenerService, + ) { + super(); + + const referencesLabel = labelOverride ?? (data.length > 1 ? + localize('usedReferencesPlural', "Used {0} references", data.length) : + localize('usedReferencesSingular', "Used {0} reference", 1)); + const iconElement = $('.chat-used-context-icon'); + const icon = (element: IChatResponseViewModel) => element.usedReferencesExpanded ? Codicon.chevronDown : Codicon.chevronRight; + iconElement.classList.add(...ThemeIcon.asClassNameArray(icon(element))); + const buttonElement = $('.chat-used-context-label', undefined); + + const collapseButton = this._register(new Button(buttonElement, { + buttonBackground: undefined, + buttonBorder: undefined, + buttonForeground: undefined, + buttonHoverBackground: undefined, + buttonSecondaryBackground: undefined, + buttonSecondaryForeground: undefined, + buttonSecondaryHoverBackground: undefined, + buttonSeparator: undefined + })); + this.domNode = $('.chat-used-context', undefined, buttonElement); + collapseButton.label = referencesLabel; + collapseButton.element.prepend(iconElement); + this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); + this.domNode.classList.toggle('chat-used-context-collapsed', !element.usedReferencesExpanded); + this._register(collapseButton.onDidClick(() => { + iconElement.classList.remove(...ThemeIcon.asClassNameArray(icon(element))); + element.usedReferencesExpanded = !element.usedReferencesExpanded; + iconElement.classList.add(...ThemeIcon.asClassNameArray(icon(element))); + this.domNode.classList.toggle('chat-used-context-collapsed', !element.usedReferencesExpanded); + this._onDidChangeHeight.fire(); + this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); + })); + + const ref = this._register(contentReferencesListPool.get()); + const list = ref.object; + this.domNode.appendChild(list.getHTMLElement().parentElement!); + + this._register(list.onDidOpen((e) => { + if (e.element && 'reference' in e.element && typeof e.element.reference === 'object') { + const uriOrLocation = 'variableName' in e.element.reference ? e.element.reference.value : e.element.reference; + const uri = URI.isUri(uriOrLocation) ? uriOrLocation : + uriOrLocation?.uri; + if (uri) { + openerService.open( + uri, + { + fromUserGesture: true, + editorOptions: { + ...e.editorOptions, + ...{ + selection: uriOrLocation && 'range' in uriOrLocation ? uriOrLocation.range : undefined + } + } + }); + } + } + })); + this._register(list.onContextMenu((e) => { + e.browserEvent.preventDefault(); + e.browserEvent.stopPropagation(); + })); + + const maxItemsShown = 6; + const itemsShown = Math.min(data.length, maxItemsShown); + const height = itemsShown * 22; + list.layout(height); + list.getHTMLElement().style.height = `${height}px`; + list.splice(0, list.length, data); + } + + hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { + return other.kind === 'references' && other.references.length === this.data.length || + other.kind === 'codeCitations' && other.citations.length === this.data.length; + } + + private updateAriaLabel(element: HTMLElement, label: string, expanded?: boolean): void { + element.ariaLabel = expanded ? localize('usedReferencesExpanded', "{0}, expanded", label) : localize('usedReferencesCollapsed', "{0}, collapsed", label); + } + + addDisposable(disposable: IDisposable): void { + this._register(disposable); + } +} + +export class CollapsibleListPool extends Disposable { + private _pool: ResourcePool>; + + public get inUse(): ReadonlySet> { + return this._pool.inUse; + } + + constructor( + private _onDidChangeVisibility: Event, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IThemeService private readonly themeService: IThemeService, + ) { + super(); + this._pool = this._register(new ResourcePool(() => this.listFactory())); + } + + private listFactory(): WorkbenchList { + const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility })); + + const container = $('.chat-used-context-list'); + this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); + + const list = this.instantiationService.createInstance( + WorkbenchList, + 'ChatListRenderer', + container, + new CollapsibleListDelegate(), + [this.instantiationService.createInstance(CollapsibleListRenderer, resourceLabels)], + { + alwaysConsumeMouseWheel: false, + accessibilityProvider: { + getAriaLabel: (element: IChatCollapsibleListItem) => { + if (element.kind === 'warning') { + return element.content.value; + } + const reference = element.reference; + if (typeof reference === 'string') { + return reference; + } else if ('variableName' in reference) { + return reference.variableName; + } else if (URI.isUri(reference)) { + return basename(reference.path); + } else { + return basename(reference.uri.path); + } + }, + + getWidgetAriaLabel: () => localize('chatCollapsibleList', "Collapsible Chat List") + }, + dnd: { + getDragURI: (element: IChatCollapsibleListItem) => { + if (element.kind === 'warning') { + return null; + } + const { reference } = element; + if (typeof reference === 'string' || 'variableName' in reference) { + return null; + } else if (URI.isUri(reference)) { + return reference.toString(); + } else { + return reference.uri.toString(); + } + }, + dispose: () => { }, + onDragOver: () => false, + drop: () => { }, + }, + }); + + return list; + } + + get(): IDisposableReference> { + const object = this._pool.get(); + let stale = false; + return { + object, + isStale: () => stale, + dispose: () => { + stale = true; + this._pool.release(object); + } + }; + } +} + +class CollapsibleListDelegate implements IListVirtualDelegate { + getHeight(element: IChatCollapsibleListItem): number { + return 22; + } + + getTemplateId(element: IChatCollapsibleListItem): string { + return CollapsibleListRenderer.TEMPLATE_ID; + } +} + +interface ICollapsibleListTemplate { + label: IResourceLabel; + templateDisposables: IDisposable; +} + +class CollapsibleListRenderer implements IListRenderer { + static TEMPLATE_ID = 'chatCollapsibleListRenderer'; + readonly templateId: string = CollapsibleListRenderer.TEMPLATE_ID; + + constructor( + private labels: ResourceLabels, + @IThemeService private readonly themeService: IThemeService, + @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, + @IProductService private readonly productService: IProductService, + ) { } + + renderTemplate(container: HTMLElement): ICollapsibleListTemplate { + const templateDisposables = new DisposableStore(); + const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true, supportIcons: true })); + return { templateDisposables, label }; + } + + + private getReferenceIcon(data: IChatContentReference): URI | ThemeIcon | undefined { + if (ThemeIcon.isThemeIcon(data.iconPath)) { + return data.iconPath; + } else { + return this.themeService.getColorTheme().type === ColorScheme.DARK && data.iconPath?.dark + ? data.iconPath?.dark + : data.iconPath?.light; + } + } + + renderElement(data: IChatCollapsibleListItem, index: number, templateData: ICollapsibleListTemplate, height: number | undefined): void { + if (data.kind === 'warning') { + templateData.label.setResource({ name: data.content.value }, { icon: Codicon.warning }); + return; + } + + const reference = data.reference; + const icon = this.getReferenceIcon(data); + templateData.label.element.style.display = 'flex'; + if (typeof reference === 'object' && 'variableName' in reference) { + if (reference.value) { + const uri = URI.isUri(reference.value) ? reference.value : reference.value.uri; + templateData.label.setResource( + { + resource: uri, + name: basenameOrAuthority(uri), + description: `#${reference.variableName}`, + range: 'range' in reference.value ? reference.value.range : undefined, + }, { icon, title: data.options?.status?.description ?? data.title }); + } else { + const variable = this.chatVariablesService.getVariable(reference.variableName); + // This is a hack to get chat attachment ThemeIcons to render for resource labels + const asThemeIcon = variable?.icon ? `$(${variable.icon.id}) ` : ''; + const asVariableName = `#${reference.variableName}`; // Fallback, shouldn't really happen + const label = `${asThemeIcon}${variable?.fullName ?? asVariableName}`; + templateData.label.setLabel(label, asVariableName, { title: data.options?.status?.description ?? variable?.description }); + } + } else if (typeof reference === 'string') { + templateData.label.setLabel(reference, undefined, { iconPath: URI.isUri(icon) ? icon : undefined, title: data.options?.status?.description ?? data.title }); + + } else { + const uri = 'uri' in reference ? reference.uri : reference; + if (uri.scheme === 'https' && isEqualAuthority(uri.authority, 'github.com') && uri.path.includes('/tree/')) { + // Parse a nicer label for GitHub URIs that point at a particular commit + file + const label = uri.path.split('/').slice(1, 3).join('/'); + const description = uri.path.split('/').slice(5).join('/'); + templateData.label.setResource({ resource: uri, name: label, description }, { icon: Codicon.github, title: data.title }); + } else if (uri.scheme === this.productService.urlProtocol && isEqualAuthority(uri.authority, SETTINGS_AUTHORITY)) { + // a nicer label for settings URIs + const settingId = uri.path.substring(1); + templateData.label.setResource({ resource: uri, name: settingId }, { icon: Codicon.settingsGear, title: localize('setting.hover', "Open setting '{0}'", settingId) }); + } else if (matchesSomeScheme(uri, Schemas.mailto, Schemas.http, Schemas.https)) { + templateData.label.setResource({ resource: uri, name: uri.toString() }, { icon: icon ?? Codicon.globe, title: data.options?.status?.description ?? data.title ?? uri.toString() }); + } else { + templateData.label.setFile(uri, { + fileKind: FileKind.FILE, + // Should not have this live-updating data on a historical reference + fileDecorations: { badges: false, colors: false }, + range: 'range' in reference ? reference.range : undefined, + title: data.options?.status?.description ?? data.title + }); + } + } + + for (const selector of ['.monaco-icon-suffix-container', '.monaco-icon-name-container']) { + const element = templateData.label.element.querySelector(selector); + if (element) { + if (data.options?.status?.kind === ChatResponseReferencePartStatusKind.Omitted || data.options?.status?.kind === ChatResponseReferencePartStatusKind.Partial) { + element.classList.add('warning'); + } else { + element.classList.remove('warning'); + } + } + } + } + + disposeTemplate(templateData: ICollapsibleListTemplate): void { + templateData.templateDisposables.dispose(); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts new file mode 100644 index 00000000..8f376c69 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Event } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IChatContentPart, IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { ChatProgressContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart'; +import { ChatCollapsibleListContentPart, CollapsibleListPool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart'; +import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatTask } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; + +export class ChatTaskContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + public readonly onDidChangeHeight: Event; + + constructor( + private readonly task: IChatTask, + contentReferencesListPool: CollapsibleListPool, + renderer: MarkdownRenderer, + context: IChatContentPartRenderContext, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + + if (task.progress.length) { + const refsPart = this._register(instantiationService.createInstance(ChatCollapsibleListContentPart, task.progress, task.content.value, context.element as IChatResponseViewModel, contentReferencesListPool)); + this.domNode = dom.$('.chat-progress-task'); + this.domNode.appendChild(refsPart.domNode); + this.onDidChangeHeight = refsPart.onDidChangeHeight; + } else { + // #217645 + const isSettled = task.isSettled?.() ?? true; + const progressPart = this._register(instantiationService.createInstance(ChatProgressContentPart, task, renderer, context, !isSettled, true)); + this.domNode = progressPart.domNode; + this.onDidChangeHeight = Event.None; + } + } + + hasSameContent(other: IChatProgressRenderableResponseContent): boolean { + return other.kind === 'progressTask' + && other.progress.length === this.task.progress.length + && other.isSettled() === this.task.isSettled(); + } + + addDisposable(disposable: IDisposable): void { + this._register(disposable); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart.ts new file mode 100644 index 00000000..195da7f5 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart.ts @@ -0,0 +1,255 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IDisposable, IReference, RefCountedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { TextEdit } from 'vs/editor/common/languages'; +import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { IModelService } from 'vs/editor/common/services/model'; +import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { localize } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IChatListItemRendererOptions } from 'vs/workbench/contrib/chat/browser/chat'; +import { IDisposableReference, ResourcePool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatCollections'; +import { IChatContentPart, IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; +import { CodeCompareBlockPart, ICodeCompareBlockData, ICodeCompareBlockDiffData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { IChatProgressRenderableResponseContent, IChatTextEditGroup } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; + +const $ = dom.$; + +const ICodeCompareModelService = createDecorator('ICodeCompareModelService'); + +interface ICodeCompareModelService { + _serviceBrand: undefined; + createModel(response: IChatResponseViewModel, chatTextEdit: IChatTextEditGroup): Promise>; +} + +export class ChatTextEditContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + private readonly comparePart: IDisposableReference | undefined; + + private readonly _onDidChangeHeight = this._register(new Emitter()); + public readonly onDidChangeHeight = this._onDidChangeHeight.event; + + constructor( + chatTextEdit: IChatTextEditGroup, + context: IChatContentPartRenderContext, + rendererOptions: IChatListItemRendererOptions, + diffEditorPool: DiffEditorPool, + currentWidth: number, + @ICodeCompareModelService private readonly codeCompareModelService: ICodeCompareModelService + ) { + super(); + const element = context.element; + + assertType(isResponseVM(element)); + + // TODO@jrieken move this into the CompareCodeBlock and properly say what kind of changes happen + if (rendererOptions.renderTextEditsAsSummary?.(chatTextEdit.uri)) { + if (element.response.value.every(item => item.kind === 'textEditGroup')) { + this.domNode = $('.interactive-edits-summary', undefined, !element.isComplete + ? '' + : element.isCanceled + ? localize('edits0', "Making changes was aborted.") + : localize('editsSummary', "Made changes.")); + } else { + this.domNode = $('div'); + } + + // TODO@roblourens this case is now handled outside this Part in ChatListRenderer, but can it be cleaned up? + // return; + } else { + + + const cts = new CancellationTokenSource(); + + let isDisposed = false; + this._register(toDisposable(() => { + isDisposed = true; + cts.dispose(true); + })); + + this.comparePart = this._register(diffEditorPool.get()); + + // Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping) + // not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render) + this._register(this.comparePart.object.onDidChangeContentHeight(() => { + this._onDidChangeHeight.fire(); + })); + + const data: ICodeCompareBlockData = { + element, + edit: chatTextEdit, + diffData: (async () => { + + const ref = await this.codeCompareModelService.createModel(element, chatTextEdit); + + if (isDisposed) { + ref.dispose(); + return; + } + + this._register(ref); + + return { + modified: ref.object.modified.textEditorModel, + original: ref.object.original.textEditorModel, + originalSha1: ref.object.originalSha1 + } satisfies ICodeCompareBlockDiffData; + })() + }; + this.comparePart.object.render(data, currentWidth, cts.token); + + this.domNode = this.comparePart.object.element; + } + } + + layout(width: number): void { + this.comparePart?.object.layout(width); + } + + hasSameContent(other: IChatProgressRenderableResponseContent): boolean { + // No other change allowed for this content type + return other.kind === 'textEditGroup'; + } + + addDisposable(disposable: IDisposable): void { + this._register(disposable); + } +} + +export class DiffEditorPool extends Disposable { + + private readonly _pool: ResourcePool; + + public inUse(): Iterable { + return this._pool.inUse; + } + + constructor( + options: ChatEditorOptions, + delegate: IChatRendererDelegate, + overflowWidgetsDomNode: HTMLElement | undefined, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + this._pool = this._register(new ResourcePool(() => { + return instantiationService.createInstance(CodeCompareBlockPart, options, MenuId.ChatCompareBlock, delegate, overflowWidgetsDomNode); + })); + } + + get(): IDisposableReference { + const codeBlock = this._pool.get(); + let stale = false; + return { + object: codeBlock, + isStale: () => stale, + dispose: () => { + codeBlock.reset(); + stale = true; + this._pool.release(codeBlock); + } + }; + } +} + +class CodeCompareModelService implements ICodeCompareModelService { + + declare readonly _serviceBrand: undefined; + + constructor( + @ITextModelService private readonly textModelService: ITextModelService, + @IModelService private readonly modelService: IModelService, + @IChatService private readonly chatService: IChatService, + ) { } + + async createModel(element: IChatResponseViewModel, chatTextEdit: IChatTextEditGroup): Promise> { + + const original = await this.textModelService.createModelReference(chatTextEdit.uri); + + const modified = await this.textModelService.createModelReference((this.modelService.createModel( + createTextBufferFactoryFromSnapshot(original.object.textEditorModel.createSnapshot()), + { languageId: original.object.textEditorModel.getLanguageId(), onDidChange: Event.None }, + URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: chatTextEdit.uri.path, query: generateUuid() }), + false + )).uri); + + const d = new RefCountedDisposable(toDisposable(() => { + original.dispose(); + modified.dispose(); + })); + + // compute the sha1 of the original model + let originalSha1: string = ''; + if (chatTextEdit.state) { + originalSha1 = chatTextEdit.state.sha1; + } else { + const sha1 = new DefaultModelSHA1Computer(); + if (sha1.canComputeSHA1(original.object.textEditorModel)) { + originalSha1 = sha1.computeSHA1(original.object.textEditorModel); + chatTextEdit.state = { sha1: originalSha1, applied: 0 }; + } + } + + // apply edits to the "modified" model + const chatModel = this.chatService.getSession(element.sessionId)!; + const editGroups: ISingleEditOperation[][] = []; + for (const request of chatModel.getRequests()) { + if (!request.response) { + continue; + } + for (const item of request.response.response.value) { + if (item.kind !== 'textEditGroup' || item.state?.applied || !isEqual(item.uri, chatTextEdit.uri)) { + continue; + } + for (const group of item.edits) { + const edits = group.map(TextEdit.asEditOperation); + editGroups.push(edits); + } + } + if (request.response === element.model) { + break; + } + } + for (const edits of editGroups) { + modified.object.textEditorModel.pushEditOperations(null, edits, () => null); + } + + // self-acquire a reference to diff models for a short while + // because streaming usually means we will be using the original-model + // repeatedly and thereby also should reuse the modified-model and just + // update it with more edits + d.acquire(); + setTimeout(() => d.release(), 5000); + + return { + object: { + originalSha1, + original: original.object, + modified: modified.object + }, + dispose() { + d.release(); + }, + }; + } +} + +registerSingleton(ICodeCompareModelService, CodeCompareModelService, InstantiationType.Delayed); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts new file mode 100644 index 00000000..8742d2da --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts @@ -0,0 +1,225 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; +import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { FileKind, FileType } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; +import { ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; +import { IDisposableReference, ResourcePool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatCollections'; +import { IChatContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; +import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; + +const $ = dom.$; + +export class ChatTreeContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + + private readonly _onDidChangeHeight = this._register(new Emitter()); + public readonly onDidChangeHeight = this._onDidChangeHeight.event; + + public readonly onDidFocus: Event; + + private tree: WorkbenchCompressibleAsyncDataTree; + + constructor( + data: IChatResponseProgressFileTreeData, + element: ChatTreeItem, + treePool: TreePool, + treeDataIndex: number, + @IOpenerService private readonly openerService: IOpenerService + ) { + super(); + + const ref = this._register(treePool.get()); + this.tree = ref.object; + this.onDidFocus = this.tree.onDidFocus; + + this._register(this.tree.onDidOpen((e) => { + if (e.element && !('children' in e.element)) { + this.openerService.open(e.element.uri); + } + })); + this._register(this.tree.onDidChangeCollapseState(() => { + this._onDidChangeHeight.fire(); + })); + this._register(this.tree.onContextMenu((e) => { + e.browserEvent.preventDefault(); + e.browserEvent.stopPropagation(); + })); + + this.tree.setInput(data).then(() => { + if (!ref.isStale()) { + this.tree.layout(); + this._onDidChangeHeight.fire(); + } + }); + + this.domNode = this.tree.getHTMLElement().parentElement!; + } + + domFocus() { + this.tree.domFocus(); + } + + hasSameContent(other: IChatProgressRenderableResponseContent): boolean { + // No other change allowed for this content type + return other.kind === 'treeData'; + } + + addDisposable(disposable: IDisposable): void { + this._register(disposable); + } +} + +export class TreePool extends Disposable { + private _pool: ResourcePool>; + + public get inUse(): ReadonlySet> { + return this._pool.inUse; + } + + constructor( + private _onDidChangeVisibility: Event, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IConfigurationService private readonly configService: IConfigurationService, + @IThemeService private readonly themeService: IThemeService, + ) { + super(); + this._pool = this._register(new ResourcePool(() => this.treeFactory())); + } + + private treeFactory(): WorkbenchCompressibleAsyncDataTree { + const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility })); + + const container = $('.interactive-response-progress-tree'); + this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); + + const tree = this.instantiationService.createInstance( + WorkbenchCompressibleAsyncDataTree, + 'ChatListRenderer', + container, + new ChatListTreeDelegate(), + new ChatListTreeCompressionDelegate(), + [new ChatListTreeRenderer(resourceLabels, this.configService.getValue('explorer.decorations'))], + new ChatListTreeDataSource(), + { + collapseByDefault: () => false, + expandOnlyOnTwistieClick: () => false, + identityProvider: { + getId: (e: IChatResponseProgressFileTreeData) => e.uri.toString() + }, + accessibilityProvider: { + getAriaLabel: (element: IChatResponseProgressFileTreeData) => element.label, + getWidgetAriaLabel: () => localize('treeAriaLabel', "File Tree") + }, + alwaysConsumeMouseWheel: false + }); + + return tree; + } + + get(): IDisposableReference> { + const object = this._pool.get(); + let stale = false; + return { + object, + isStale: () => stale, + dispose: () => { + stale = true; + this._pool.release(object); + } + }; + } +} + +class ChatListTreeDelegate implements IListVirtualDelegate { + static readonly ITEM_HEIGHT = 22; + + getHeight(element: IChatResponseProgressFileTreeData): number { + return ChatListTreeDelegate.ITEM_HEIGHT; + } + + getTemplateId(element: IChatResponseProgressFileTreeData): string { + return 'chatListTreeTemplate'; + } +} + +class ChatListTreeCompressionDelegate implements ITreeCompressionDelegate { + isIncompressible(element: IChatResponseProgressFileTreeData): boolean { + return !element.children; + } +} + +interface IChatListTreeRendererTemplate { + templateDisposables: DisposableStore; + label: IResourceLabel; +} + +class ChatListTreeRenderer implements ICompressibleTreeRenderer { + templateId: string = 'chatListTreeTemplate'; + + constructor(private labels: ResourceLabels, private decorations: IFilesConfiguration['explorer']['decorations']) { } + + renderCompressedElements(element: ITreeNode, void>, index: number, templateData: IChatListTreeRendererTemplate, height: number | undefined): void { + templateData.label.element.style.display = 'flex'; + const label = element.element.elements.map((e) => e.label); + templateData.label.setResource({ resource: element.element.elements[0].uri, name: label }, { + title: element.element.elements[0].label, + fileKind: element.children ? FileKind.FOLDER : FileKind.FILE, + extraClasses: ['explorer-item'], + fileDecorations: this.decorations + }); + } + renderTemplate(container: HTMLElement): IChatListTreeRendererTemplate { + const templateDisposables = new DisposableStore(); + const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true })); + return { templateDisposables, label }; + } + renderElement(element: ITreeNode, index: number, templateData: IChatListTreeRendererTemplate, height: number | undefined): void { + templateData.label.element.style.display = 'flex'; + if (!element.children.length && element.element.type !== FileType.Directory) { + templateData.label.setFile(element.element.uri, { + fileKind: FileKind.FILE, + hidePath: true, + fileDecorations: this.decorations, + }); + } else { + templateData.label.setResource({ resource: element.element.uri, name: element.element.label }, { + title: element.element.label, + fileKind: FileKind.FOLDER, + fileDecorations: this.decorations + }); + } + } + disposeTemplate(templateData: IChatListTreeRendererTemplate): void { + templateData.templateDisposables.dispose(); + } +} + +class ChatListTreeDataSource implements IAsyncDataSource { + hasChildren(element: IChatResponseProgressFileTreeData): boolean { + return !!element.children; + } + + async getChildren(element: IChatResponseProgressFileTreeData): Promise> { + return element.children ?? []; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatWarningContentPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatWarningContentPart.ts new file mode 100644 index 00000000..3fd0b9fb --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/chatWarningContentPart.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Codicon } from 'vs/base/common/codicons'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; +import { IChatContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; + +const $ = dom.$; + +export class ChatWarningContentPart extends Disposable implements IChatContentPart { + public readonly domNode: HTMLElement; + + constructor( + kind: 'info' | 'warning' | 'error', + content: IMarkdownString, + renderer: MarkdownRenderer, + ) { + super(); + + this.domNode = $('.chat-notification-widget'); + let icon; + let iconClass; + switch (kind) { + case 'warning': + icon = Codicon.warning; + iconClass = '.chat-warning-codicon'; + break; + case 'error': + icon = Codicon.error; + iconClass = '.chat-error-codicon'; + break; + case 'info': + icon = Codicon.info; + iconClass = '.chat-info-codicon'; + break; + } + this.domNode.appendChild($(iconClass, undefined, renderIcon(icon))); + const markdownContent = renderer.render(content); + this.domNode.appendChild(markdownContent.element); + } + + hasSameContent(other: IChatProgressRenderableResponseContent): boolean { + // No other change allowed for this content type + return other.kind === 'warning'; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chatConfirmationWidget.css b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css similarity index 95% rename from patched-vscode/src/vs/workbench/contrib/chat/browser/media/chatConfirmationWidget.css rename to patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css index e244f077..bb9d7bc7 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chatConfirmationWidget.css +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css @@ -6,10 +6,13 @@ .chat-confirmation-widget { border: 1px solid var(--vscode-chat-requestBorder); border-radius: 4px; - margin-bottom: 16px; padding: 8px 12px 12px; } +.chat-confirmation-widget:not(:last-child) { + margin-bottom: 16px; +} + .chat-confirmation-widget .chat-confirmation-widget-title { font-weight: 600; } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 1da5d57b..3decf975 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -16,13 +16,13 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { Memento } from 'vs/workbench/common/memento'; -import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; -import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; -import { IChatModel, IExportableChatData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { clearChatEditor } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; +import { ChatWidget, IChatViewState } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel, IExportableChatData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { CHAT_PROVIDER_ID } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; export interface IChatEditorOptions extends IEditorOptions { target?: { sessionId: string } | { data: IExportableChatData | ISerializableChatData }; @@ -50,19 +50,21 @@ export class ChatEditor extends EditorPane { super(ChatEditorInput.EditorID, group, telemetryService, themeService, storageService); } - public async clear() { - return this.instantiationService.invokeFunction(clearChatEditor); + private async clear() { + if (this.input) { + return this.instantiationService.invokeFunction(clearChatEditor, this.input as ChatEditorInput); + } } protected override createEditor(parent: HTMLElement): void { this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); - const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); + const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); this.widget = this._register( scopedInstantiationService.createInstance( ChatWidget, ChatAgentLocation.Panel, - { resource: true }, + undefined, { supportsFileReferences: true }, { listForeground: editorForeground, @@ -75,7 +77,7 @@ export class ChatEditor extends EditorPane { this.widget.setVisible(true); } - protected override setEditorVisible(visible: boolean): void { + protected override setEditorVisible(visible: boolean): void { super.setEditorVisible(visible); this.widget?.setVisible(visible); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 4f59229f..4219221b 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -9,7 +9,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { formatChatQuestion } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; const $ = dom.$; @@ -36,18 +36,9 @@ export class ChatFollowups extends Disposable { return; } - let tooltipPrefix = ''; - if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent(this.location)?.id) { - const agent = this.chatAgentService.getAgent(followup.agentId); - if (!agent) { - // Refers to agent that doesn't exist - return; - } - - tooltipPrefix += `${chatAgentLeader}${agent.name} `; - if ('subCommand' in followup && followup.subCommand) { - tooltipPrefix += `${chatSubcommandLeader}${followup.subCommand} `; - } + const tooltipPrefix = formatChatQuestion(this.chatAgentService, this.location, '', followup.agentId, followup.subCommand); + if (tooltipPrefix === undefined) { + return; } const baseTitle = followup.kind === 'reply' ? diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 30e33eba..52d7d73b 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -6,14 +6,16 @@ import * as dom from 'vs/base/browser/dom'; import { DEFAULT_FONT_FAMILY } from 'vs/base/browser/fonts'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { Range } from 'vs/editor/common/core/range'; import { Button } from 'vs/base/browser/ui/button/button'; import { IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter } from 'vs/base/common/event'; -import { HistoryNavigator } from 'vs/base/common/history'; +import { HistoryNavigator2 } from 'vs/base/common/history'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { basename, dirname } from 'vs/base/common/path'; import { isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; @@ -21,9 +23,11 @@ import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IDimension } from 'vs/editor/common/core/dimension'; import { IPosition } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; +import { MarginHoverController } from 'vs/editor/contrib/hover/browser/marginHoverController'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; @@ -38,6 +42,7 @@ import { registerAndCreateHistoryNavigationContext } from 'vs/platform/history/b import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ResourceLabels } from 'vs/workbench/browser/labels'; @@ -52,10 +57,7 @@ import { IChatRequestVariableEntry } from 'vs/workbench/contrib/chat/common/chat import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IChatHistoryEntry, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; -import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { basename, dirname } from 'vs/base/common/path'; +import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEditorSelectionStyling } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; const $ = dom.$; @@ -88,13 +90,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidBlur = this._register(new Emitter()); readonly onDidBlur = this._onDidBlur.event; - private _onDidDeleteContext = this._register(new Emitter()); - readonly onDidDeleteContext = this._onDidDeleteContext.event; + private _onDidChangeContext = this._register(new Emitter<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }>()); + readonly onDidChangeContext = this._onDidChangeContext.event; private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>()); readonly onDidAcceptFollowup = this._onDidAcceptFollowup.event; - public get attachedContext() { + public get attachedContext(): ReadonlySet { return this._attachedContext; } @@ -130,10 +132,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return this._inputEditor; } - private history: HistoryNavigator; + private history: HistoryNavigator2; private historyNavigationBackwardsEnablement!: IContextKey; private historyNavigationForewardsEnablement!: IContextKey; - private onHistoryEntry = false; private inHistoryNavigation = false; private inputModel: ITextModel | undefined; private inputEditorHasText: IContextKey; @@ -149,6 +150,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // private readonly editorOptions: ChatEditorOptions, // TODO this should be used private readonly location: ChatAgentLocation, private readonly options: IChatInputPartOptions, + private readonly getInputState: () => any, @IChatWidgetHistoryService private readonly historyService: IChatWidgetHistoryService, @IModelService private readonly modelService: IModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -156,6 +158,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IConfigurationService private readonly configurationService: IConfigurationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @ILogService private readonly logService: ILogService, ) { super(); @@ -165,8 +168,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.chatCursorAtTop = CONTEXT_CHAT_INPUT_CURSOR_AT_TOP.bindTo(contextKeyService); this.inputEditorHasFocus = CONTEXT_CHAT_INPUT_HAS_FOCUS.bindTo(contextKeyService); - this.history = new HistoryNavigator([], 5); - this._register(this.historyService.onDidClearHistory(() => this.history.clear())); + this.history = this.loadHistory(); + this._register(this.historyService.onDidClearHistory(() => this.history = new HistoryNavigator2([{ text: '' }], 50, historyKeyFn))); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AccessibilityVerbositySettingId.Chat)) { @@ -175,6 +178,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); } + private loadHistory(): HistoryNavigator2 { + const history = this.historyService.getHistory(this.location); + if (history.length === 0) { + history.push({ text: '' }); + } + + return new HistoryNavigator2(history, 50, historyKeyFn); + } + private _getAriaLabel(): string { const verbose = this.configurationService.getValue(AccessibilityVerbositySettingId.Chat); if (verbose) { @@ -184,15 +196,37 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return localize('chatInput', "Chat Input"); } - setState(inputValue: string | undefined): void { - const history = this.historyService.getHistory(this.location); - this.history = new HistoryNavigator(history, 50); + updateState(inputState: Object): void { + if (this.inHistoryNavigation) { + return; + } + + const newEntry = { text: this._inputEditor.getValue(), state: inputState }; + + if (this.history.isAtEnd()) { + // The last history entry should always be the current input value + this.history.replaceLast(newEntry); + } else { + // Added a reference while in the middle of history navigation, it's a new entry + this.history.replaceLast(newEntry); + this.history.resetCursor(); + } + } + + initForNewChatModel(inputValue: string | undefined, inputState: Object): void { + this.history = this.loadHistory(); + this.history.add({ text: inputValue ?? this.history.current().text, state: inputState }); - if (typeof inputValue === 'string') { - this.setValue(inputValue); + if (inputValue) { + this.setValue(inputValue, false); } } + logInputHistory(): void { + const historyStr = [...this.history].map(entry => JSON.stringify(entry)).join('\n'); + this.logService.info(`[${this.location}] Chat input history:`, historyStr); + } + setVisible(visible: boolean): void { this._onDidChangeVisibility.fire(visible); } @@ -202,24 +236,41 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } showPreviousValue(): void { + const inputState = this.getInputState(); + if (this.history.isAtEnd()) { + this.saveCurrentValue(inputState); + } else { + if (!this.history.has({ text: this._inputEditor.getValue(), state: inputState })) { + this.saveCurrentValue(inputState); + this.history.resetCursor(); + } + } + this.navigateHistory(true); } showNextValue(): void { + const inputState = this.getInputState(); + if (this.history.isAtEnd()) { + return; + } else { + if (!this.history.has({ text: this._inputEditor.getValue(), state: inputState })) { + this.saveCurrentValue(inputState); + this.history.resetCursor(); + } + } + this.navigateHistory(false); } private navigateHistory(previous: boolean): void { - const historyEntry = (previous ? - (this.history.previous() ?? this.history.first()) : this.history.next()) - ?? { text: '' }; - - this.onHistoryEntry = previous || this.history.current() !== null; + const historyEntry = previous ? + this.history.previous() : this.history.next(); aria.status(historyEntry.text); this.inHistoryNavigation = true; - this.setValue(historyEntry.text); + this.setValue(historyEntry.text, true); this.inHistoryNavigation = false; this._onDidLoadInputState.fire(historyEntry.state); @@ -235,10 +286,19 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - setValue(value: string): void { + setValue(value: string, transient: boolean): void { this.inputEditor.setValue(value); // always leave cursor at the end this.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); + + if (!transient) { + this.saveCurrentValue(this.getInputState()); + } + } + + private saveCurrentValue(inputState: any): void { + const newEntry = { text: this._inputEditor.getValue(), state: inputState }; + this.history.replaceLast(newEntry); } focus() { @@ -253,17 +313,17 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge * Reset the input and update history. * @param userQuery If provided, this will be added to the history. Followups and programmatic queries should not be passed. */ - async acceptInput(userQuery?: string, inputState?: any): Promise { - if (userQuery) { - let element = this.history.getHistory().find(candidate => candidate.text === userQuery); - if (!element) { - element = { text: userQuery, state: inputState }; - } else { - element.state = inputState; - } - this.history.add(element); + async acceptInput(isUserQuery?: boolean): Promise { + if (isUserQuery) { + const userQuery = this._inputEditor.getValue(); + const entry: IChatHistoryEntry = { text: userQuery, state: this.getInputState() }; + this.history.replaceLast(entry); + this.history.add({ text: '' }); } + // Clear attached context, fire event to clear input state, and clear the input editor + this._attachedContext.clear(); + this._onDidLoadInputState.fire({}); if (this.accessibilityService.isScreenReaderOptimized() && isMacintosh) { this._acceptInputForVoiceover(); } else { @@ -279,33 +339,56 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } // Remove the input editor from the DOM temporarily to prevent VoiceOver // from reading the cleared text (the request) to the user. - this._inputEditorElement.removeChild(domNode); + domNode.remove(); this._inputEditor.setValue(''); this._inputEditorElement.appendChild(domNode); this._inputEditor.focus(); } - attachContext(...contentReferences: IChatRequestVariableEntry[]): void { - for (const reference of contentReferences) { - this.attachedContext.add(reference); + attachContext(overwrite: boolean, ...contentReferences: IChatRequestVariableEntry[]): void { + const removed = []; + if (overwrite) { + removed.push(...Array.from(this._attachedContext)); + this._attachedContext.clear(); } - this.initAttachedContext(this.attachedContextContainer); + if (contentReferences.length > 0) { + for (const reference of contentReferences) { + this._attachedContext.add(reference); + } + } + + if (removed.length > 0 || contentReferences.length > 0) { + this.initAttachedContext(this.attachedContextContainer); + + if (!overwrite) { + this._onDidChangeContext.fire({ removed, added: contentReferences }); + } + } } render(container: HTMLElement, initialValue: string, widget: IChatWidget) { this.container = dom.append(container, $('.interactive-input-part')); this.container.classList.toggle('compact', this.options.renderStyle === 'compact'); - this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); - this.attachedContextContainer = dom.append(this.container, $('.chat-attached-context')); + let inputContainer: HTMLElement; + let inputAndSideToolbar: HTMLElement; + if (this.options.renderStyle === 'compact') { + inputAndSideToolbar = dom.append(this.container, $('.interactive-input-and-side-toolbar')); + this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); + inputContainer = dom.append(inputAndSideToolbar, $('.interactive-input-and-execute-toolbar')); + this.attachedContextContainer = dom.append(this.container, $('.chat-attached-context')); + } else { + this.followupsContainer = dom.append(this.container, $('.interactive-input-followups')); + this.attachedContextContainer = dom.append(this.container, $('.chat-attached-context')); + inputAndSideToolbar = dom.append(this.container, $('.interactive-input-and-side-toolbar')); + inputContainer = dom.append(inputAndSideToolbar, $('.interactive-input-and-execute-toolbar')); + } this.initAttachedContext(this.attachedContextContainer); - const inputAndSideToolbar = dom.append(this.container, $('.interactive-input-and-side-toolbar')); - const inputContainer = dom.append(inputAndSideToolbar, $('.interactive-input-and-execute-toolbar')); const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer)); CONTEXT_IN_CHAT_INPUT.bindTo(inputScopedContextKeyService).set(true); - const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, inputScopedContextKeyService])); + const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, inputScopedContextKeyService]))); const { historyNavigationBackwardsEnablement, historyNavigationForwardsEnablement } = this._register(registerAndCreateHistoryNavigationContext(inputScopedContextKeyService, this)); this.historyNavigationBackwardsEnablement = historyNavigationBackwardsEnablement; @@ -330,10 +413,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge insertMode: 'replace', }; options.scrollbar = { ...(options.scrollbar ?? {}), vertical: 'hidden' }; + options.stickyScroll = { enabled: false }; - this._inputEditorElement = dom.append(inputContainer, $('.interactive-input-editor')); + this._inputEditorElement = dom.append(inputContainer, $(chatInputEditorContainerSelector)); const editorOptions = getSimpleCodeEditorWidgetOptions(); - editorOptions.contributions?.push(...EditorExtensionsRegistry.getSomeEditorContributions([HoverController.ID])); + editorOptions.contributions?.push(...EditorExtensionsRegistry.getSomeEditorContributions([ContentHoverController.ID, MarginHoverController.ID])); this._inputEditor = this._register(scopedInstantiationService.createInstance(CodeEditorWidget, this._inputEditorElement, options, editorOptions)); this._register(this._inputEditor.onDidChangeModelContent(() => { @@ -343,21 +427,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidChangeHeight.fire(); } - // Only allow history navigation when the input is empty. - // (If this model change happened as a result of a history navigation, this is canceled out by a call in this.navigateHistory) const model = this._inputEditor.getModel(); const inputHasText = !!model && model.getValue().trim().length > 0; this.inputEditorHasText.set(inputHasText); - - // If the user is typing on a history entry, then reset the onHistoryEntry flag so that history navigation can be disabled - if (!this.inHistoryNavigation) { - this.onHistoryEntry = false; - } - - if (!this.onHistoryEntry) { - this.historyNavigationForewardsEnablement.set(!inputHasText); - this.historyNavigationBackwardsEnablement.set(!inputHasText); - } })); this._register(this._inputEditor.onDidFocusEditorText(() => { this.inputEditorHasFocus.set(true); @@ -370,20 +442,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidBlur.fire(); })); - this._register(this._inputEditor.onDidChangeCursorPosition(e => { - const model = this._inputEditor.getModel(); - if (!model) { - return; - } - - const atTop = e.position.column === 1 && e.position.lineNumber === 1; - this.chatCursorAtTop.set(atTop); - - if (this.onHistoryEntry) { - this.historyNavigationBackwardsEnablement.set(atTop); - this.historyNavigationForewardsEnablement.set(e.position.equals(getLastPosition(model))); - } - })); this.toolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, inputContainer, this.options.menus.executeToolbar, { telemetrySource: this.options.menus.telemetrySource, @@ -436,9 +494,30 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const lineNumber = this.inputModel.getLineCount(); this._inputEditor.setPosition({ lineNumber, column: this.inputModel.getLineMaxColumn(lineNumber) }); } + + const onDidChangeCursorPosition = () => { + const model = this._inputEditor.getModel(); + if (!model) { + return; + } + + const position = this._inputEditor.getPosition(); + if (!position) { + return; + } + + const atTop = position.column === 1 && position.lineNumber === 1; + this.chatCursorAtTop.set(atTop); + + this.historyNavigationBackwardsEnablement.set(atTop); + this.historyNavigationForewardsEnablement.set(position.equals(getLastPosition(model))); + }; + this._register(this._inputEditor.onDidChangeCursorPosition(e => onDidChangeCursorPosition())); + onDidChangeCursorPosition(); } - private initAttachedContext(container: HTMLElement) { + private initAttachedContext(container: HTMLElement, isLayout = false) { + const oldHeight = container.offsetHeight; dom.clearNode(container); this.attachedContextDisposables.clear(); dom.setVisibility(Boolean(this.attachedContext.size), this.attachedContextContainer); @@ -465,7 +544,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge widget.tabIndex = 0; } else { const attachmentLabel = attachment.fullName ?? attachment.name; - label.setLabel(attachmentLabel, undefined); + const withIcon = attachment.icon?.id ? `$(${attachment.icon.id}) ${attachmentLabel}` : attachmentLabel; + label.setLabel(withIcon, undefined); widget.ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name); widget.tabIndex = 0; @@ -481,7 +561,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.attachedContextDisposables.add(clearButton); clearButton.icon = Codicon.close; const disp = clearButton.onDidClick((e) => { - this.attachedContext.delete(attachment); + this._attachedContext.delete(attachment); disp.dispose(); // Set focus to the next attached context item if deletion was triggered by a keystroke (vs a mouse click) @@ -493,10 +573,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } this._onDidChangeHeight.fire(); - this._onDidDeleteContext.fire(attachment); + this._onDidChangeContext.fire({ removed: [attachment] }); }); this.attachedContextDisposables.add(disp); }); + + if (oldHeight !== container.offsetHeight && !isLayout) { + this._onDidChangeHeight.fire(); + } } async renderFollowups(items: IChatFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { @@ -509,6 +593,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (items && items.length > 0) { this.followupsDisposables.add(this.instantiationService.createInstance, ChatFollowups>(ChatFollowups, this.followupsContainer, items, this.location, undefined, followup => this._onDidAcceptFollowup.fire({ followup, response }))); } + this._onDidChangeHeight.fire(); } get contentHeight(): number { @@ -524,12 +609,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private previousInputEditorDimension: IDimension | undefined; private _layout(height: number, width: number, allowRecurse = true): void { - this.initAttachedContext(this.attachedContextContainer); + this.initAttachedContext(this.attachedContextContainer, true); const data = this.getLayoutData(); const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.inputPartVerticalPadding); + const followupsWidth = width - data.inputPartHorizontalPadding; + this.followupsContainer.style.width = `${followupsWidth}px`; + this._inputPartHeight = data.followupsHeight + inputEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.implicitContextHeight; const initialEditorScrollWidth = this._inputEditor.getScrollWidth(); @@ -553,23 +641,26 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge inputEditorBorder: 2, followupsHeight: this.followupsContainer.offsetHeight, inputPartEditorHeight: Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight), - inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 8 : 40, + inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 12 : 40, inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : 24, implicitContextHeight: this.attachedContextContainer.offsetHeight, editorBorder: 2, editorPadding: 12, - toolbarPadding: 4, + toolbarPadding: (this.toolbar.getItemsLength() - 1) * 4, executeToolbarWidth: this.cachedToolbarWidth = this.toolbar.getItemsWidth(), sideToolbarWidth: this.inputSideToolbarContainer ? dom.getTotalWidth(this.inputSideToolbarContainer) + 4 /*gap*/ : 0, }; } saveState(): void { - const inputHistory = this.history.getHistory(); + this.saveCurrentValue(this.getInputState()); + const inputHistory = [...this.history]; this.historyService.saveHistory(this.location, inputHistory); } } +const historyKeyFn = (entry: IChatHistoryEntry) => JSON.stringify(entry); + function getLastPosition(model: ITextModel): IPosition { return { lineNumber: model.getLineCount(), column: model.getLineLength(model.getLineCount()) + 1 }; } @@ -624,3 +715,6 @@ class ChatSubmitDropdownActionItem extends DropdownWithPrimaryActionViewItem { this._register(menu.onDidChange(() => setActions())); } } + +const chatInputEditorContainerSelector = '.interactive-input-editor'; +setupSimpleEditorSelectionStyling(chatInputEditorContainerSelector); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 6e29cb90..242ab4e2 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -7,94 +7,83 @@ import * as dom from 'vs/base/browser/dom'; import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { alert } from 'vs/base/browser/ui/aria/aria'; -import { Button } from 'vs/base/browser/ui/button/button'; +import { DropdownMenuActionViewItem, IDropdownMenuActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; -import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; -import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { IAction } from 'vs/base/common/actions'; -import { distinct } from 'vs/base/common/arrays'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { coalesce, distinct } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { FuzzyScore } from 'vs/base/common/filters'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; -import { FileAccess, Schemas, matchesSomeScheme } from 'vs/base/common/network'; +import { FileAccess } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; import { autorun } from 'vs/base/common/observable'; -import { basename } from 'vs/base/common/path'; -import { basenameOrAuthority, isEqual } from 'vs/base/common/resources'; -import { equalsIgnoreCase } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; -import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; -import { Range } from 'vs/editor/common/core/range'; -import { TextEdit } from 'vs/editor/common/languages'; -import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { IModelService } from 'vs/editor/common/services/model'; -import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; -import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { localize } from 'vs/nls'; -import { IMenuEntryActionViewItemOptions, MenuEntryActionViewItem, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuEntryActionViewItemOptions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { FileKind, FileType } from 'vs/platform/files/common/files'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { WorkbenchCompressibleAsyncDataTree, WorkbenchList } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { ChatTreeItem, GeneratingPhrase, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat'; +import { MarkUnhelpfulActionId } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions'; +import { ChatTreeItem, GeneratingPhrase, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatAgentHover, getChatAgentHoverOptions } from 'vs/workbench/contrib/chat/browser/chatAgentHover'; -import { ChatConfirmationWidget } from 'vs/workbench/contrib/chat/browser/chatConfirmationWidget'; +import { ChatAttachmentsContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart'; +import { ChatCodeCitationContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatCodeCitationContentPart'; +import { ChatCommandButtonContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatCommandContentPart'; +import { ChatConfirmationContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart'; +import { IChatContentPart, IChatContentPartRenderContext } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatContentParts'; +import { ChatMarkdownContentPart, EditorPool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart'; +import { ChatProgressContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart'; +import { ChatCollapsibleListContentPart, CollapsibleListPool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart'; +import { ChatTaskContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatTaskContentPart'; +import { ChatTextEditContentPart, DiffEditorPool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatTextEditContentPart'; +import { ChatTreeContentPart, TreePool } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart'; +import { ChatWarningContentPart } from 'vs/workbench/contrib/chat/browser/chatContentParts/chatWarningContentPart'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatMarkdownDecorationsRenderer } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; +import { ChatMarkdownRenderer } from 'vs/workbench/contrib/chat/browser/chatMarkdownRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { ChatCodeBlockContentProvider, CodeBlockPart, CodeCompareBlockPart, ICodeBlockData, ICodeCompareBlockData, ICodeCompareBlockDiffData, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { ChatCodeBlockContentProvider, CodeBlockPart } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { ChatAgentLocation, IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatProgressRenderableResponseContent, IChatTextEditGroup } from 'vs/workbench/contrib/chat/common/chatModel'; +import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { IChatRequestVariableEntry, IChatTextEditGroup } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { ChatAgentVoteDirection, IChatCommandButton, IChatConfirmation, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatSendRequestOptions, IChatService, IChatTask, IChatWarningMessage } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownRenderData, IChatResponseViewModel, IChatTaskRenderData, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; -import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; -import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; -import { IMarkdownVulnerability, annotateSpecialMarkdownContent } from '../common/annotations'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatConfirmation, IChatContentReference, IChatFollowup, IChatTask, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCodeCitations, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; +import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; +import { annotateSpecialMarkdownContent } from '../common/annotations'; import { CodeBlockModelCollection } from '../common/codeBlockModelCollection'; -import { IChatListItemRendererOptions } from './chat'; -import { ChatMarkdownRenderer } from 'vs/workbench/contrib/chat/browser/chatMarkdownRenderer'; -import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; const $ = dom.$; interface IChatListItemTemplate { currentElement?: ChatTreeItem; + renderedParts?: IChatContentPart[]; readonly rowContainer: HTMLElement; readonly titleToolbar?: MenuWorkbenchToolBar; readonly avatarContainer: HTMLElement; readonly username: HTMLElement; readonly detail: HTMLElement; readonly value: HTMLElement; - readonly referencesListContainer: HTMLElement; readonly contextKeyService: IContextKeyService; + readonly instantiationService: IInstantiationService; readonly templateDisposables: IDisposable; readonly elementDisposables: DisposableStore; readonly agentHover: ChatAgentHover; @@ -105,11 +94,9 @@ interface IItemHeightChangeParams { height: number; } -interface IChatMarkdownRenderResult extends IMarkdownRenderResult { - codeBlockCount: number; -} - -const forceVerboseLayoutTracing = false; +const forceVerboseLayoutTracing = false + // || Boolean("TRUE") // causes a linter warning so that it cannot be pushed + ; export interface IChatRendererDelegate { getListLength(): number; @@ -141,14 +128,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer()); - private _usedReferencesEnabled = false; - constructor( editorOptions: ChatEditorOptions, private readonly location: ChatAgentLocation, @@ -159,14 +144,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - if (e.affectsConfiguration('chat.experimental.usedReferences')) { - this._usedReferencesEnabled = configService.getValue('chat.experimental.usedReferences') ?? true; - } - })); } get templateId(): string { return ChatListItemRenderer.ID; } - editorsInUse() { + editorsInUse(): Iterable { return this._editorPool.inUse(); } @@ -203,21 +177,19 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer submenu.actions.length <= 1 - }, - actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { - if (action instanceof MenuItemAction && (action.item.id === 'workbench.action.chat.voteDown' || action.item.id === 'workbench.action.chat.voteUp')) { - return scopedInstantiationService.createInstance(ChatVoteButton, action, options as IMenuEntryActionViewItemOptions); - } - return createActionViewItem(scopedInstantiationService, action, options); - } - })); - } - + const scopedInstantiationService = templateDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService]))); const agentHover = templateDisposables.add(this.instantiationService.createInstance(ChatAgentHover)); const hoverContent = () => { - if (isResponseVM(template.currentElement) && template.currentElement.agent) { + if (isResponseVM(template.currentElement) && template.currentElement.agent && !template.currentElement.agent.isDefault) { agentHover.setAgent(template.currentElement.agent.id); return agentHover.domNode; } @@ -316,7 +290,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer isResponseVM(template.currentElement) ? template.currentElement.agent : undefined, this.commandService); - templateDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('element'), user, hoverContent, hoverOptions)); + templateDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), user, hoverContent, hoverOptions)); templateDisposables.add(dom.addDisposableListener(user, dom.EventType.KEY_DOWN, e => { const ev = new StandardKeyboardEvent(e); if (ev.equals(KeyCode.Space) || ev.equals(KeyCode.Enter)) { @@ -328,7 +302,29 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer submenu.actions.length <= 1 + }, + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { + const currentElement = template.currentElement; + if (action instanceof MenuItemAction && action.item.id === MarkUnhelpfulActionId && isResponseVM(currentElement)) { + return scopedInstantiationService.createInstance(ChatVoteDownButton, action, options as IMenuEntryActionViewItemOptions); + } + return createActionViewItem(scopedInstantiationService, action, options); + } + })); + } + return template; } @@ -357,13 +353,13 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { try { - if (this.doNextProgressiveRender(element, index, templateData, !!initial, progressiveRenderingDisposables)) { + if (this.doNextProgressiveRender(element, index, templateData, !!initial)) { timer.cancel(); } } catch (err) { // Kill the timer if anything went wrong, avoid getting stuck in a nasty rendering loop. timer.cancel(); - throw err; + this.logService.error(err); } }; timer.cancelAndSet(runProgressiveRender, 50, dom.getWindow(templateData.rowContainer)); runProgressiveRender(true); } else if (isResponseVM(element)) { - const renderableResponse = annotateSpecialMarkdownContent(element.response.value); - this.basicRenderElement(renderableResponse, element, index, templateData); + this.basicRenderElement(element, index, templateData); } else if (isRequestVM(element)) { - const markdown = 'message' in element.message ? - element.message.message : - this.markdownDecorationsRenderer.convertParsedRequestToMarkdown(element.message); - this.basicRenderElement([{ content: new MarkdownString(markdown), kind: 'markdownContent' }], element, index, templateData); + this.basicRenderElement(element, index, templateData); } else { this.renderWelcomeMessage(element, templateData); } @@ -422,14 +416,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer, element: ChatTreeItem, index: number, templateData: IChatListItemTemplate) { - const fillInIncompleteTokens = isResponseVM(element) && (!element.isComplete || element.isCanceled || element.errorDetails?.responseIsFiltered || element.errorDetails?.responseIsIncomplete); + private basicRenderElement(element: ChatTreeItem, index: number, templateData: IChatListItemTemplate) { + let value: IChatRendererContent[] = []; + if (isRequestVM(element) && !element.confirmation) { + const markdown = 'message' in element.message ? + element.message.message : + this.markdownDecorationsRenderer.convertParsedRequestToMarkdown(element.message); + value = [{ content: new MarkdownString(markdown), kind: 'markdownContent' }]; + } else if (isResponseVM(element)) { + if (element.contentReferences.length) { + value.push({ kind: 'references', references: element.contentReferences }); + } + value.push(...annotateSpecialMarkdownContent(element.response.value)); + if (element.codeCitations.length) { + value.push({ kind: 'codeCitations', citations: element.codeCitations }); + } + } dom.clearNode(templateData.value); - dom.clearNode(templateData.referencesListContainer); if (isResponseVM(element)) { this.renderDetail(element, templateData); } - this.renderContentReferencesIfNeeded(element, templateData, templateData.elementDisposables); - - let fileTreeIndex = 0; - let codeBlockIndex = 0; - value.forEach((data, index) => { - const result = data.kind === 'treeData' - ? this.renderTreeData(data.treeData, element, templateData, fileTreeIndex++) - : data.kind === 'markdownContent' - ? this.renderMarkdown(data.content, element, templateData, fillInIncompleteTokens, codeBlockIndex) - : data.kind === 'progressMessage' && onlyProgressMessagesAfterI(value, index) ? this.renderProgressMessage(data, false) // TODO render command - : data.kind === 'progressTask' ? this.renderProgressTask(data, false, element, templateData) - : data.kind === 'command' ? this.renderCommandButton(element, data) - : data.kind === 'textEditGroup' ? this.renderTextEdit(element, data, templateData) - : data.kind === 'warning' ? this.renderNotification('warning', data.content) - : data.kind === 'confirmation' ? this.renderConfirmation(element, data, templateData) - : undefined; - - if (result) { - templateData.value.appendChild(result.element); - templateData.elementDisposables.add(result); + const isFiltered = !!(isResponseVM(element) && element.errorDetails?.responseIsFiltered); - if ('codeBlockCount' in result) { - codeBlockIndex += (result as IChatMarkdownRenderResult).codeBlockCount; + const parts: IChatContentPart[] = []; + if (!isFiltered) { + value.forEach((data, index) => { + const context: IChatContentPartRenderContext = { + element, + index, + content: value, + preceedingContentParts: parts, + }; + const newPart = this.renderChatContentPart(data, templateData, context); + if (newPart) { + templateData.value.appendChild(newPart.domNode); + parts.push(newPart); + } + }); + } + + if (templateData.renderedParts) { + dispose(templateData.renderedParts); + } + templateData.renderedParts = parts; + + if (!isFiltered) { + if (isRequestVM(element) && element.variables.length) { + const newPart = this.renderAttachments(element.variables, element.contentReferences, templateData); + if (newPart) { + templateData.value.appendChild(newPart.domNode); + templateData.elementDisposables.add(newPart); } } - }); + } if (isResponseVM(element) && element.errorDetails?.message) { - const renderedError = this.renderNotification(element.errorDetails.responseIsFiltered ? 'info' : 'error', new MarkdownString(element.errorDetails.message)); + const renderedError = this.instantiationService.createInstance(ChatWarningContentPart, element.errorDetails.responseIsFiltered ? 'info' : 'error', new MarkdownString(element.errorDetails.message), this.renderer); templateData.elementDisposables.add(renderedError); - templateData.value.appendChild(renderedError.element); + templateData.value.appendChild(renderedError.domNode); } const newHeight = templateData.rowContainer.offsetHeight; @@ -541,12 +556,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { if (Array.isArray(item)) { - const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService])); + const scopedInstaService = templateData.elementDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService]))); templateData.elementDisposables.add( scopedInstaService.createInstance, ChatFollowups>( ChatFollowups, @@ -556,11 +569,18 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this._onDidClickFollowup.fire(followup))); } else { - const result = this.renderMarkdown(item as IMarkdownString, element, templateData); - templateData.value.appendChild(result.element); + const context: IChatContentPartRenderContext = { + element, + index: i, + // NA for welcome msg + content: [], + preceedingContentParts: [] + }; + const result = this.renderMarkdown(item, templateData, context); + templateData.value.appendChild(result.domNode); templateData.elementDisposables.add(result); } - } + }); const newHeight = templateData.rowContainer.offsetHeight; const fireEvent = !element.currentRenderedHeight || element.currentRenderedHeight !== newHeight; @@ -579,669 +599,324 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const renderedPart = renderedParts[index]; - // Is this part completely new? - if (!renderedPart) { - if (part.kind === 'treeData') { - partsToRender[index] = part.treeData; - } else if (part.kind === 'progressMessage') { - partsToRender[index] = { - progressMessage: part, - isAtEndOfResponse: onlyProgressMessagesAfterI(renderableResponse, index), - isLast: index === renderableResponse.length - 1, - } satisfies IChatProgressMessageRenderData; - } else if (part.kind === 'command' || - part.kind === 'textEditGroup' || - part.kind === 'confirmation' || - part.kind === 'warning') { - partsToRender[index] = part; - } else if (part.kind === 'progressTask') { - partsToRender[index] = { - task: part, - isSettled: part.isSettled?.() ?? true, - progressLength: part.progress.length, - }; - } else { - const wordCountResult = this.getDataForProgressiveRender(element, contentToMarkdown(part.content), { renderedWordCount: 0, lastRenderTime: 0 }); - if (wordCountResult !== undefined) { - this.traceLayout('doNextProgressiveRender', `Rendering new part ${index}, wordCountResult=${wordCountResult.actualWordCount}, rate=${wordCountResult.rate}`); - partsToRender[index] = { - renderedWordCount: wordCountResult.actualWordCount, - lastRenderTime: Date.now(), - isFullyRendered: wordCountResult.isFullString, - originalMarkdown: part.content, - }; - wordCountResults[index] = wordCountResult; - } - } - } - - // Did this part's content change? - else if ((part.kind === 'markdownContent' || part.kind === 'progressMessage') && isMarkdownRenderData(renderedPart)) { // TODO - const wordCountResult = this.getDataForProgressiveRender(element, contentToMarkdown(part.content), renderedPart); - // Check if there are any new words to render - if (wordCountResult !== undefined && renderedPart.renderedWordCount !== wordCountResult?.actualWordCount) { - this.traceLayout('doNextProgressiveRender', `Rendering changed part ${index}, wordCountResult=${wordCountResult.actualWordCount}, rate=${wordCountResult.rate}`); - partsToRender[index] = { - renderedWordCount: wordCountResult.actualWordCount, - lastRenderTime: Date.now(), - isFullyRendered: wordCountResult.isFullString, - originalMarkdown: part.content, - }; - wordCountResults[index] = wordCountResult; - } else if (!renderedPart.isFullyRendered && !wordCountResult) { - // This part is not fully rendered, but not enough time has passed to render more content - somePartIsNotFullyRendered = true; - } - } - - // Is it a progress message that needs to be rerendered? - else if (part.kind === 'progressMessage' && isProgressMessageRenderData(renderedPart) && ( - (renderedPart.isAtEndOfResponse !== onlyProgressMessagesAfterI(renderableResponse, index)) || - renderedPart.isLast !== (index === renderableResponse.length - 1))) { - partsToRender[index] = { - progressMessage: part, - isAtEndOfResponse: onlyProgressMessagesAfterI(renderableResponse, index), - isLast: index === renderableResponse.length - 1, - } satisfies IChatProgressMessageRenderData; - } - - else if (part.kind === 'progressTask' && isProgressTaskRenderData(renderedPart)) { - const isSettled = part.isSettled?.() ?? true; - if (renderedPart.isSettled !== isSettled || part.progress.length !== renderedPart.progressLength || isSettled) { - partsToRender[index] = { task: part, isSettled, progressLength: part.progress.length }; - } - } - }); + this.basicRenderElement(element, index, templateData); + return true; + } - isFullyRendered = partsToRender.filter((p) => !('isSettled' in p) || !p.isSettled).length === 0 && !somePartIsNotFullyRendered; + let isFullyRendered = false; + this.traceLayout('doNextProgressiveRender', `START progressive render, index=${index}, renderData=${JSON.stringify(element.renderData)}`); + const contentForThisTurn = this.getNextProgressiveRenderContent(element); + const partsToRender = this.diff(templateData.renderedParts ?? [], contentForThisTurn, element); + isFullyRendered = partsToRender.every(part => part === null); - if (isFullyRendered && element.isComplete) { + if (isFullyRendered) { + if (element.isComplete) { // Response is done and content is rendered, so do a normal render - this.traceLayout('runProgressiveRender', `end progressive render, index=${index} and clearing renderData, response is complete, index=${index}`); + this.traceLayout('doNextProgressiveRender', `END progressive render, index=${index} and clearing renderData, response is complete`); element.renderData = undefined; - disposables.clear(); - this.basicRenderElement(renderableResponse, element, index, templateData); - } else if (!isFullyRendered) { - disposables.clear(); - this.renderContentReferencesIfNeeded(element, templateData, disposables); - let hasRenderedOneMarkdownBlock = false; - partsToRender.forEach((partToRender, index) => { - if (!partToRender) { - return; - } - - // Undefined => don't do anything. null => remove the rendered element - let result: { element: HTMLElement } & IDisposable | undefined | null; - if (isInteractiveProgressTreeData(partToRender)) { - result = this.renderTreeData(partToRender, element, templateData, index); - } else if (isProgressMessageRenderData(partToRender)) { - if (onlyProgressMessageRenderDatasAfterI(partsToRender, index)) { - result = this.renderProgressMessage(partToRender.progressMessage, index === partsToRender.length - 1); - } else { - result = null; - } - } else if (isProgressTaskRenderData(partToRender)) { - result = this.renderProgressTask(partToRender.task, !partToRender.isSettled, element, templateData); - } else if (isCommandButtonRenderData(partToRender)) { - result = this.renderCommandButton(element, partToRender); - } else if (isTextEditRenderData(partToRender)) { - result = this.renderTextEdit(element, partToRender, templateData); - } else if (isConfirmationRenderData(partToRender)) { - result = this.renderConfirmation(element, partToRender, templateData); - } else if (isWarningRenderData(partToRender)) { - result = this.renderNotification('warning', partToRender.content); - } - - // Avoid doing progressive rendering for multiple markdown parts simultaneously - else if (!hasRenderedOneMarkdownBlock && wordCountResults[index]) { - const { value } = wordCountResults[index]; - const part = partsToRender[index]; - const originalMarkdown = 'originalMarkdown' in part ? part.originalMarkdown : undefined; - const markdownToRender = new MarkdownString(value, originalMarkdown); - result = this.renderMarkdown(markdownToRender, element, templateData, true); - hasRenderedOneMarkdownBlock = true; - } - - if (result === undefined) { - return; - } - - // Doing the progressive render - renderedParts[index] = partToRender; - const existingElement = templateData.value.children[index]; - if (existingElement) { - if (result === null) { - templateData.value.replaceChild($('span.placeholder-for-deleted-thing'), existingElement); - } else { - templateData.value.replaceChild(result.element, existingElement); - } - } else if (result) { - templateData.value.appendChild(result.element); - } - - if (result) { - disposables.add(result); - } - }); - } else { - // Nothing new to render, not done, keep waiting - return false; + this.basicRenderElement(element, index, templateData); + return true; } + + // Nothing new to render, not done, keep waiting + this.traceLayout('doNextProgressiveRender', 'caught up with the stream- no new content to render'); + return false; } - // Some render happened - update the height + // Do an actual progressive render + this.traceLayout('doNextProgressiveRender', `doing progressive render, ${partsToRender.length} parts to render`); + this.renderChatContentDiff(partsToRender, contentForThisTurn, element, templateData); + const height = templateData.rowContainer.offsetHeight; element.currentRenderedHeight = height; if (!isInRenderElement) { this._onDidChangeItemHeight.fire({ element, height: templateData.rowContainer.offsetHeight }); } - return isFullyRendered; + return false; } - private renderTreeData(data: IChatResponseProgressFileTreeData, element: ChatTreeItem, templateData: IChatListItemTemplate, treeDataIndex: number): { element: HTMLElement; dispose: () => void } { - const treeDisposables = new DisposableStore(); - const ref = treeDisposables.add(this._treePool.get()); - const tree = ref.object; - - treeDisposables.add(tree.onDidOpen((e) => { - if (e.element && !('children' in e.element)) { - this.openerService.open(e.element.uri); + private renderChatContentDiff(partsToRender: ReadonlyArray, contentForThisTurn: ReadonlyArray, element: IChatResponseViewModel, templateData: IChatListItemTemplate): void { + const renderedParts = templateData.renderedParts ?? []; + templateData.renderedParts = renderedParts; + partsToRender.forEach((partToRender, index) => { + if (!partToRender) { + // null=no change + return; } - })); - treeDisposables.add(tree.onDidChangeCollapseState(() => { - this.updateItemHeight(templateData); - })); - treeDisposables.add(tree.onContextMenu((e) => { - e.browserEvent.preventDefault(); - e.browserEvent.stopPropagation(); - })); - tree.setInput(data).then(() => { - if (!ref.isStale()) { - tree.layout(); - this.updateItemHeight(templateData); + const alreadyRenderedPart = templateData.renderedParts?.[index]; + if (alreadyRenderedPart) { + alreadyRenderedPart.dispose(); } - }); - if (isResponseVM(element)) { - const fileTreeFocusInfo = { - treeDataId: data.uri.toString(), - treeIndex: treeDataIndex, - focus() { - tree.domFocus(); - } + const preceedingContentParts = renderedParts.slice(0, index); + const context: IChatContentPartRenderContext = { + element, + content: contentForThisTurn, + preceedingContentParts, + index }; + const newPart = this.renderChatContentPart(partToRender, templateData, context); + if (newPart) { + // Maybe the part can't be rendered in this context, but this shouldn't really happen + if (alreadyRenderedPart) { + try { + // This method can throw HierarchyRequestError + alreadyRenderedPart.domNode.replaceWith(newPart.domNode); + } catch (err) { + this.logService.error('ChatListItemRenderer#renderChatContentDiff: error replacing part', err); + } + } else { + templateData.value.appendChild(newPart.domNode); + } - treeDisposables.add(tree.onDidFocus(() => { - this.focusedFileTreesByResponseId.set(element.id, fileTreeFocusInfo.treeIndex); - })); - - const fileTrees = this.fileTreesByResponseId.get(element.id) ?? []; - fileTrees.push(fileTreeFocusInfo); - this.fileTreesByResponseId.set(element.id, distinct(fileTrees, (v) => v.treeDataId)); - treeDisposables.add(toDisposable(() => this.fileTreesByResponseId.set(element.id, fileTrees.filter(v => v.treeDataId !== data.uri.toString())))); - } - - return { - element: tree.getHTMLElement().parentElement!, - dispose: () => { - treeDisposables.dispose(); - } - }; - } - - private renderContentReferencesIfNeeded(element: ChatTreeItem, templateData: IChatListItemTemplate, disposables: DisposableStore): void { - if (isResponseVM(element) && this._usedReferencesEnabled && element.contentReferences.length) { - dom.show(templateData.referencesListContainer); - const contentReferencesListResult = this.renderContentReferencesListData(null, element.contentReferences, element, templateData); - if (templateData.referencesListContainer.firstChild) { - templateData.referencesListContainer.replaceChild(contentReferencesListResult.element, templateData.referencesListContainer.firstChild!); - } else { - templateData.referencesListContainer.appendChild(contentReferencesListResult.element); + renderedParts[index] = newPart; + } else if (alreadyRenderedPart) { + alreadyRenderedPart.domNode.remove(); } - disposables.add(contentReferencesListResult); - } else { - dom.hide(templateData.referencesListContainer); - } + }); } - private renderContentReferencesListData(task: IChatTask | null, data: ReadonlyArray, element: IChatResponseViewModel, templateData: IChatListItemTemplate): { element: HTMLElement; dispose: () => void } { - const listDisposables = new DisposableStore(); - const referencesLabel = task?.content.value ?? (data.length > 1 ? - localize('usedReferencesPlural', "Used {0} references", data.length) : - localize('usedReferencesSingular', "Used {0} reference", 1)); - const iconElement = $('.chat-used-context-icon'); - const icon = (element: IChatResponseViewModel) => element.usedReferencesExpanded ? Codicon.chevronDown : Codicon.chevronRight; - iconElement.classList.add(...ThemeIcon.asClassNameArray(icon(element))); - const buttonElement = $('.chat-used-context-label', undefined); - - const collapseButton = listDisposables.add(new Button(buttonElement, { - buttonBackground: undefined, - buttonBorder: undefined, - buttonForeground: undefined, - buttonHoverBackground: undefined, - buttonSecondaryBackground: undefined, - buttonSecondaryForeground: undefined, - buttonSecondaryHoverBackground: undefined, - buttonSeparator: undefined - })); - const container = $('.chat-used-context', undefined, buttonElement); - collapseButton.label = referencesLabel; - collapseButton.element.prepend(iconElement); - this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); - container.classList.toggle('chat-used-context-collapsed', !element.usedReferencesExpanded); - listDisposables.add(collapseButton.onDidClick(() => { - iconElement.classList.remove(...ThemeIcon.asClassNameArray(icon(element))); - element.usedReferencesExpanded = !element.usedReferencesExpanded; - iconElement.classList.add(...ThemeIcon.asClassNameArray(icon(element))); - container.classList.toggle('chat-used-context-collapsed', !element.usedReferencesExpanded); - this.updateItemHeight(templateData); - this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); - })); + /** + * Returns all content parts that should be rendered, and trimmed markdown content. We will diff this with the current rendered set. + */ + private getNextProgressiveRenderContent(element: IChatResponseViewModel): IChatRendererContent[] { + const data = this.getDataForProgressiveRender(element); - const ref = listDisposables.add(this._contentReferencesListPool.get()); - const list = ref.object; - container.appendChild(list.getHTMLElement().parentElement!); - - listDisposables.add(list.onDidOpen((e) => { - if (e.element && 'reference' in e.element) { - const uriOrLocation = 'variableName' in e.element.reference ? e.element.reference.value : e.element.reference; - const uri = URI.isUri(uriOrLocation) ? uriOrLocation : - uriOrLocation?.uri; - if (uri) { - this.openerService.open( - uri, - { - fromUserGesture: true, - editorOptions: { - ...e.editorOptions, - ...{ - selection: uriOrLocation && 'range' in uriOrLocation ? uriOrLocation.range : undefined - } - } - }); - } - } - })); - listDisposables.add(list.onContextMenu((e) => { - e.browserEvent.preventDefault(); - e.browserEvent.stopPropagation(); - })); + const renderableResponse = annotateSpecialMarkdownContent(element.response.value); - const maxItemsShown = 6; - const itemsShown = Math.min(data.length, maxItemsShown); - const height = itemsShown * 22; - list.layout(height); - list.getHTMLElement().style.height = `${height}px`; - list.splice(0, list.length, data); + this.traceLayout('getNextProgressiveRenderContent', `Want to render ${data.numWordsToRender} at ${data.rate} words/s, counting...`); + let numNeededWords = data.numWordsToRender; + const partsToRender: IChatRendererContent[] = []; + if (element.contentReferences.length) { + partsToRender.push({ kind: 'references', references: element.contentReferences }); + } - return { - element: container, - dispose: () => { - listDisposables.dispose(); + for (let i = 0; i < renderableResponse.length; i++) { + const part = renderableResponse[i]; + if (numNeededWords <= 0) { + break; } - }; - } - - private updateAriaLabel(element: HTMLElement, label: string, expanded?: boolean): void { - element.ariaLabel = expanded ? localize('usedReferencesExpanded', "{0}, expanded", label) : localize('usedReferencesCollapsed', "{0}, collapsed", label); - } - private renderProgressTask(task: IChatTask, showSpinner: boolean, element: ChatTreeItem, templateData: IChatListItemTemplate): IMarkdownRenderResult | undefined { - if (!isResponseVM(element)) { - return; - } + if (part.kind === 'markdownContent') { + const wordCountResult = getNWords(part.content.value, numNeededWords); + if (wordCountResult.isFullString) { + partsToRender.push(part); + } else { + partsToRender.push({ kind: 'markdownContent', content: new MarkdownString(wordCountResult.value, part.content) }); + } - if (task.progress.length) { - const refs = this.renderContentReferencesListData(task, task.progress, element, templateData); - const node = dom.$('.chat-progress-task'); - node.appendChild(refs.element); - return { element: node, dispose: refs.dispose }; + this.traceLayout('getNextProgressiveRenderContent', ` Chunk ${i}: Want to render ${numNeededWords} words and found ${wordCountResult.returnedWordCount} words. Total words in chunk: ${wordCountResult.totalWordCount}`); + numNeededWords -= wordCountResult.returnedWordCount; + } else { + partsToRender.push(part); + } } - return this.renderProgressMessage(task, showSpinner); - } - - private renderProgressMessage(progress: IChatProgressMessage | IChatTask, showSpinner: boolean): IMarkdownRenderResult { - if (showSpinner) { - // this step is in progress, communicate it to SR users - alert(progress.content.value); + const lastWordCount = element.contentUpdateTimings?.lastWordCount ?? 0; + const newRenderedWordCount = data.numWordsToRender - numNeededWords; + const bufferWords = lastWordCount - newRenderedWordCount; + this.traceLayout('getNextProgressiveRenderContent', `Want to render ${data.numWordsToRender} words. Rendering ${newRenderedWordCount} words. Buffer: ${bufferWords} words`); + if (newRenderedWordCount > 0 && newRenderedWordCount !== element.renderData?.renderedWordCount) { + // Only update lastRenderTime when we actually render new content + element.renderData = { lastRenderTime: Date.now(), renderedWordCount: newRenderedWordCount, renderedParts: partsToRender }; } - const codicon = showSpinner ? ThemeIcon.modify(Codicon.loading, 'spin').id : Codicon.check.id; - const markdown = new MarkdownString(`$(${codicon}) ${progress.content.value}`, { - supportThemeIcons: true - }); - const result = this.renderer.render(markdown); - result.element.classList.add('progress-step'); - return result; - } - private renderCommandButton(element: ChatTreeItem, commandButton: IChatCommandButton): IMarkdownRenderResult { - const container = $('.chat-command-button'); - const disposables = new DisposableStore(); - const enabled = !isResponseVM(element) || !element.isStale; - const tooltip = enabled ? - commandButton.command.tooltip : - localize('commandButtonDisabled', "Button not available in restored chat"); - const button = disposables.add(new Button(container, { ...defaultButtonStyles, supportIcons: true, title: tooltip })); - button.label = commandButton.command.title; - button.enabled = enabled; - - // TODO still need telemetry for command buttons - disposables.add(button.onDidClick(() => this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? [])))); - return { - dispose() { - disposables.dispose(); - }, - element: container - }; + return partsToRender; } - private renderNotification(kind: 'info' | 'warning' | 'error', content: IMarkdownString): IMarkdownRenderResult { - const container = $('.chat-notification-widget'); - let icon; - let iconClass; - switch (kind) { - case 'warning': - icon = Codicon.warning; - iconClass = '.chat-warning-codicon'; - break; - case 'error': - icon = Codicon.error; - iconClass = '.chat-error-codicon'; - break; - case 'info': - icon = Codicon.info; - iconClass = '.chat-info-codicon'; - break; - } - container.appendChild($(iconClass, undefined, renderIcon(icon))); - const markdownContent = this.renderer.render(content); - container.appendChild(markdownContent.element); - return { - element: container, - dispose() { markdownContent.dispose(); } - }; - } + private getDataForProgressiveRender(element: IChatResponseViewModel) { + const renderData = element.renderData ?? { lastRenderTime: 0, renderedWordCount: 0 }; - private renderConfirmation(element: ChatTreeItem, confirmation: IChatConfirmation, templateData: IChatListItemTemplate): IMarkdownRenderResult | undefined { - const store = new DisposableStore(); - const confirmationWidget = store.add(this.instantiationService.createInstance(ChatConfirmationWidget, confirmation.title, confirmation.message, [ - { label: localize('accept', "Accept"), data: confirmation.data }, - { label: localize('dismiss', "Dismiss"), data: confirmation.data, isSecondary: true }, - ])); - confirmationWidget.setShowButtons(!confirmation.isUsed); - - store.add(confirmationWidget.onDidClick(async e => { - if (isResponseVM(element)) { - const prompt = `${e.label}: "${confirmation.title}"`; - const data: IChatSendRequestOptions = e.isSecondary ? - { rejectedConfirmationData: [e.data] } : - { acceptedConfirmationData: [e.data] }; - data.agentId = element.agent?.id; - if (await this.chatService.sendRequest(element.sessionId, prompt, data)) { - confirmation.isUsed = true; - confirmationWidget.setShowButtons(false); - this.updateItemHeight(templateData); - } - } - })); + const rate = this.getProgressiveRenderRate(element); + const numWordsToRender = renderData.lastRenderTime === 0 ? + 1 : + renderData.renderedWordCount + + // Additional words to render beyond what's already rendered + Math.floor((Date.now() - renderData.lastRenderTime) / 1000 * rate); return { - element: confirmationWidget.domNode, - dispose() { store.dispose(); } + numWordsToRender, + rate }; } - private renderTextEdit(element: ChatTreeItem, chatTextEdit: IChatTextEditGroup, templateData: IChatListItemTemplate): IMarkdownRenderResult | undefined { + private diff(renderedParts: ReadonlyArray, contentToRender: ReadonlyArray, element: ChatTreeItem): ReadonlyArray { + const diff: (IChatRendererContent | null)[] = []; + for (let i = 0; i < contentToRender.length; i++) { + const content = contentToRender[i]; + const renderedPart = renderedParts[i]; - // TODO@jrieken move this into the CompareCodeBlock and properly say what kind of changes happen - if (this.rendererOptions.renderTextEditsAsSummary?.(chatTextEdit.uri)) { - if (isResponseVM(element) && element.response.value.every(item => item.kind === 'textEditGroup')) { - return { - element: $('.interactive-edits-summary', undefined, !element.isComplete ? localize('editsSummary1', "Making changes...") : localize('editsSummary', "Made changes.")), - dispose() { } - }; + if (!renderedPart || !renderedPart.hasSameContent(content, contentToRender.slice(i + 1), element)) { + diff.push(content); + } else { + // null -> no change + diff.push(null); } - return undefined; } - const store = new DisposableStore(); - const cts = new CancellationTokenSource(); + return diff; + } + + private renderChatContentPart(content: IChatRendererContent, templateData: IChatListItemTemplate, context: IChatContentPartRenderContext): IChatContentPart | undefined { + if (content.kind === 'treeData') { + return this.renderTreeData(content, templateData, context); + } else if (content.kind === 'progressMessage') { + return this.instantiationService.createInstance(ChatProgressContentPart, content, this.renderer, context); + } else if (content.kind === 'progressTask') { + return this.renderProgressTask(content, templateData, context); + } else if (content.kind === 'command') { + return this.instantiationService.createInstance(ChatCommandButtonContentPart, content, context); + } else if (content.kind === 'textEditGroup') { + return this.renderTextEdit(context, content, templateData); + } else if (content.kind === 'confirmation') { + return this.renderConfirmation(context, content, templateData); + } else if (content.kind === 'warning') { + return this.instantiationService.createInstance(ChatWarningContentPart, 'warning', content.content, this.renderer); + } else if (content.kind === 'markdownContent') { + return this.renderMarkdown(content.content, templateData, context); + } else if (content.kind === 'references') { + return this.renderContentReferencesListData(content, undefined, context, templateData); + } else if (content.kind === 'codeCitations') { + return this.renderCodeCitationsListData(content, context, templateData); + } - let isDisposed = false; - store.add(toDisposable(() => { - isDisposed = true; - cts.dispose(true); - })); + return undefined; + } - const ref = this._diffEditorPool.get(); + private renderTreeData(content: IChatTreeData, templateData: IChatListItemTemplate, context: IChatContentPartRenderContext): IChatContentPart { + const data = content.treeData; + const treeDataIndex = context.preceedingContentParts.filter(part => part instanceof ChatTreeContentPart).length; + const treePart = this.instantiationService.createInstance(ChatTreeContentPart, data, context.element, this._treePool, treeDataIndex); - // Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping) - // not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render) - store.add(ref.object.onDidChangeContentHeight(() => { - ref.object.layout(this._currentLayoutWidth); + treePart.addDisposable(treePart.onDidChangeHeight(() => { this.updateItemHeight(templateData); })); - const data: ICodeCompareBlockData = { - element, - edit: chatTextEdit, - diffData: (async () => { - - const ref = await this.textModelService.createModelReference(chatTextEdit.uri); - - if (isDisposed) { - ref.dispose(); - return; - } - - store.add(ref); - - const original = ref.object.textEditorModel; - let originalSha1: string = ''; - - if (chatTextEdit.state) { - originalSha1 = chatTextEdit.state.sha1; - } else { - const sha1 = new DefaultModelSHA1Computer(); - if (sha1.canComputeSHA1(original)) { - originalSha1 = sha1.computeSHA1(original); - chatTextEdit.state = { sha1: originalSha1, applied: 0 }; - } - } - - const modified = this.modelService.createModel( - createTextBufferFactoryFromSnapshot(original.createSnapshot()), - { languageId: original.getLanguageId(), onDidChange: Event.None }, - URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: original.uri.path, query: generateUuid() }), - false - ); - const modRef = await this.textModelService.createModelReference(modified.uri); - store.add(modRef); - - const editGroups: ISingleEditOperation[][] = []; - if (isResponseVM(element)) { - const chatModel = this.chatService.getSession(element.sessionId)!; - - for (const request of chatModel.getRequests()) { - if (!request.response) { - continue; - } - for (const item of request.response.response.value) { - if (item.kind !== 'textEditGroup' || item.state?.applied || !isEqual(item.uri, chatTextEdit.uri)) { - continue; - } - for (const group of item.edits) { - const edits = group.map(TextEdit.asEditOperation); - editGroups.push(edits); - } - } - if (request.response === element.model) { - break; - } - } + if (isResponseVM(context.element)) { + const fileTreeFocusInfo = { + treeDataId: data.uri.toString(), + treeIndex: treeDataIndex, + focus() { + treePart.domFocus(); } + }; - for (const edits of editGroups) { - modified.pushEditOperations(null, edits, () => null); - } + // TODO@roblourens there's got to be a better way to navigate trees + treePart.addDisposable(treePart.onDidFocus(() => { + this.focusedFileTreesByResponseId.set(context.element.id, fileTreeFocusInfo.treeIndex); + })); - return { - modified, - original, - originalSha1 - } satisfies ICodeCompareBlockDiffData; - })() - }; - ref.object.render(data, this._currentLayoutWidth, cts.token); + const fileTrees = this.fileTreesByResponseId.get(context.element.id) ?? []; + fileTrees.push(fileTreeFocusInfo); + this.fileTreesByResponseId.set(context.element.id, distinct(fileTrees, (v) => v.treeDataId)); + treePart.addDisposable(toDisposable(() => this.fileTreesByResponseId.set(context.element.id, fileTrees.filter(v => v.treeDataId !== data.uri.toString())))); + } - return { - element: ref.object.element, - dispose() { - store.dispose(); - ref.dispose(); - }, - }; + return treePart; } - private renderMarkdown(markdown: IMarkdownString, element: ChatTreeItem, templateData: IChatListItemTemplate, fillInIncompleteTokens = false, codeBlockStartIndex = 0): IChatMarkdownRenderResult { - const disposables = new DisposableStore(); - - // We release editors in order so that it's more likely that the same editor will be assigned if this element is re-rendered right away, like it often is during progressive rendering - const orderedDisposablesList: IDisposable[] = []; - const codeblocks: IChatCodeBlockInfo[] = []; - let codeBlockIndex = codeBlockStartIndex; - const result = this.renderer.render(markdown, { - fillInIncompleteTokens, - codeBlockRendererSync: (languageId, text) => { - const index = codeBlockIndex++; - let textModel: Promise; - let range: Range | undefined; - let vulns: readonly IMarkdownVulnerability[] | undefined; - if (equalsIgnoreCase(languageId, localFileLanguageId)) { - try { - const parsedBody = parseLocalFileData(text); - range = parsedBody.range && Range.lift(parsedBody.range); - textModel = this.textModelService.createModelReference(parsedBody.uri).then(ref => ref.object); - } catch (e) { - return $('div'); - } - } else { - if (!isRequestVM(element) && !isResponseVM(element)) { - console.error('Trying to render code block in welcome', element.id, index); - return $('div'); - } + private renderContentReferencesListData(references: IChatReferences, labelOverride: string | undefined, context: IChatContentPartRenderContext, templateData: IChatListItemTemplate): ChatCollapsibleListContentPart { + const referencesPart = this.instantiationService.createInstance(ChatCollapsibleListContentPart, references.references, labelOverride, context.element as IChatResponseViewModel, this._contentReferencesListPool); + referencesPart.addDisposable(referencesPart.onDidChangeHeight(() => { + this.updateItemHeight(templateData); + })); - const sessionId = isResponseVM(element) || isRequestVM(element) ? element.sessionId : ''; - const modelEntry = this.codeBlockModelCollection.getOrCreate(sessionId, element, index); - vulns = modelEntry.vulns; - textModel = modelEntry.model; - } + return referencesPart; + } - const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; - const ref = this.renderCodeBlock({ languageId, textModel, codeBlockIndex: index, element, range, hideToolbar, parentContextKeyService: templateData.contextKeyService, vulns }, text); - - // Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping) - // not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render) - disposables.add(ref.object.onDidChangeContentHeight(() => { - ref.object.layout(this._currentLayoutWidth); - this.updateItemHeight(templateData); - })); - - if (isResponseVM(element)) { - const info: IChatCodeBlockInfo = { - codeBlockIndex: index, - element, - focus() { - ref.object.focus(); - } - }; - codeblocks.push(info); - if (ref.object.uri) { - const uri = ref.object.uri; - this.codeBlocksByEditorUri.set(uri, info); - disposables.add(toDisposable(() => this.codeBlocksByEditorUri.delete(uri))); - } - } - orderedDisposablesList.push(ref); - return ref.object.element; - }, - asyncRenderCallback: () => this.updateItemHeight(templateData), - }); + private renderCodeCitationsListData(citations: IChatCodeCitations, context: IChatContentPartRenderContext, templateData: IChatListItemTemplate): ChatCodeCitationContentPart { + const citationsPart = this.instantiationService.createInstance(ChatCodeCitationContentPart, citations, context); + return citationsPart; + } - if (isResponseVM(element)) { - this.codeBlocksByResponseId.set(element.id, codeblocks); - disposables.add(toDisposable(() => this.codeBlocksByResponseId.delete(element.id))); + private renderProgressTask(task: IChatTask, templateData: IChatListItemTemplate, context: IChatContentPartRenderContext): IChatContentPart | undefined { + if (!isResponseVM(context.element)) { + return; } - disposables.add(this.markdownDecorationsRenderer.walkTreeAndAnnotateReferenceLinks(result.element)); + const taskPart = this.instantiationService.createInstance(ChatTaskContentPart, task, this._contentReferencesListPool, this.renderer, context); + taskPart.addDisposable(taskPart.onDidChangeHeight(() => { + this.updateItemHeight(templateData); + })); + return taskPart; + } - orderedDisposablesList.reverse().forEach(d => disposables.add(d)); - return { - codeBlockCount: codeBlockIndex - codeBlockStartIndex, - element: result.element, - dispose() { - result.dispose(); - disposables.dispose(); - } - }; + private renderConfirmation(context: IChatContentPartRenderContext, confirmation: IChatConfirmation, templateData: IChatListItemTemplate): IChatContentPart { + const part = this.instantiationService.createInstance(ChatConfirmationContentPart, confirmation, context); + part.addDisposable(part.onDidChangeHeight(() => this.updateItemHeight(templateData))); + return part; } - private renderCodeBlock(data: ICodeBlockData, text: string): IDisposableReference { - const ref = this._editorPool.get(); - const editorInfo = ref.object; - if (isResponseVM(data.element)) { - this.codeBlockModelCollection.update(data.element.sessionId, data.element, data.codeBlockIndex, { text, languageId: data.languageId }); - } + private renderAttachments(variables: IChatRequestVariableEntry[], contentReferences: ReadonlyArray | undefined, templateData: IChatListItemTemplate) { + return this.instantiationService.createInstance(ChatAttachmentsContentPart, variables, contentReferences, undefined); + } - editorInfo.render(data, this._currentLayoutWidth, this.rendererOptions.editableCodeBlock); + private renderTextEdit(context: IChatContentPartRenderContext, chatTextEdit: IChatTextEditGroup, templateData: IChatListItemTemplate): IChatContentPart { + const textEditPart = this.instantiationService.createInstance(ChatTextEditContentPart, chatTextEdit, context, this.rendererOptions, this._diffEditorPool, this._currentLayoutWidth); + textEditPart.addDisposable(textEditPart.onDidChangeHeight(() => { + textEditPart.layout(this._currentLayoutWidth); + this.updateItemHeight(templateData); + })); - return ref; + return textEditPart; } - private getDataForProgressiveRender(element: IChatResponseViewModel, data: IMarkdownString, renderData: Pick): IWordCountResult & { rate: number } | undefined { - const rate = this.getProgressiveRenderRate(element); - const numWordsToRender = renderData.lastRenderTime === 0 ? - 1 : - renderData.renderedWordCount + - // Additional words to render beyond what's already rendered - Math.floor((Date.now() - renderData.lastRenderTime) / 1000 * rate); + private renderMarkdown(markdown: IMarkdownString, templateData: IChatListItemTemplate, context: IChatContentPartRenderContext): IChatContentPart { + const element = context.element; + const fillInIncompleteTokens = isResponseVM(element) && (!element.isComplete || element.isCanceled || element.errorDetails?.responseIsFiltered || element.errorDetails?.responseIsIncomplete || !!element.renderData); + const codeBlockStartIndex = context.preceedingContentParts.reduce((acc, part) => acc + (part instanceof ChatMarkdownContentPart ? part.codeblocks.length : 0), 0); + const markdownPart = this.instantiationService.createInstance(ChatMarkdownContentPart, markdown, context, this._editorPool, fillInIncompleteTokens, codeBlockStartIndex, this.renderer, this._currentLayoutWidth, this.codeBlockModelCollection, this.rendererOptions); + markdownPart.addDisposable(markdownPart.onDidChangeHeight(() => { + markdownPart.layout(this._currentLayoutWidth); + this.updateItemHeight(templateData); + })); - if (numWordsToRender === renderData.renderedWordCount) { - return undefined; - } + const codeBlocksByResponseId = this.codeBlocksByResponseId.get(element.id) ?? []; + this.codeBlocksByResponseId.set(element.id, codeBlocksByResponseId); + markdownPart.addDisposable(toDisposable(() => { + const codeBlocksByResponseId = this.codeBlocksByResponseId.get(element.id); + if (codeBlocksByResponseId) { + markdownPart.codeblocks.forEach((info, i) => delete codeBlocksByResponseId[codeBlockStartIndex + i]); + } + })); - return { - ...getNWords(data.value, numWordsToRender), - rate - }; + markdownPart.codeblocks.forEach((info, i) => { + codeBlocksByResponseId[codeBlockStartIndex + i] = info; + if (info.uri) { + const uri = info.uri; + this.codeBlocksByEditorUri.set(uri, info); + markdownPart.addDisposable(toDisposable(() => this.codeBlocksByEditorUri.delete(uri))); + } + }); + + return markdownPart; } disposeElement(node: ITreeNode, index: number, templateData: IChatListItemTemplate): void { + this.traceLayout('disposeElement', `Disposing element, index=${index}`); + + // We could actually reuse a template across a renderElement call? + if (templateData.renderedParts) { + try { + dispose(coalesce(templateData.renderedParts)); + templateData.renderedParts = undefined; + dom.clearNode(templateData.value); + } catch (err) { + throw err; + } + } + + templateData.currentElement = undefined; templateData.elementDisposables.clear(); } @@ -1280,469 +955,88 @@ export class ChatListDelegate implements IListVirtualDelegate { } } - -interface IDisposableReference extends IDisposable { - object: T; - isStale: () => boolean; -} - -class EditorPool extends Disposable { - - private readonly _pool: ResourcePool; - - public inUse(): Iterable { - return this._pool.inUse; - } - - constructor( - options: ChatEditorOptions, - delegate: IChatRendererDelegate, - overflowWidgetsDomNode: HTMLElement | undefined, - @IInstantiationService instantiationService: IInstantiationService, - ) { - super(); - this._pool = this._register(new ResourcePool(() => { - return instantiationService.createInstance(CodeBlockPart, options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); - })); - } - - get(): IDisposableReference { - const codeBlock = this._pool.get(); - let stale = false; - return { - object: codeBlock, - isStale: () => stale, - dispose: () => { - codeBlock.reset(); - stale = true; - this._pool.release(codeBlock); - } - }; - } -} - -class DiffEditorPool extends Disposable { - - private readonly _pool: ResourcePool; - - public inUse(): Iterable { - return this._pool.inUse; - } - +const voteDownDetailLabels: Record = { + [ChatAgentVoteDownReason.IncorrectCode]: localize('incorrectCode', "Suggested incorrect code"), + [ChatAgentVoteDownReason.DidNotFollowInstructions]: localize('didNotFollowInstructions', "Didn't follow instructions"), + [ChatAgentVoteDownReason.MissingContext]: localize('missingContext', "Missing context"), + [ChatAgentVoteDownReason.OffensiveOrUnsafe]: localize('offensiveOrUnsafe', "Offensive or unsafe"), + [ChatAgentVoteDownReason.PoorlyWrittenOrFormatted]: localize('poorlyWrittenOrFormatted', "Poorly written or formatted"), + [ChatAgentVoteDownReason.RefusedAValidRequest]: localize('refusedAValidRequest', "Refused a valid request"), + [ChatAgentVoteDownReason.IncompleteCode]: localize('incompleteCode', "Incomplete code"), + [ChatAgentVoteDownReason.WillReportIssue]: localize('reportIssue', "Report an issue"), + [ChatAgentVoteDownReason.Other]: localize('other', "Other"), +}; + +export class ChatVoteDownButton extends DropdownMenuActionViewItem { constructor( - options: ChatEditorOptions, - delegate: IChatRendererDelegate, - overflowWidgetsDomNode: HTMLElement | undefined, - @IInstantiationService instantiationService: IInstantiationService, - ) { - super(); - this._pool = this._register(new ResourcePool(() => { - return instantiationService.createInstance(CodeCompareBlockPart, options, MenuId.ChatCompareBlock, delegate, overflowWidgetsDomNode); - })); - } - - get(): IDisposableReference { - const codeBlock = this._pool.get(); - let stale = false; - return { - object: codeBlock, - isStale: () => stale, - dispose: () => { - codeBlock.reset(); - stale = true; - this._pool.release(codeBlock); - } - }; - } -} - -class TreePool extends Disposable { - private _pool: ResourcePool>; - - public get inUse(): ReadonlySet> { - return this._pool.inUse; - } - - constructor( - private _onDidChangeVisibility: Event, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configService: IConfigurationService, - @IThemeService private readonly themeService: IThemeService, + action: IAction, + options: IDropdownMenuActionViewItemOptions | undefined, + @ICommandService private readonly commandService: ICommandService, + @IWorkbenchIssueService private readonly issueService: IWorkbenchIssueService, + @ILogService private readonly logService: ILogService, + @IContextMenuService contextMenuService: IContextMenuService, ) { - super(); - this._pool = this._register(new ResourcePool(() => this.treeFactory())); - } - - private treeFactory(): WorkbenchCompressibleAsyncDataTree { - const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility })); - - const container = $('.interactive-response-progress-tree'); - this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); - - const tree = this.instantiationService.createInstance( - WorkbenchCompressibleAsyncDataTree, - 'ChatListRenderer', - container, - new ChatListTreeDelegate(), - new ChatListTreeCompressionDelegate(), - [new ChatListTreeRenderer(resourceLabels, this.configService.getValue('explorer.decorations'))], - new ChatListTreeDataSource(), + super(action, + { getActions: () => this.getActions(), }, + contextMenuService, { - collapseByDefault: () => false, - expandOnlyOnTwistieClick: () => false, - identityProvider: { - getId: (e: IChatResponseProgressFileTreeData) => e.uri.toString() - }, - accessibilityProvider: { - getAriaLabel: (element: IChatResponseProgressFileTreeData) => element.label, - getWidgetAriaLabel: () => localize('treeAriaLabel', "File Tree") - }, - alwaysConsumeMouseWheel: false + ...options, + classNames: ThemeIcon.asClassNameArray(Codicon.thumbsdown), }); - - return tree; - } - - get(): IDisposableReference> { - const object = this._pool.get(); - let stale = false; - return { - object, - isStale: () => stale, - dispose: () => { - stale = true; - this._pool.release(object); - } - }; - } -} - -class ContentReferencesListPool extends Disposable { - private _pool: ResourcePool>; - - public get inUse(): ReadonlySet> { - return this._pool.inUse; - } - - constructor( - private _onDidChangeVisibility: Event, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IThemeService private readonly themeService: IThemeService, - ) { - super(); - this._pool = this._register(new ResourcePool(() => this.listFactory())); } - private listFactory(): WorkbenchList { - const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility })); - - const container = $('.chat-used-context-list'); - this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); - - const list = this.instantiationService.createInstance( - WorkbenchList, - 'ChatListRenderer', - container, - new ContentReferencesListDelegate(), - [this.instantiationService.createInstance(ContentReferencesListRenderer, resourceLabels)], + getActions(): readonly IAction[] { + return [ + this.getVoteDownDetailAction(ChatAgentVoteDownReason.IncorrectCode), + this.getVoteDownDetailAction(ChatAgentVoteDownReason.DidNotFollowInstructions), + this.getVoteDownDetailAction(ChatAgentVoteDownReason.IncompleteCode), + this.getVoteDownDetailAction(ChatAgentVoteDownReason.MissingContext), + this.getVoteDownDetailAction(ChatAgentVoteDownReason.PoorlyWrittenOrFormatted), + this.getVoteDownDetailAction(ChatAgentVoteDownReason.RefusedAValidRequest), + this.getVoteDownDetailAction(ChatAgentVoteDownReason.OffensiveOrUnsafe), + this.getVoteDownDetailAction(ChatAgentVoteDownReason.Other), { - alwaysConsumeMouseWheel: false, - accessibilityProvider: { - getAriaLabel: (element: IChatContentReference | IChatWarningMessage) => { - if (element.kind === 'warning') { - return element.content.value; - } - const reference = element.reference; - if ('variableName' in reference) { - return reference.variableName; - } else if (URI.isUri(reference)) { - return basename(reference.path); - } else { - return basename(reference.uri.path); - } - }, - - getWidgetAriaLabel: () => localize('usedReferences', "Used References") - }, - dnd: { - getDragURI: (element: IChatContentReference | IChatWarningMessage) => { - if (element.kind === 'warning') { - return null; - } - const { reference } = element; - if ('variableName' in reference) { - return null; - } else if (URI.isUri(reference)) { - return reference.toString(); - } else { - return reference.uri.toString(); - } - }, - dispose: () => { }, - onDragOver: () => false, - drop: () => { }, - }, - }); - - return list; - } - - get(): IDisposableReference> { - const object = this._pool.get(); - let stale = false; - return { - object, - isStale: () => stale, - dispose: () => { - stale = true; - this._pool.release(object); - } - }; - } -} - -class ContentReferencesListDelegate implements IListVirtualDelegate { - getHeight(element: IChatContentReference): number { - return 22; - } - - getTemplateId(element: IChatContentReference): string { - return ContentReferencesListRenderer.TEMPLATE_ID; - } -} - -interface IChatContentReferenceListTemplate { - label: IResourceLabel; - templateDisposables: IDisposable; -} - -class ContentReferencesListRenderer implements IListRenderer { - static TEMPLATE_ID = 'contentReferencesListRenderer'; - readonly templateId: string = ContentReferencesListRenderer.TEMPLATE_ID; - - constructor( - private labels: ResourceLabels, - @IThemeService private readonly themeService: IThemeService, - @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, - ) { } - - renderTemplate(container: HTMLElement): IChatContentReferenceListTemplate { - const templateDisposables = new DisposableStore(); - const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true })); - return { templateDisposables, label }; - } - - - private getReferenceIcon(data: IChatContentReference): URI | ThemeIcon | undefined { - if (ThemeIcon.isThemeIcon(data.iconPath)) { - return data.iconPath; - } else { - return this.themeService.getColorTheme().type === ColorScheme.DARK && data.iconPath?.dark - ? data.iconPath?.dark - : data.iconPath?.light; - } - } - - renderElement(data: IChatContentReference | IChatWarningMessage, index: number, templateData: IChatContentReferenceListTemplate, height: number | undefined): void { - if (data.kind === 'warning') { - templateData.label.setResource({ name: data.content.value }, { icon: Codicon.warning }); - return; - } + id: 'reportIssue', + label: voteDownDetailLabels[ChatAgentVoteDownReason.WillReportIssue], + tooltip: '', + enabled: true, + class: undefined, + run: async (context: IChatResponseViewModel) => { + if (!isResponseVM(context)) { + this.logService.error('ChatVoteDownButton#run: invalid context'); + return; + } - const reference = data.reference; - const icon = this.getReferenceIcon(data); - templateData.label.element.style.display = 'flex'; - if ('variableName' in reference) { - if (reference.value) { - const uri = URI.isUri(reference.value) ? reference.value : reference.value.uri; - templateData.label.setResource( - { - resource: uri, - name: basenameOrAuthority(uri), - description: `#${reference.variableName}`, - range: 'range' in reference.value ? reference.value.range : undefined, - }, { icon }); - } else { - const variable = this.chatVariablesService.getVariable(reference.variableName); - templateData.label.setLabel(`#${reference.variableName}`, undefined, { title: variable?.description }); - } - } else { - const uri = 'uri' in reference ? reference.uri : reference; - if (matchesSomeScheme(uri, Schemas.mailto, Schemas.http, Schemas.https)) { - templateData.label.setResource({ resource: uri, name: uri.toString() }, { icon: icon ?? Codicon.globe }); - } else { - templateData.label.setFile(uri, { - fileKind: FileKind.FILE, - // Should not have this live-updating data on a historical reference - fileDecorations: { badges: false, colors: false }, - range: 'range' in reference ? reference.range : undefined - }); + await this.commandService.executeCommand(MarkUnhelpfulActionId, context, ChatAgentVoteDownReason.WillReportIssue); + await this.issueService.openReporter({ extensionId: context.agent?.extensionId.value }); + } } - } - } - - disposeTemplate(templateData: IChatContentReferenceListTemplate): void { - templateData.templateDisposables.dispose(); - } -} - -class ResourcePool extends Disposable { - private readonly pool: T[] = []; - - private _inUse = new Set; - public get inUse(): ReadonlySet { - return this._inUse; - } - - constructor( - private readonly _itemFactory: () => T, - ) { - super(); - } - - get(): T { - if (this.pool.length > 0) { - const item = this.pool.pop()!; - this._inUse.add(item); - return item; - } - - const item = this._register(this._itemFactory()); - this._inUse.add(item); - return item; - } - - release(item: T): void { - this._inUse.delete(item); - this.pool.push(item); + ]; } -} -class ChatVoteButton extends MenuEntryActionViewItem { override render(container: HTMLElement): void { super.render(container); - container.classList.toggle('checked', this.action.checked); - } -} - -class ChatListTreeDelegate implements IListVirtualDelegate { - static readonly ITEM_HEIGHT = 22; - getHeight(element: IChatResponseProgressFileTreeData): number { - return ChatListTreeDelegate.ITEM_HEIGHT; + this.element?.classList.toggle('checked', this.action.checked); } - getTemplateId(element: IChatResponseProgressFileTreeData): string { - return 'chatListTreeTemplate'; - } -} - -class ChatListTreeCompressionDelegate implements ITreeCompressionDelegate { - isIncompressible(element: IChatResponseProgressFileTreeData): boolean { - return !element.children; - } -} - -interface IChatListTreeRendererTemplate { - templateDisposables: DisposableStore; - label: IResourceLabel; -} - -class ChatListTreeRenderer implements ICompressibleTreeRenderer { - templateId: string = 'chatListTreeTemplate'; - - constructor(private labels: ResourceLabels, private decorations: IFilesConfiguration['explorer']['decorations']) { } - - renderCompressedElements(element: ITreeNode, void>, index: number, templateData: IChatListTreeRendererTemplate, height: number | undefined): void { - templateData.label.element.style.display = 'flex'; - const label = element.element.elements.map((e) => e.label); - templateData.label.setResource({ resource: element.element.elements[0].uri, name: label }, { - title: element.element.elements[0].label, - fileKind: element.children ? FileKind.FOLDER : FileKind.FILE, - extraClasses: ['explorer-item'], - fileDecorations: this.decorations - }); - } - renderTemplate(container: HTMLElement): IChatListTreeRendererTemplate { - const templateDisposables = new DisposableStore(); - const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true })); - return { templateDisposables, label }; - } - renderElement(element: ITreeNode, index: number, templateData: IChatListTreeRendererTemplate, height: number | undefined): void { - templateData.label.element.style.display = 'flex'; - if (!element.children.length && element.element.type !== FileType.Directory) { - templateData.label.setFile(element.element.uri, { - fileKind: FileKind.FILE, - hidePath: true, - fileDecorations: this.decorations, - }); - } else { - templateData.label.setResource({ resource: element.element.uri, name: element.element.label }, { - title: element.element.label, - fileKind: FileKind.FOLDER, - fileDecorations: this.decorations - }); - } - } - disposeTemplate(templateData: IChatListTreeRendererTemplate): void { - templateData.templateDisposables.dispose(); - } -} - -class ChatListTreeDataSource implements IAsyncDataSource { - hasChildren(element: IChatResponseProgressFileTreeData): boolean { - return !!element.children; - } + private getVoteDownDetailAction(reason: ChatAgentVoteDownReason): IAction { + const label = voteDownDetailLabels[reason]; + return { + id: MarkUnhelpfulActionId, + label, + tooltip: '', + enabled: true, + checked: (this._context as IChatResponseViewModel).voteDownReason === reason, + class: undefined, + run: async (context: IChatResponseViewModel) => { + if (!isResponseVM(context)) { + this.logService.error('ChatVoteDownButton#getVoteDownDetailAction: invalid context'); + return; + } - async getChildren(element: IChatResponseProgressFileTreeData): Promise> { - return element.children ?? []; + await this.commandService.executeCommand(MarkUnhelpfulActionId, context, reason); + } + }; } } - -function isInteractiveProgressTreeData(item: Object): item is IChatResponseProgressFileTreeData { - return 'label' in item; -} - -function contentToMarkdown(str: string | IMarkdownString): IMarkdownString { - return typeof str === 'string' ? { value: str } : str; -} - -function isProgressMessage(item: any): item is IChatProgressMessage { - return item && 'kind' in item && item.kind === 'progressMessage'; -} - -function isProgressTaskRenderData(item: any): item is IChatTaskRenderData { - return item && 'isSettled' in item; -} - -function isWarningRenderData(item: any): item is IChatWarningMessage { - return item && 'kind' in item && item.kind === 'warning'; -} - -function isProgressMessageRenderData(item: IChatRenderData): item is IChatProgressMessageRenderData { - return item && 'isAtEndOfResponse' in item; -} - -function isCommandButtonRenderData(item: IChatRenderData): item is IChatCommandButton { - return item && 'kind' in item && item.kind === 'command'; -} - -function isTextEditRenderData(item: IChatRenderData): item is IChatTextEditGroup { - return item && 'kind' in item && item.kind === 'textEditGroup'; -} - -function isConfirmationRenderData(item: IChatRenderData): item is IChatConfirmation { - return item && 'kind' in item && item.kind === 'confirmation'; -} - -function isMarkdownRenderData(item: IChatRenderData): item is IChatResponseMarkdownRenderData { - return item && 'renderedWordCount' in item; -} - -function onlyProgressMessagesAfterI(items: ReadonlyArray, i: number): boolean { - return items.slice(i).every(isProgressMessage); -} - -function onlyProgressMessageRenderDatasAfterI(items: ReadonlyArray, i: number): boolean { - return items.slice(i).every(isProgressMessageRenderData); -} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index 25c4ac91..822bbe1c 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -7,10 +7,12 @@ import * as dom from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { URI } from 'vs/base/common/uri'; import { Location } from 'vs/editor/common/languages'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -21,11 +23,11 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatAgentHover, getChatAgentHoverOptions } from 'vs/workbench/contrib/chat/browser/chatAgentHover'; import { getFullyQualifiedId, IChatAgentCommand, IChatAgentData, IChatAgentNameService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors'; -import { chatAgentLeader, ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestTextPart, chatSubcommandLeader, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { chatAgentLeader, ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestVariablePart, chatSubcommandLeader, IParsedChatRequest, IParsedChatRequestPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { contentRefUrl } from '../common/annotations'; -import { Lazy } from 'vs/base/common/lazy'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; /** For rendering slash commands, variables */ const decorationRefUrl = `http://_vscodedecoration_`; @@ -42,7 +44,7 @@ export function agentToMarkdown(agent: IChatAgentData, isClickable: boolean, acc const isAllowed = chatAgentNameService.getAgentNameRestriction(agent); let name = `${isAllowed ? agent.name : getFullyQualifiedId(agent)}`; - const isDupe = isAllowed && chatAgentService.getAgentsByName(agent.name).length > 1; + const isDupe = isAllowed && chatAgentService.agentHasDupeName(agent.id); if (isDupe) { name += ` (${agent.publisherDisplayName})`; } @@ -68,6 +70,10 @@ interface ISlashCommandWidgetArgs { command: string; } +interface IDecorationWidgetArgs { + title?: string; +} + export class ChatMarkdownDecorationsRenderer { constructor( @IKeybindingService private readonly keybindingService: IKeybindingService, @@ -79,6 +85,8 @@ export class ChatMarkdownDecorationsRenderer { @IChatService private readonly chatService: IChatService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @ICommandService private readonly commandService: ICommandService, + @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, + @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, ) { } convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string { @@ -89,21 +97,29 @@ export class ChatMarkdownDecorationsRenderer { } else if (part instanceof ChatRequestAgentPart) { result += this.instantiationService.invokeFunction(accessor => agentToMarkdown(part.agent, false, accessor)); } else { - const uri = part instanceof ChatRequestDynamicVariablePart && part.data instanceof URI ? - part.data : - undefined; - const title = uri ? encodeURIComponent(this.labelService.getUriLabel(uri, { relative: true })) : - part instanceof ChatRequestAgentPart ? part.agent.id : - ''; - - const text = part.text; - result += `[${text}](${decorationRefUrl}?${title})`; + result += this.genericDecorationToMarkdown(part); } } return result; } + private genericDecorationToMarkdown(part: IParsedChatRequestPart): string { + const uri = part instanceof ChatRequestDynamicVariablePart && part.data instanceof URI ? + part.data : + undefined; + const title = uri ? this.labelService.getUriLabel(uri, { relative: true }) : + part instanceof ChatRequestSlashCommandPart ? part.slashCommand.detail : + part instanceof ChatRequestAgentSubcommandPart ? part.command.description : + part instanceof ChatRequestVariablePart ? (this.chatVariablesService.getVariable(part.variableName)?.description) : + part instanceof ChatRequestToolPart ? (this.toolsService.getTool(part.toolId)?.userDescription) : + ''; + + const args: IDecorationWidgetArgs = { title }; + const text = part.text; + return `[${text}](${decorationRefUrl}?${encodeURIComponent(JSON.stringify(args))})`; + } + walkTreeAndAnnotateReferenceLinks(element: HTMLElement): IDisposable { const store = new DisposableStore(); element.querySelectorAll('a').forEach(a => { @@ -136,9 +152,13 @@ export class ChatMarkdownDecorationsRenderer { a); } } else if (href.startsWith(decorationRefUrl)) { - const title = decodeURIComponent(href.slice(decorationRefUrl.length + 1)); + let args: IDecorationWidgetArgs | undefined; + try { + args = JSON.parse(decodeURIComponent(href.slice(decorationRefUrl.length + 1))); + } catch (e) { } + a.parentElement!.replaceChild( - this.renderResourceWidget(a.textContent!, title), + this.renderResourceWidget(a.textContent!, args, store), a); } else if (href.startsWith(contentRefUrl)) { this.renderFileWidget(href, a); @@ -172,12 +192,12 @@ export class ChatMarkdownDecorationsRenderer { this.chatService.sendRequest(widget.viewModel!.sessionId, agent.metadata.sampleRequest ?? '', { location: widget.location, agentId: agent.id }); })); } else { - container = this.renderResourceWidget(nameWithLeader, undefined); + container = this.renderResourceWidget(nameWithLeader, undefined, store); } const agent = this.chatAgentService.getAgent(args.agentId); const hover: Lazy = new Lazy(() => store.add(this.instantiationService.createInstance(ChatAgentHover))); - store.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('element'), container, () => { + store.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), container, () => { hover.value.setAgent(args.agentId); return hover.value.domNode; }, agent && getChatAgentHoverOptions(() => agent, this.commandService))); @@ -232,11 +252,11 @@ export class ChatMarkdownDecorationsRenderer { } - private renderResourceWidget(name: string, title: string | undefined): HTMLElement { + private renderResourceWidget(name: string, args: IDecorationWidgetArgs | undefined, store: DisposableStore): HTMLElement { const container = dom.$('span.chat-resource-widget'); const alias = dom.$('span', undefined, name); - if (title) { - alias.title = title; + if (args?.title) { + store.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), container, args.title)); } container.appendChild(alias); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts index 3e4b6a93..98e84b99 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts @@ -4,9 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownRenderOptions, MarkedOptions } from 'vs/base/browser/markdownRenderer'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMarkdownRendererOptions, IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITrustedDomainService } from 'vs/workbench/contrib/url/browser/trustedDomainService'; @@ -29,6 +32,8 @@ const allowedHtmlTags = [ 'p', 'pre', 'strong', + 'sub', + 'sup', 'table', 'tbody', 'td', @@ -54,6 +59,7 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { @ILanguageService languageService: ILanguageService, @IOpenerService openerService: IOpenerService, @ITrustedDomainService private readonly trustedDomainService: ITrustedDomainService, + @IHoverService private readonly hoverService: IHoverService, ) { super(options ?? {}, languageService, openerService); } @@ -73,9 +79,30 @@ export class ChatMarkdownRenderer extends MarkdownRenderer { ...markdown, // dompurify uses DOMParser, which strips leading comments. Wrapping it all in 'body' prevents this. - value: `${markdown.value}`, + // The \n\n prevents marked.js from parsing the body contents as just text in an 'html' token, instead of actual markdown. + value: `\n\n${markdown.value}`, } : markdown; - return super.render(mdWithBody, options, markedOptions); + const result = super.render(mdWithBody, options, markedOptions); + return this.attachCustomHover(result); + } + + private attachCustomHover(result: IMarkdownRenderResult): IMarkdownRenderResult { + const store = new DisposableStore(); + result.element.querySelectorAll('a').forEach((element) => { + if (element.title) { + const title = element.title; + element.title = ''; + store.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), element, title)); + } + }); + + return { + element: result.element, + dispose: () => { + result.dispose(); + store.dispose(); + } + }; } } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts index 43eb5d2b..eb5e0049 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isNonEmptyArray } from 'vs/base/common/arrays'; -import * as strings from 'vs/base/common/strings'; +import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; -import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import * as strings from 'vs/base/common/strings'; import { localize, localize2 } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService } from 'vs/platform/log/common/log'; +import { Severity } from 'vs/platform/notification/common/notification'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -20,12 +21,12 @@ import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer import { CHAT_VIEW_ID } from 'vs/workbench/contrib/chat/browser/chat'; import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { CONTEXT_CHAT_EXTENSION_INVALID, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IRawChatParticipantContribution } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; +import { showExtensionsWithIdsCommandId } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { Action } from 'vs/base/common/actions'; -import { ICommandService } from 'vs/platform/commands/common/commands'; const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatParticipants', @@ -45,7 +46,7 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi name: { description: localize('chatParticipantName', "User-facing name for this chat participant. The user will use '@' with this name to invoke the participant. Name must not contain whitespace."), type: 'string', - pattern: '^[\\w0-9_-]+$' + pattern: '^[\\w-]+$' }, fullName: { markdownDescription: localize('chatParticipantFullName', "The full name of this chat participant, which is shown as the label for responses coming from this participant. If not provided, {0} is used.", '`name`'), @@ -63,6 +64,34 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi description: localize('chatSampleRequest', "When the user clicks this participant in `/help`, this text will be submitted to the participant."), type: 'string' }, + when: { + description: localize('chatParticipantWhen', "A condition which must be true to enable this participant."), + type: 'string' + }, + disambiguation: { + description: localize('chatParticipantDisambiguation', "Metadata to help with automatically routing user questions to this chat participant. You must add `contribChatParticipantDetection` to `enabledApiProposals` to use this API."), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { categoryName: '', description: '', examples: [] } }], + required: ['categoryName', 'description', 'examples'], + properties: { + categoryName: { + markdownDescription: localize('chatParticipantDisambiguationCategory', "A detailed name for this category, e.g. `workspace_questions` or `web_questions`."), + type: 'string' + }, + description: { + description: localize('chatParticipantDisambiguationDescription', "A detailed description of the kinds of questions that are suitable for this chat participant."), + type: 'string' + }, + examples: { + description: localize('chatParticipantDisambiguationExamples', "A list of representative example questions that are suitable for this chat participant."), + type: 'array' + }, + } + } + }, commands: { markdownDescription: localize('chatCommandsDescription', "Commands available for this chat participant, which the user can invoke with a `/`."), type: 'array', @@ -92,9 +121,37 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi description: localize('chatCommandSticky', "Whether invoking the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message."), type: 'boolean' }, + disambiguation: { + description: localize('chatCommandDisambiguation', "Metadata to help with automatically routing user questions to this chat command. You must add `contribChatParticipantDetection` to `enabledApiProposals` to use this API."), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { categoryName: '', description: '', examples: [] } }], + required: ['categoryName', 'description', 'examples'], + properties: { + categoryName: { + markdownDescription: localize('chatCommandDisambiguationCategory', "A detailed name for this category, e.g. `workspace_questions` or `web_questions`."), + type: 'string' + }, + description: { + description: localize('chatCommandDisambiguationDescription', "A detailed description of the kinds of questions that are suitable for this chat command."), + type: 'string' + }, + examples: { + description: localize('chatCommandDisambiguationExamples', "A list of representative example questions that are suitable for this chat command."), + type: 'array' + }, + } + } + } } } }, + supportsToolReferences: { + description: localize('chatParticipantSupportsToolReferences', "Whether this participant supports {0}.", 'ChatRequest#toolReferences'), + type: 'boolean' + } } } }, @@ -109,86 +166,24 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatExtensionPointHandler'; - private readonly disposables = new DisposableStore(); - private _welcomeViewDescriptor?: IViewDescriptor; private _viewContainer: ViewContainer; private _participantRegistrationDisposables = new DisposableMap(); constructor( @IChatAgentService private readonly _chatAgentService: IChatAgentService, - @IProductService private readonly productService: IProductService, - @IContextKeyService private readonly contextService: IContextKeyService, - @ILogService private readonly logService: ILogService, - @INotificationService private readonly notificationService: INotificationService, - @ICommandService private readonly commandService: ICommandService, + @ILogService private readonly logService: ILogService ) { this._viewContainer = this.registerViewContainer(); - this.registerListeners(); + this.registerDefaultParticipantView(); this.handleAndRegisterChatExtensions(); } - private registerListeners() { - this.contextService.onDidChangeContext(e => { - - if (!this.productService.chatWelcomeView) { - return; - } - - const showWelcomeViewConfigKey = 'workbench.chat.experimental.showWelcomeView'; - const keys = new Set([showWelcomeViewConfigKey]); - if (e.affectsSome(keys)) { - const contextKeyExpr = ContextKeyExpr.equals(showWelcomeViewConfigKey, true); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - if (this.contextService.contextMatchesRules(contextKeyExpr)) { - this._welcomeViewDescriptor = { - id: CHAT_VIEW_ID, - name: { original: this.productService.chatWelcomeView.welcomeViewTitle, value: this.productService.chatWelcomeView.welcomeViewTitle }, - containerIcon: this._viewContainer.icon, - ctorDescriptor: new SyncDescriptor(ChatViewPane), - canToggleVisibility: false, - canMoveView: true, - order: 100 - }; - viewsRegistry.registerViews([this._welcomeViewDescriptor], this._viewContainer); - - viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, { - content: this.productService.chatWelcomeView.welcomeViewContent, - }); - } else if (this._welcomeViewDescriptor) { - viewsRegistry.deregisterViews([this._welcomeViewDescriptor], this._viewContainer); - } - } - }, null, this.disposables); - } - private handleAndRegisterChatExtensions(): void { chatParticipantExtensionPoint.setHandler((extensions, delta) => { for (const extension of delta.added) { - // Detect old version of Copilot Chat extension. - // TODO@roblourens remove after one release, after this we will rely on things like the API version - if (extension.value.some(participant => participant.id === 'github.copilot.default' && !participant.fullName)) { - this.notificationService.notify({ - severity: Severity.Error, - message: localize('chatFailErrorMessage', "Chat failed to load. Please ensure that the GitHub Copilot Chat extension is up to date."), - actions: { - primary: [ - new Action('showExtension', localize('action.showExtension', "Show Extension"), undefined, true, () => { - return this.commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', ['GitHub.copilot-chat']); - }) - ] - } - }); - continue; - } - - if (this.productService.quality === 'stable' && !isProposedApiEnabled(extension.description, 'chatParticipantPrivate')) { - this.logService.warn(`Chat participants are not yet enabled in VS Code Stable (${extension.description.identifier.value})`); - continue; - } - for (const providerDescriptor of extension.value) { - if (!providerDescriptor.name.match(/^[\w0-9_-]+$/)) { - this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT register participant with invalid name: ${providerDescriptor.name}. Name must match /^[\\w0-9_-]+$/.`); + if (!providerDescriptor.name?.match(/^[\w-]+$/)) { + this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT register participant with invalid name: ${providerDescriptor.name}. Name must match /^[\\w-]+$/.`); continue; } @@ -218,16 +213,35 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { continue; } - const store = new DisposableStore(); - if (providerDescriptor.isDefault && (!providerDescriptor.locations || providerDescriptor.locations?.includes(ChatAgentLocation.Panel))) { - store.add(this.registerDefaultParticipantView(providerDescriptor)); - } + const participantsAndCommandsDisambiguation: { + categoryName: string; + description: string; + examples: string[]; + }[] = []; - if (providerDescriptor.when && !isProposedApiEnabled(extension.description, 'chatParticipantAdditions')) { - this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT use API proposal: chatParticipantAdditions.`); - continue; + let hasLoggedParticipantDetectionApiWarning = false; + if (providerDescriptor.disambiguation?.length) { + if (isProposedApiEnabled(extension.description, 'contribChatParticipantDetection')) { + participantsAndCommandsDisambiguation.push(...providerDescriptor.disambiguation); + } else if (!hasLoggedParticipantDetectionApiWarning) { + this.logService.warn(`'${extension.description.identifier.value}' must add API proposal: 'contribChatParticipantDetection' to 'enabledApiProposals' to contribute disambiguation metadata.`); + hasLoggedParticipantDetectionApiWarning = true; + } + } + if (providerDescriptor.commands) { + for (const command of providerDescriptor.commands) { + if (command.disambiguation?.length) { + if (isProposedApiEnabled(extension.description, 'contribChatParticipantDetection')) { + participantsAndCommandsDisambiguation.push(...command.disambiguation); + } else if (!hasLoggedParticipantDetectionApiWarning) { + this.logService.warn(`'${extension.description.identifier.value}' must add API proposal: 'contribChatParticipantDetection' to 'enabledApiProposals' to contribute disambiguation metadata.`); + hasLoggedParticipantDetectionApiWarning = true; + } + } + } } + const store = new DisposableStore(); store.add(this._chatAgentService.registerAgent( providerDescriptor.id, { @@ -245,11 +259,12 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { name: providerDescriptor.name, fullName: providerDescriptor.fullName, isDefault: providerDescriptor.isDefault, - defaultImplicitVariables: providerDescriptor.defaultImplicitVariables, locations: isNonEmptyArray(providerDescriptor.locations) ? providerDescriptor.locations.map(ChatAgentLocation.fromRaw) : [ChatAgentLocation.Panel], - slashCommands: providerDescriptor.commands ?? [] + slashCommands: providerDescriptor.commands ?? [], + disambiguation: coalesce(participantsAndCommandsDisambiguation.flat()), + supportsToolReferences: providerDescriptor.supportsToolReferences, } satisfies IChatAgentData)); this._participantRegistrationDisposables.set( @@ -261,7 +276,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { for (const extension of delta.removed) { for (const providerDescriptor of extension.value) { - this._participantRegistrationDisposables.deleteAndDispose(getParticipantKey(extension.description.identifier, providerDescriptor.name)); + this._participantRegistrationDisposables.deleteAndDispose(getParticipantKey(extension.description.identifier, providerDescriptor.id)); } } }); @@ -285,15 +300,9 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { return viewContainer; } - private hasRegisteredDefaultParticipantView = false; - private registerDefaultParticipantView(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable { - if (this.hasRegisteredDefaultParticipantView) { - this.logService.warn(`Tried to register a second default chat participant view for "${defaultParticipantDescriptor.id}"`); - return Disposable.None; - } - - // Register View - const name = defaultParticipantDescriptor.fullName ?? defaultParticipantDescriptor.name; + private registerDefaultParticipantView(): IDisposable { + // Register View. Name must be hardcoded because we want to show it even when the extension fails to load due to an API version incompatibility. + const name = 'GitHub Copilot'; const viewDescriptor: IViewDescriptor[] = [{ id: CHAT_VIEW_ID, containerIcon: this._viewContainer.icon, @@ -303,12 +312,11 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(ChatViewPane), + when: ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID) }]; - this.hasRegisteredDefaultParticipantView = true; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); return toDisposable(() => { - this.hasRegisteredDefaultParticipantView = false; Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); }); } @@ -317,3 +325,42 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string { return `${extensionId.value}_${participantName}`; } + +export class ChatCompatibilityNotifier implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatCompatNotifier'; + + constructor( + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IContextKeyService contextKeyService: IContextKeyService, + @IChatAgentService chatAgentService: IChatAgentService, + @IProductService productService: IProductService, + ) { + // It may be better to have some generic UI for this, for any extension that is incompatible, + // but this is only enabled for Copilot Chat now and it needs to be obvious. + const isInvalid = CONTEXT_CHAT_EXTENSION_INVALID.bindTo(contextKeyService); + extensionsWorkbenchService.queryLocal().then(exts => { + const chat = exts.find(ext => ext.identifier.id === 'github.copilot-chat'); + if (chat?.local?.validations.some(v => v[0] === Severity.Error)) { + const showExtensionLabel = localize('showExtension', "Show Extension"); + const mainMessage = localize('chatFailErrorMessage', "Chat failed to load because the installed version of the {0} extension is not compatible with this version of {1}. Please ensure that the GitHub Copilot Chat extension is up to date.", 'GitHub Copilot Chat', productService.nameLong); + const commandButton = `[${showExtensionLabel}](command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([['GitHub.copilot-chat']]))})`; + const versionMessage = `GitHub Copilot Chat version: ${chat.version}`; + const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, { + content: [mainMessage, commandButton, versionMessage].join('\n\n'), + when: CONTEXT_CHAT_EXTENSION_INVALID, + }); + + // This catches vscode starting up with the invalid extension, but the extension may still get updated by vscode after this. + isInvalid.set(true); + } + }); + + const listener = chatAgentService.onDidChangeAgents(() => { + if (chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { + isInvalid.set(false); + listener.dispose(); + } + }); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatQuick.ts index f40ad589..a3861b5d 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -219,7 +219,7 @@ class QuickChat extends Disposable { scopedInstantiationService.createInstance( ChatWidget, ChatAgentLocation.Panel, - { resource: true }, + { isQuickChat: true }, { renderInputOnTop: true, renderStyle: 'compact', menus: { inputSideToolbar: MenuId.ChatInputSide } }, { listForeground: quickInputForeground, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts index 009d9f4c..ad6462a3 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts @@ -5,15 +5,15 @@ import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; -import { alertAccessibleViewFocusChange, IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; +import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IChatWidgetService, IChatWidget } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatWidgetService, IChatWidget, ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { Disposable } from 'vs/base/common/lifecycle'; export class ChatResponseAccessibleView implements IAccessibleViewImplentation { readonly priority = 100; @@ -22,78 +22,90 @@ export class ChatResponseAccessibleView implements IAccessibleViewImplentation { readonly when = CONTEXT_IN_CHAT_SESSION; getProvider(accessor: ServicesAccessor) { const widgetService = accessor.get(IChatWidgetService); - const codeEditorService = accessor.get(ICodeEditorService); - return resolveProvider(widgetService, codeEditorService, true); - function resolveProvider(widgetService: IChatWidgetService, codeEditorService: ICodeEditorService, initialRender?: boolean) { - const widget = widgetService.lastFocusedWidget; - if (!widget) { - return; - } - const chatInputFocused = initialRender && !!codeEditorService.getFocusedCodeEditor(); - if (initialRender && chatInputFocused) { - widget.focusLastMessage(); - } + const widget = widgetService.lastFocusedWidget; + if (!widget) { + return; + } + const chatInputFocused = widget.hasInputFocus(); + if (chatInputFocused) { + widget.focusLastMessage(); + } - if (!widget) { - return; - } + const verifiedWidget: IChatWidget = widget; + const focusedItem = verifiedWidget.getFocus(); - const verifiedWidget: IChatWidget = widget; - const focusedItem = verifiedWidget.getFocus(); + if (!focusedItem) { + return; + } - if (!focusedItem) { - return; - } + return new ChatResponseAccessibleProvider(verifiedWidget, focusedItem, chatInputFocused); + } +} + +class ChatResponseAccessibleProvider extends Disposable implements IAccessibleViewContentProvider { + private _focusedItem: ChatTreeItem; + constructor( + private readonly _widget: IChatWidget, + item: ChatTreeItem, + private readonly _chatInputFocused: boolean + ) { + super(); + this._focusedItem = item; + } + + readonly id = AccessibleViewProviderId.Chat; + readonly verbositySettingKey = AccessibilityVerbositySettingId.Chat; + readonly options = { type: AccessibleViewType.View }; + + provideContent(): string { + return this._getContent(this._focusedItem); + } - widget.focus(focusedItem); - const isWelcome = focusedItem instanceof ChatWelcomeMessageModel; - let responseContent = isResponseVM(focusedItem) ? focusedItem.response.asString() : undefined; - if (isWelcome) { - const welcomeReplyContents = []; - for (const content of focusedItem.content) { - if (Array.isArray(content)) { - welcomeReplyContents.push(...content.map(m => m.message)); - } else { - welcomeReplyContents.push((content as IMarkdownString).value); - } + private _getContent(item: ChatTreeItem): string { + const isWelcome = item instanceof ChatWelcomeMessageModel; + let responseContent = isResponseVM(item) ? item.response.toString() : ''; + if (isWelcome) { + const welcomeReplyContents = []; + for (const content of item.content) { + if (Array.isArray(content)) { + welcomeReplyContents.push(...content.map(m => m.message)); + } else { + welcomeReplyContents.push((content as IMarkdownString).value); } - responseContent = welcomeReplyContents.join('\n'); - } - if (!responseContent && 'errorDetails' in focusedItem && focusedItem.errorDetails) { - responseContent = focusedItem.errorDetails.message; } - if (!responseContent) { - return; - } - const responses = verifiedWidget.viewModel?.getItems().filter(i => isResponseVM(i)); - const length = responses?.length; - const responseIndex = responses?.findIndex(i => i === focusedItem); + responseContent = welcomeReplyContents.join('\n'); + } + if (!responseContent && 'errorDetails' in item && item.errorDetails) { + responseContent = item.errorDetails.message; + } + return renderMarkdownAsPlaintext(new MarkdownString(responseContent), true); + } - return { - id: AccessibleViewProviderId.Chat, - verbositySettingKey: AccessibilityVerbositySettingId.Chat, - provideContent(): string { return renderMarkdownAsPlaintext(new MarkdownString(responseContent), true); }, - onClose() { - verifiedWidget.reveal(focusedItem); - if (chatInputFocused) { - verifiedWidget.focusInput(); - } else { - verifiedWidget.focus(focusedItem); - } - }, - next() { - verifiedWidget.moveFocus(focusedItem, 'next'); - alertAccessibleViewFocusChange(responseIndex, length, 'next'); - resolveProvider(widgetService, codeEditorService); - }, - previous() { - verifiedWidget.moveFocus(focusedItem, 'previous'); - alertAccessibleViewFocusChange(responseIndex, length, 'previous'); - resolveProvider(widgetService, codeEditorService); - }, - options: { type: AccessibleViewType.View } - }; + onClose(): void { + this._widget.reveal(this._focusedItem); + if (this._chatInputFocused) { + this._widget.focusInput(); + } else { + this._widget.focus(this._focusedItem); } } - dispose() { } + + provideNextContent(): string | undefined { + const next = this._widget.getSibling(this._focusedItem, 'next'); + if (next) { + this._focusedItem = next; + return this._getContent(next); + } + return; + } + + providePreviousContent(): string | undefined { + const previous = this._widget.getSibling(this._focusedItem, 'previous'); + if (previous) { + this._focusedItem = previous; + return this._getContent(previous); + } + return; + } } + diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 61b4ee19..98a72786 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -15,11 +15,13 @@ import { IChatWidgetService, showChatView } from 'vs/workbench/contrib/chat/brow import { ChatDynamicVariableModel } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatModel, IChatRequestVariableData, IChatRequestVariableEntry } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestDynamicVariablePart, ChatRequestVariablePart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestDynamicVariablePart, ChatRequestToolPart, ChatRequestVariablePart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatContentReference } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IChatVariableResolverProgress, IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; import { ChatContextAttachments } from 'vs/workbench/contrib/chat/browser/contrib/chatContextAttachments'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; +import { ThemeIcon } from 'vs/base/common/themables'; interface IChatData { data: IChatVariableData; @@ -34,6 +36,7 @@ export class ChatVariablesService implements IChatVariablesService { constructor( @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IViewsService private readonly viewsService: IViewsService, + @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, ) { } @@ -56,12 +59,17 @@ export class ChatVariablesService implements IChatVariablesService { }; jobs.push(data.resolver(prompt.text, part.variableArg, model, variableProgressCallback, token).then(value => { if (value) { - resolvedVariables[i] = { id: data.data.id, modelDescription: data.data.modelDescription, name: part.variableName, range: part.range, value, references }; + resolvedVariables[i] = { id: data.data.id, modelDescription: data.data.modelDescription, name: part.variableName, range: part.range, value, references, fullName: data.data.fullName, icon: data.data.icon }; } }).catch(onUnexpectedExternalError)); } } else if (part instanceof ChatRequestDynamicVariablePart) { - resolvedVariables[i] = { id: part.id, name: part.referenceText, range: part.range, value: part.data }; + resolvedVariables[i] = { id: part.id, name: part.referenceText, range: part.range, value: part.data, }; + } else if (part instanceof ChatRequestToolPart) { + const tool = this.toolsService.getTool(part.toolId); + if (tool) { + resolvedVariables[i] = { id: part.toolId, name: part.toolName, range: part.range, value: undefined, isTool: true, icon: ThemeIcon.isThemeIcon(tool.icon) ? tool.icon : undefined, fullName: tool.displayName }; + } } }); @@ -80,21 +88,25 @@ export class ChatVariablesService implements IChatVariablesService { }; jobs.push(data.resolver(prompt.text, '', model, variableProgressCallback, token).then(value => { if (value) { - resolvedAttachedContext[i] = { id: data.data.id, modelDescription: data.data.modelDescription, name: attachment.name, range: attachment.range, value, references }; + resolvedAttachedContext[i] = { id: data.data.id, modelDescription: data.data.modelDescription, name: attachment.name, fullName: attachment.fullName, range: attachment.range, value, references, icon: attachment.icon }; } }).catch(onUnexpectedExternalError)); - } else if (attachment.isDynamic) { - resolvedAttachedContext[i] = { id: attachment.id, name: attachment.name, value: attachment.value }; + } else if (attachment.isDynamic || attachment.isTool) { + resolvedAttachedContext[i] = { ...attachment }; } }); await Promise.allSettled(jobs); + // Make array not sparse resolvedVariables = coalesce(resolvedVariables); // "reverse", high index first so that replacement is simple resolvedVariables.sort((a, b) => b.range!.start - a.range!.start); - resolvedVariables.push(...resolvedAttachedContext); + + // resolvedAttachedContext is a sparse array + resolvedVariables.push(...coalesce(resolvedAttachedContext)); + return { variables: resolvedVariables, @@ -118,9 +130,12 @@ export class ChatVariablesService implements IChatVariablesService { return this._resolver.get(name.toLowerCase())?.data; } - getVariables(): Iterable> { + getVariables(location: ChatAgentLocation): Iterable> { const all = Iterable.map(this._resolver.values(), data => data.data); - return Iterable.filter(all, data => !data.hidden); + return Iterable.filter(all, data => { + // TODO@jrieken this is improper and should be know from the variable registeration data + return location !== ChatAgentLocation.Editor || !new Set(['selection', 'editor']).has(data.name); + }); } getDynamicVariables(sessionId: string): ReadonlyArray { @@ -157,8 +172,7 @@ export class ChatVariablesService implements IChatVariablesService { return; } - await showChatView(this.viewsService); - const widget = this.chatWidgetService.lastFocusedWidget; + const widget = await showChatView(this.viewsService); if (!widget || !widget.viewModel) { return; } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index b0c54404..68fb88bd 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -22,7 +22,6 @@ import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/vie import { Memento } from 'vs/workbench/common/memento'; import { SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { IChatViewPane } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CHAT_PROVIDER_ID } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; @@ -35,7 +34,7 @@ interface IViewPaneState extends IChatViewState { } export const CHAT_SIDEBAR_PANEL_ID = 'workbench.panel.chatSidebar'; -export class ChatViewPane extends ViewPane implements IChatViewPane { +export class ChatViewPane extends ViewPane { private _widget!: ChatWidget; get widget(): ChatWidget { return this._widget; } @@ -89,8 +88,9 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { } else if (this._widget?.viewModel?.initState === ChatModelInitState.Initialized) { // Model is initialized, and the default agent disappeared, so show welcome view this.didUnregisterProvider = true; - this._onDidChangeViewWelcomeState.fire(); } + + this._onDidChangeViewWelcomeState.fire(); })); } @@ -100,7 +100,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { }; } - private updateModel(model?: IChatModel | undefined, viewState?: IViewPaneState): void { + private updateModel(model?: IChatModel | undefined): void { this.modelDisposables.clear(); model = model ?? (this.chatService.transferredSessionData?.sessionId @@ -110,11 +110,15 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { throw new Error('Could not start chat session'); } - this._widget.setModel(model, { ...(viewState ?? this.viewState) }); + this._widget.setModel(model, { ...this.viewState }); this.viewState.sessionId = model.sessionId; } override shouldShowWelcome(): boolean { + if (!this.chatAgentService.getContributedDefaultAgent(ChatAgentLocation.Panel)) { + return true; + } + const noPersistedSessions = !this.chatService.hasSessions(); return this.didUnregisterProvider || !this._widget?.viewModel && (noPersistedSessions || this.didProviderRegistrationFail); } @@ -134,7 +138,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { try { super.renderBody(parent); - const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); + const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); const locationBasedColors = this.getLocationBasedColors(); this._widget = this._register(scopedInstantiationService.createInstance( ChatWidget, @@ -176,11 +180,14 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { this._widget.acceptInput(query); } - clear(): void { + private clear(): void { if (this.widget.viewModel) { this.chatService.clearSession(this.widget.viewModel.sessionId); } - this.updateModel(undefined, { ...this.viewState, inputValue: undefined }); + + // Grab the widget's latest view state because it will be loaded back into the widget + this.updateViewState(); + this.updateModel(undefined); } loadSession(sessionId: string): void { @@ -212,12 +219,16 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { // TODO multiple chat views will overwrite each other this._widget.saveState(); - const widgetViewState = this._widget.getViewState(); - this.viewState.inputValue = widgetViewState.inputValue; - this.viewState.inputState = widgetViewState.inputState; + this.updateViewState(); this.memento.saveMemento(); } super.saveState(); } + + private updateViewState(): void { + const widgetViewState = this._widget.getViewState(); + this.viewState.inputValue = widgetViewState.inputValue; + this.viewState.inputState = widgetViewState.inputState; + } } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 830664ed..56b0bf44 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -9,7 +9,7 @@ import { disposableTimeout, timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { matchesScheme, Schemas } from 'vs/base/common/network'; +import { Schemas } from 'vs/base/common/network'; import { extUri, isEqual } from 'vs/base/common/resources'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -26,7 +26,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions, IChatListItemRendererOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatAccessibilityProvider } from 'vs/workbench/contrib/chat/browser/chatAccessibilityProvider'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatListDelegate, ChatListItemRenderer, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; @@ -34,13 +34,12 @@ import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModelInitState, IChatModel, IChatRequestVariableEntry, IChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, formatChatQuestion } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatLocationData, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; -import { IChatListItemRendererOptions } from './chat'; const $ = dom.$; @@ -75,12 +74,24 @@ export interface IChatWidgetContrib extends IDisposable { setInputState?(s: any): void; } +export interface IChatWidgetLocationOptions { + location: ChatAgentLocation; + resolveData?(): IChatLocationData | undefined; +} + +export function isQuickChat(widget: IChatWidget): boolean { + return 'viewContext' in widget && 'isQuickChat' in widget.viewContext && Boolean(widget.viewContext.isQuickChat); +} + export class ChatWidget extends Disposable implements IChatWidget { public static readonly CONTRIBS: { new(...args: [IChatWidget, ...any]): IChatWidgetContrib }[] = []; private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>()); public readonly onDidSubmitAgent = this._onDidSubmitAgent.event; + private _onDidChangeAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>()); + readonly onDidChangeAgent = this._onDidChangeAgent.event; + private _onDidFocus = this._register(new Emitter()); readonly onDidFocus = this._onDidFocus.event; @@ -96,8 +107,8 @@ export class ChatWidget extends Disposable implements IChatWidget { private _onDidAcceptInput = this._register(new Emitter()); readonly onDidAcceptInput = this._onDidAcceptInput.event; - private _onDidDeleteContext = this._register(new Emitter()); - readonly onDidDeleteContext = this._onDidDeleteContext.event; + private _onDidChangeContext = this._register(new Emitter<{ removed?: IChatRequestVariableEntry[]; added?: IChatRequestVariableEntry[] }>()); + readonly onDidChangeContext = this._onDidChangeContext.event; private _onDidHide = this._register(new Emitter()); readonly onDidHide = this._onDidHide.event; @@ -105,13 +116,16 @@ export class ChatWidget extends Disposable implements IChatWidget { private _onDidChangeParsedInput = this._register(new Emitter()); readonly onDidChangeParsedInput = this._onDidChangeParsedInput.event; + private readonly _onWillMaybeChangeHeight = new Emitter(); + readonly onWillMaybeChangeHeight: Event = this._onWillMaybeChangeHeight.event; + private _onDidChangeHeight = this._register(new Emitter()); readonly onDidChangeHeight = this._onDidChangeHeight.event; private readonly _onDidChangeContentHeight = new Emitter(); readonly onDidChangeContentHeight: Event = this._onDidChangeContentHeight.event; - private contribs: IChatWidgetContrib[] = []; + private contribs: ReadonlyArray = []; private tree!: WorkbenchObjectTree; private renderer!: ChatListItemRenderer; @@ -171,9 +185,17 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.contextKeyService; } + private readonly _location: IChatWidgetLocationOptions; + + get location() { + return this._location.location; + } + + readonly viewContext: IChatWidgetViewContext; + constructor( - readonly location: ChatAgentLocation, - readonly viewContext: IChatWidgetViewContext, + location: ChatAgentLocation | IChatWidgetLocationOptions, + _viewContext: IChatWidgetViewContext | undefined, private readonly viewOptions: IChatWidgetViewOptions, private readonly styles: IChatWidgetStyles, @ICodeEditorService codeEditorService: ICodeEditorService, @@ -189,9 +211,18 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, ) { super(); + + this.viewContext = _viewContext ?? {}; + + if (typeof location === 'object') { + this._location = location; + } else { + this._location = { location }; + } + CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); - CONTEXT_CHAT_LOCATION.bindTo(contextKeyService).set(location); - CONTEXT_IN_QUICK_CHAT.bindTo(contextKeyService).set('resource' in viewContext); + CONTEXT_CHAT_LOCATION.bindTo(contextKeyService).set(this._location.location); + CONTEXT_IN_QUICK_CHAT.bindTo(contextKeyService).set(isQuickChat(this)); this.agentInInput = CONTEXT_CHAT_INPUT_HAS_AGENT.bindTo(contextKeyService); this.requestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); @@ -200,13 +231,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection)); this._register(codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, _source: ICodeEditor | null, _sideBySide?: boolean): Promise => { - let resource = input.resource; - - // if trying to open backing documents, actually open the real chat code block doc - if (matchesScheme(resource, Schemas.vscodeCopilotBackingChatCodeBlock)) { - resource = resource.with({ scheme: Schemas.vscodeChatCodeBlock }); - } - + const resource = input.resource; if (resource.scheme !== Schemas.vscodeChatCodeBlock) { return null; } @@ -227,18 +252,35 @@ export class ChatWidget extends Disposable implements IChatWidget { await timeout(0); // wait for list to actually render - for (const editor of this.renderer.editorsInUse() ?? []) { - if (extUri.isEqual(editor.uri, resource, true)) { - const inner = editor.editor; + for (const codeBlockPart of this.renderer.editorsInUse()) { + if (extUri.isEqual(codeBlockPart.uri, resource, true)) { + const editor = codeBlockPart.editor; + + let relativeTop = 0; + const editorDomNode = editor.getDomNode(); + if (editorDomNode) { + const row = dom.findParentWithClass(editorDomNode, 'monaco-list-row'); + if (row) { + relativeTop = dom.getTopLeftOffset(editorDomNode).top - dom.getTopLeftOffset(row).top; + } + } + if (input.options?.selection) { - inner.setSelection({ + const editorSelectionTopOffset = editor.getTopForPosition(input.options.selection.startLineNumber, input.options.selection.startColumn); + relativeTop += editorSelectionTopOffset; + + editor.focus(); + editor.setSelection({ startLineNumber: input.options.selection.startLineNumber, startColumn: input.options.selection.startColumn, - endLineNumber: input.options.selection.startLineNumber ?? input.options.selection.endLineNumber, - endColumn: input.options.selection.startColumn ?? input.options.selection.endColumn + endLineNumber: input.options.selection.endLineNumber ?? input.options.selection.startLineNumber, + endColumn: input.options.selection.endColumn ?? input.options.selection.startColumn }); } - return inner; + + this.reveal(item, relativeTop); + + return editor; } } return null; @@ -325,7 +367,7 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.inputPart.hasFocus(); } - moveFocus(item: ChatTreeItem, type: 'next' | 'previous'): void { + getSibling(item: ChatTreeItem, type: 'next' | 'previous'): ChatTreeItem | undefined { if (!isResponseVM(item)) { return; } @@ -342,7 +384,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (indexToFocus < 0 || indexToFocus > responseItems.length - 1) { return; } - this.focus(responseItems[indexToFocus]); + return responseItems[indexToFocus]; } clear(): void { @@ -363,6 +405,8 @@ export class ChatWidget extends Disposable implements IChatWidget { }; }); + this._onWillMaybeChangeHeight.fire(); + this.tree.setChildren(null, treeItems, { diffIdentityProvider: { getId: (element) => { @@ -375,7 +419,10 @@ export class ChatWidget extends Disposable implements IChatWidget { // be re-rendered so progressive rendering is restarted, even if the model wasn't updated. `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}` + // Re-render once content references are loaded - (isResponseVM(element) ? `_${element.contentReferences.length}` : ''); + (isResponseVM(element) ? `_${element.contentReferences.length}` : '') + + // Rerender request if we got new content references in the response + // since this may change how we render the corresponding attachments in the request + (isRequestVM(element) && element.contentReferences ? `_${element.contentReferences?.length}` : ''); }, } }); @@ -424,7 +471,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } private createList(listContainer: HTMLElement, options: IChatListItemRendererOptions): void { - const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]))); + const scopedInstantiationService = this._register(this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService])))); const delegate = scopedInstantiationService.createInstance(ChatListDelegate, this.viewOptions.defaultElementHeight ?? 200); const rendererDelegate: IChatRendererDelegate = { getListLength: () => this.tree.getNode(null).visibleChildrenCount, @@ -452,7 +499,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this._register(this.renderer.onDidClickRerunWithAgentOrCommandDetection(item => { const request = this.chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId); if (request) { - this.chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt, location: this.location }).catch(e => this.logService.error('FAILED to rerun request', e)); + this.chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: this.location }).catch(e => this.logService.error('FAILED to rerun request', e)); } })); @@ -538,27 +585,29 @@ export class ChatWidget extends Disposable implements IChatWidget { this._onDidChangeContentHeight.fire(); } - private createInput(container: HTMLElement, options?: { renderFollowups: boolean; renderStyle?: 'default' | 'compact' }): void { + private createInput(container: HTMLElement, options?: { renderFollowups: boolean; renderStyle?: 'default' | 'compact' | 'minimal' }): void { this.inputPart = this._register(this.instantiationService.createInstance(ChatInputPart, this.location, { renderFollowups: options?.renderFollowups ?? true, - renderStyle: options?.renderStyle, + renderStyle: options?.renderStyle === 'minimal' ? 'compact' : options?.renderStyle, menus: { executeToolbar: MenuId.ChatExecute, ...this.viewOptions.menus }, editorOverflowWidgetsDomNode: this.viewOptions.editorOverflowWidgetsDomNode, - } + }, + () => this.collectInputState() )); this.inputPart.render(container, '', this); this._register(this.inputPart.onDidLoadInputState(state => { this.contribs.forEach(c => { - if (c.setInputState && typeof state === 'object' && state?.[c.id]) { - c.setInputState(state[c.id]); + if (c.setInputState) { + const contribState = (typeof state === 'object' && state?.[c.id]) ?? {}; + c.setInputState(contribState); } }); })); this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); - this._register(this.inputPart.onDidDeleteContext((e) => this._onDidDeleteContext.fire(e))); + this._register(this.inputPart.onDidChangeContext((e) => this._onDidChangeContext.fire(e))); this._register(this.inputPart.onDidAcceptFollowup(e => { if (!this.viewModel) { return; @@ -593,6 +642,7 @@ export class ChatWidget extends Disposable implements IChatWidget { sessionId: this.viewModel.sessionId, requestId: e.response.requestId, agentId: e.response.agent?.id, + command: e.response.slashCommand?.name, result: e.response.result, action: { kind: 'followUp', @@ -646,12 +696,17 @@ export class ChatWidget extends Disposable implements IChatWidget { this.viewModel = undefined; this.onDidChangeItems(); })); - this.inputPart.setState(viewState.inputValue); + this.inputPart.initForNewChatModel(viewState.inputValue, viewState.inputState ?? this.collectInputState()); this.contribs.forEach(c => { if (c.setInputState && viewState.inputState?.[c.id]) { c.setInputState(viewState.inputState?.[c.id]); } }); + this.viewModelDisposables.add(model.onDidChange((e) => { + if (e.kind === 'setAgent') { + this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command }); + } + })); if (this.tree) { this.onDidChangeItems(); @@ -664,8 +719,8 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.tree.getFocus()[0] ?? undefined; } - reveal(item: ChatTreeItem): void { - this.tree.reveal(item); + reveal(item: ChatTreeItem, relativeTop?: number): void { + this.tree.reveal(item, relativeTop); } focus(item: ChatTreeItem): void { @@ -692,13 +747,17 @@ export class ChatWidget extends Disposable implements IChatWidget { } setInput(value = ''): void { - this.inputPart.setValue(value); + this.inputPart.setValue(value, false); } getInput(): string { return this.inputPart.inputEditor.getValue(); } + logInputHistory(): void { + this.inputPart.logInputHistory(); + } + async acceptInput(query?: string): Promise { return this._acceptInput(query ? { query } : undefined); } @@ -727,17 +786,27 @@ export class ChatWidget extends Disposable implements IChatWidget { 'query' in opts ? opts.query : `${opts.prefix} ${editorValue}`; const isUserQuery = !opts || 'prefix' in opts; - const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, { location: this.location, parserContext: { selectedAgent: this._lastSelectedAgent }, attachedContext: [...this.inputPart.attachedContext.values()] }); + const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, { + location: this.location, + locationData: this._location.resolveData?.(), + parserContext: { selectedAgent: this._lastSelectedAgent }, + attachedContext: [...this.inputPart.attachedContext.values()] + }); if (result) { - this.inputPart.attachedContext.clear(); - const inputState = this.collectInputState(); - this.inputPart.acceptInput(isUserQuery ? input : undefined, isUserQuery ? inputState : undefined); + this.inputPart.acceptInput(isUserQuery); this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand }); result.responseCompletePromise.then(() => { const responses = this.viewModel?.getItems().filter(isResponseVM); const lastResponse = responses?.[responses.length - 1]; this.chatAccessibilityService.acceptResponse(lastResponse, requestId); + if (lastResponse?.result?.nextQuestion) { + const { prompt, participant, command } = lastResponse.result.nextQuestion; + const question = formatChatQuestion(this.chatAgentService, this.location, prompt, participant, command); + if (question) { + this.input.setValue(question, false); + } + } }); return result.responseCreatedPromise; } @@ -745,16 +814,8 @@ export class ChatWidget extends Disposable implements IChatWidget { return undefined; } - setContext(overwrite: boolean, ...contentReferences: IChatRequestVariableEntry[]) { - if (overwrite) { - this.inputPart.attachedContext.clear(); - } - this.inputPart.attachContext(...contentReferences); - - if (this.bodyDimension) { - this.layout(this.bodyDimension.height, this.bodyDimension.width); - } + this.inputPart.attachContext(overwrite, ...contentReferences); } getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[] { @@ -915,11 +976,8 @@ export class ChatWidget extends Disposable implements IChatWidget { } getViewState(): IChatViewState { - this.inputPart.saveState(); return { inputValue: this.getInput(), inputState: this.collectInputState() }; } - - } export class ChatWidgetService implements IChatWidgetService { diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/codeBlockPart.css b/patched-vscode/src/vs/workbench/contrib/chat/browser/codeBlockPart.css index 279626da..35830dae 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/codeBlockPart.css +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/codeBlockPart.css @@ -15,7 +15,7 @@ .interactive-result-code-block .interactive-result-code-block-toolbar > .monaco-action-bar, .interactive-result-code-block .interactive-result-code-block-toolbar > .monaco-toolbar { position: absolute; - top: -13px; + top: -15px; height: 26px; line-height: 26px; background-color: var(--vscode-interactive-result-editor-background-color, var(--vscode-editor-background)); @@ -147,3 +147,17 @@ .interactive-result-code-block.compare .message A > CODE { color: var(--vscode-textLink-foreground); } + +.interactive-result-code-block.compare .interactive-result-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 3px; + box-sizing: border-box; + border-bottom: solid 1px var(--vscode-chat-requestBorder); +} + +.interactive-result-code-block.compare.no-diff .interactive-result-header, +.interactive-result-code-block.compare.no-diff .interactive-result-editor { + display: none; +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 46e306fd..5db94d51 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -6,24 +6,39 @@ import 'vs/css!./codeBlockPart'; import * as dom from 'vs/base/browser/dom'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { Button } from 'vs/base/browser/ui/button/button'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { combinedDisposable, Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { TabFocus } from 'vs/editor/browser/config/tabFocus'; +import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { IDiffEditorViewModel, ScrollType } from 'vs/editor/common/editorCommon'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { TextEdit } from 'vs/editor/common/languages'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; +import { TextModelText } from 'vs/editor/common/model/textModelText'; import { IModelService } from 'vs/editor/common/services/model'; +import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/browser/bracketMatching'; +import { ColorDetector } from 'vs/editor/contrib/colorPicker/browser/colorDetector'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; +import { MarginHoverController } from 'vs/editor/contrib/hover/browser/marginHoverController'; +import { LinkDetector } from 'vs/editor/contrib/links/browser/links'; +import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import { ViewportSemanticTokensContribution } from 'vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens'; import { SmartSelectController } from 'vs/editor/contrib/smartSelect/browser/smartSelect'; import { WordHighlighterContribution } from 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; @@ -33,36 +48,26 @@ import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { FileKind } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; +import { CONTEXT_CHAT_EDIT_APPLIED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { IChatResponseModel, IChatTextEditGroup } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { IMarkdownVulnerability } from '../common/annotations'; -import { TabFocus } from 'vs/editor/browser/config/tabFocus'; -import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; -import { CONTEXT_CHAT_EDIT_APPLIED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IChatResponseModel, IChatTextEditGroup } from 'vs/workbench/contrib/chat/common/chatModel'; -import { TextEdit } from 'vs/editor/common/languages'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { basename, isEqual } from 'vs/base/common/resources'; -import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { TextModelText } from 'vs/editor/common/model/textModelText'; -import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { toAction } from 'vs/base/common/actions'; +import { assertType } from 'vs/base/common/types'; const $ = dom.$; @@ -143,6 +148,9 @@ export class CodeBlockPart extends Disposable { private currentScrollWidth = 0; private readonly disposableStore = this._register(new DisposableStore()); + private isDisposed = false; + + private resourceContextKey: ResourceContextKey; constructor( private readonly options: ChatEditorOptions, @@ -158,8 +166,9 @@ export class CodeBlockPart extends Disposable { super(); this.element = $('.interactive-result-code-block'); + this.resourceContextKey = this._register(instantiationService.createInstance(ResourceContextKey)); this.contextKeyService = this._register(contextKeyService.createScoped(this.element)); - const scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService])); + const scopedInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]))); const editorElement = dom.append(this.element, $('.interactive-result-editor')); this.editor = this.createEditor(scopedInstantiationService, editorElement, { ...getSimpleEditorOptions(this.configurationService), @@ -189,7 +198,7 @@ export class CodeBlockPart extends Disposable { const toolbarElement = dom.append(this.element, $('.interactive-result-code-block-toolbar')); const editorScopedService = this.editor.contextKeyService.createScoped(toolbarElement); - const editorScopedInstantiationService = scopedInstantiationService.createChild(new ServiceCollection([IContextKeyService, editorScopedService])); + const editorScopedInstantiationService = this._register(scopedInstantiationService.createChild(new ServiceCollection([IContextKeyService, editorScopedService]))); this.toolbar = this._register(editorScopedInstantiationService.createInstance(MenuWorkbenchToolBar, toolbarElement, menuId, { menuOptions: { shouldForwardArgs: true @@ -263,6 +272,11 @@ export class CodeBlockPart extends Disposable { } } + override dispose() { + this.isDisposed = true; + super.dispose(); + } + get uri(): URI | undefined { return this.editor.getModel()?.uri; } @@ -279,9 +293,12 @@ export class CodeBlockPart extends Disposable { ViewportSemanticTokensContribution.ID, BracketMatchingController.ID, SmartSelectController.ID, - HoverController.ID, + ContentHoverController.ID, + MarginHoverController.ID, MessageController.ID, GotoDefinitionAtPositionEditorContribution.ID, + ColorDetector.ID, + LinkDetector.ID, ]) })); } @@ -354,6 +371,9 @@ export class CodeBlockPart extends Disposable { } await this.updateEditor(data); + if (this.isDisposed) { + return; + } this.layout(width); if (editable) { @@ -385,7 +405,8 @@ export class CodeBlockPart extends Disposable { } private clearWidgets() { - HoverController.get(this.editor)?.hideContentHover(); + ContentHoverController.get(this.editor)?.hideContentHover(); + MarginHoverController.get(this.editor)?.hideContentHover(); } private async updateEditor(data: ICodeBlockData): Promise { @@ -402,6 +423,7 @@ export class CodeBlockPart extends Disposable { element: data.element, languageId: textModel.getLanguageId() } satisfies ICodeBlockActionContext; + this.resourceContextKey.set(textModel.uri); } private getVulnerabilitiesLabel(): string { @@ -458,22 +480,23 @@ export interface ICodeCompareBlockData { readonly diffData: Promise; readonly parentContextKeyService?: IContextKeyService; - readonly hideToolbar?: boolean; + // readonly hideToolbar?: boolean; } +// long-lived object that sits in the DiffPool and that gets reused export class CodeCompareBlockPart extends Disposable { protected readonly _onDidChangeContentHeight = this._register(new Emitter()); public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; private readonly contextKeyService: IContextKeyService; private readonly diffEditor: DiffEditorWidget; - private readonly toolbar1: ActionBar; - private readonly toolbar2: MenuWorkbenchToolBar; + private readonly resourceLabel: ResourceLabel; + private readonly toolbar: MenuWorkbenchToolBar; readonly element: HTMLElement; private readonly messageElement: HTMLElement; - private readonly _lastDiffEditorViewModel = this._store.add(new MutableDisposable()); + private readonly _lastDiffEditorViewModel = this._store.add(new MutableDisposable()); private currentScrollWidth = 0; constructor( @@ -498,7 +521,8 @@ export class CodeCompareBlockPart extends Disposable { this.messageElement.tabIndex = 0; this.contextKeyService = this._register(contextKeyService.createScoped(this.element)); - const scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService])); + const scopedInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]))); + const editorHeader = dom.append(this.element, $('.interactive-result-header.show-file-icons')); const editorElement = dom.append(this.element, $('.interactive-result-editor')); this.diffEditor = this.createDiffEditor(scopedInstantiationService, editorElement, { ...getSimpleEditorOptions(this.configurationService), @@ -525,24 +549,16 @@ export class CodeCompareBlockPart extends Disposable { ...this.getEditorOptionsFromConfig(), }); - const toolbarElement = dom.append(this.element, $('.interactive-result-code-block-toolbar')); + this.resourceLabel = this._register(scopedInstantiationService.createInstance(ResourceLabel, editorHeader, { supportIcons: true })); - // this.resourceLabel = this._register(scopedInstantiationService.createInstance(ResourceLabel, toolbarElement, { supportIcons: true })); - - const editorScopedService = this.diffEditor.getModifiedEditor().contextKeyService.createScoped(toolbarElement); - const editorScopedInstantiationService = scopedInstantiationService.createChild(new ServiceCollection([IContextKeyService, editorScopedService])); - this.toolbar1 = this._register(new ActionBar(toolbarElement, {})); - this.toolbar2 = this._register(editorScopedInstantiationService.createInstance(MenuWorkbenchToolBar, toolbarElement, menuId, { + const editorScopedService = this.diffEditor.getModifiedEditor().contextKeyService.createScoped(editorHeader); + const editorScopedInstantiationService = this._register(scopedInstantiationService.createChild(new ServiceCollection([IContextKeyService, editorScopedService]))); + this.toolbar = this._register(editorScopedInstantiationService.createInstance(MenuWorkbenchToolBar, editorHeader, menuId, { menuOptions: { shouldForwardArgs: true } })); - - this._register(this.toolbar2.onDidChangeDropdownVisibility(e => { - toolbarElement.classList.toggle('force-visibility', e); - })); - this._configureForScreenReader(); this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => this._configureForScreenReader())); this._register(this.configurationService.onDidChangeConfiguration((e) => { @@ -598,7 +614,8 @@ export class CodeCompareBlockPart extends Disposable { ViewportSemanticTokensContribution.ID, BracketMatchingController.ID, SmartSelectController.ID, - HoverController.ID, + ContentHoverController.ID, + MarginHoverController.ID, GotoDefinitionAtPositionEditorContribution.ID, ]) }; @@ -614,9 +631,16 @@ export class CodeCompareBlockPart extends Disposable { diffAlgorithm: 'advanced', readOnly: false, isInEmbeddedEditor: true, - useInlineViewWhenSpaceIsLimited: false, + useInlineViewWhenSpaceIsLimited: true, + experimental: { + useTrueInlineView: true, + }, + renderSideBySideInlineBreakpoint: 300, + renderOverviewRuler: false, + compactMode: true, hideUnchangedRegions: { enabled: true, contextLineCount: 1 }, renderGutterMenu: false, + lineNumbersMinChars: 1, ...options }, { originalEditor: widgetOptions, modifiedEditor: widgetOptions })); } @@ -637,7 +661,7 @@ export class CodeCompareBlockPart extends Disposable { } private _configureForScreenReader(): void { - const toolbarElt = this.toolbar2.getElement(); + const toolbarElt = this.toolbar.getElement(); if (this.accessibilityService.isScreenReaderOptimized()) { toolbarElt.style.display = 'block'; toolbarElt.ariaLabel = this.configurationService.getValue(AccessibilityVerbositySettingId.Chat) ? localize('chat.codeBlock.toolbarVerbose', 'Toolbar for code block which can be reached via tab') : localize('chat.codeBlock.toolbar', 'Code block toolbar'); @@ -671,7 +695,9 @@ export class CodeCompareBlockPart extends Disposable { } private getContentHeight() { - return this.diffEditor.getContentHeight(); + return this.diffEditor.getModel() + ? this.diffEditor.getContentHeight() + : dom.getTotalHeight(this.messageElement); } async render(data: ICodeCompareBlockData, width: number, token: CancellationToken) { @@ -690,21 +716,10 @@ export class CodeCompareBlockPart extends Disposable { this.layout(width); this.diffEditor.updateOptions({ ariaLabel: localize('chat.compareCodeBlockLabel', "Code Edits") }); - this.toolbar1.clear(); - this.toolbar1.push(toAction({ - label: basename(data.edit.uri), - tooltip: localize('chat.edit.tooltip', "Open '{0}'", this.labelService.getUriLabel(data.edit.uri, { relative: true })), - run: () => { - this.openerService.open(data.edit.uri, { fromUserGesture: true, allowCommands: false }); - }, - id: '', - }), { icon: false, label: true }); - - if (data.hideToolbar) { - dom.hide(this.toolbar2.getElement()); - } else { - dom.show(this.toolbar2.getElement()); - } + this.resourceLabel.element.setFile(data.edit.uri, { + fileKind: FileKind.FILE, + fileDecorations: { colors: true, badges: false } + }); } reset() { @@ -712,8 +727,10 @@ export class CodeCompareBlockPart extends Disposable { } private clearWidgets() { - HoverController.get(this.diffEditor.getOriginalEditor())?.hideContentHover(); - HoverController.get(this.diffEditor.getModifiedEditor())?.hideContentHover(); + ContentHoverController.get(this.diffEditor.getOriginalEditor())?.hideContentHover(); + ContentHoverController.get(this.diffEditor.getModifiedEditor())?.hideContentHover(); + MarginHoverController.get(this.diffEditor.getOriginalEditor())?.hideContentHover(); + MarginHoverController.get(this.diffEditor.getModifiedEditor())?.hideContentHover(); } private async updateEditor(data: ICodeCompareBlockData, token: CancellationToken): Promise { @@ -728,14 +745,19 @@ export class CodeCompareBlockPart extends Disposable { this.element.classList.toggle('no-diff', isEditApplied); - if (data.edit.state?.applied) { + if (isEditApplied) { + assertType(data.edit.state?.applied); const uriLabel = this.labelService.getUriLabel(data.edit.uri, { relative: true, noPrefix: true }); - const template = data.edit.state.applied > 1 - ? localize('chat.edits.N', "Made {0} changes in [[``{1}``]]", data.edit.state.applied, uriLabel) - : localize('chat.edits.1', "Made 1 change in [[``{0}``]]", uriLabel); - + let template: string; + if (data.edit.state.applied === 1) { + template = localize('chat.edits.1', "Made 1 change in [[``{0}``]]", uriLabel); + } else if (data.edit.state.applied < 0) { + template = localize('chat.edits.rejected', "Edits in [[``{0}``]] have been rejected", uriLabel); + } else { + template = localize('chat.edits.N', "Made {0} changes in [[``{1}``]]", data.edit.state.applied, uriLabel); + } const message = renderFormattedText(template, { renderCodeSegments: true, @@ -748,15 +770,11 @@ export class CodeCompareBlockPart extends Disposable { }); dom.reset(this.messageElement, message); - } const diffData = await data.diffData; - if (!diffData) { - return; - } - if (!isEditApplied) { + if (!isEditApplied && diffData) { const viewModel = this.diffEditor.createViewModel({ original: diffData.original, modified: diffData.modified @@ -768,15 +786,21 @@ export class CodeCompareBlockPart extends Disposable { return; } + const listener = Event.any(diffData.original.onWillDispose, diffData.modified.onWillDispose)(() => { + // this a bit weird and basically duplicates https://github.com/microsoft/vscode/blob/7cbcafcbcc88298cfdcd0238018fbbba8eb6853e/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts#L328 + // which cannot call `setModel(null)` without first complaining + this.diffEditor.setModel(null); + }); this.diffEditor.setModel(viewModel); - this._lastDiffEditorViewModel.value = viewModel; + this._lastDiffEditorViewModel.value = combinedDisposable(listener, viewModel); } else { this.diffEditor.setModel(null); this._lastDiffEditorViewModel.value = undefined; + this._onDidChangeContentHeight.fire(); } - this.toolbar2.context = { + this.toolbar.context = { edit: data.edit, element: data.element, diffEditor: this.diffEditor, @@ -888,4 +912,18 @@ export class DefaultChatTextEditor { } return true; } + + discard(response: IChatResponseModel | IChatResponseViewModel, item: IChatTextEditGroup) { + if (!response.response.value.includes(item)) { + // bogous item + return; + } + + if (item.state?.applied) { + // already applied + return; + } + + response.setEditApplied(item, -1); + } } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatContextAttachments.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatContextAttachments.ts index ff0a4455..c19bc953 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatContextAttachments.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatContextAttachments.ts @@ -21,8 +21,10 @@ export class ChatContextAttachments extends Disposable implements IChatWidgetCon constructor(readonly widget: IChatWidget) { super(); - this._register(this.widget.onDidDeleteContext((e) => { - this._removeContext(e); + this._register(this.widget.onDidChangeContext((e) => { + if (e.removed) { + this._removeContext(e.removed); + } })); this._register(this.widget.onDidSubmitAgent(() => { @@ -30,13 +32,18 @@ export class ChatContextAttachments extends Disposable implements IChatWidgetCon })); } - getInputState?() { + getInputState(): IChatRequestVariableEntry[] { return [...this._attachedContext.values()]; } - setInputState?(s: any): void { + setInputState(s: any): void { if (!Array.isArray(s)) { - return; + s = []; + } + + this._attachedContext.clear(); + for (const attachment of s) { + this._attachedContext.add(attachment); } this.widget.setContext(true, ...s); @@ -57,8 +64,10 @@ export class ChatContextAttachments extends Disposable implements IChatWidgetCon this.widget.setContext(overwrite, ...attachments); } - private _removeContext(attachment: IChatRequestVariableEntry) { - this._attachedContext.delete(attachment); + private _removeContext(attachments: IChatRequestVariableEntry[]) { + if (attachments.length) { + attachments.forEach(this._attachedContext.delete, this._attachedContext); + } } private _clearAttachedContext() { diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts index 4c3fa0ee..a81f88f9 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts @@ -41,7 +41,6 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC constructor( private readonly widget: IChatWidget, @ILabelService private readonly labelService: ILabelService, - @ILogService private readonly logService: ILogService, ) { super(); this._register(widget.inputEditor.onDidChangeModelContent(e => { @@ -50,12 +49,15 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC this._variables = coalesce(this._variables.map(ref => { const intersection = Range.intersectRanges(ref.range, c.range); if (intersection && !intersection.isEmpty()) { - // The reference text was changed, it's broken - const rangeToDelete = new Range(ref.range.startLineNumber, ref.range.startColumn, ref.range.endLineNumber, ref.range.endColumn - 1); - this.widget.inputEditor.executeEdits(this.id, [{ - range: rangeToDelete, - text: '', - }]); + // The reference text was changed, it's broken. + // But if the whole reference range was deleted (eg history navigation) then don't try to change the editor. + if (!Range.containsRange(c.range, ref.range)) { + const rangeToDelete = new Range(ref.range.startLineNumber, ref.range.startColumn, ref.range.endLineNumber, ref.range.endColumn - 1); + this.widget.inputEditor.executeEdits(this.id, [{ + range: rangeToDelete, + text: '', + }]); + } return null; } else if (Range.compareRangesUsingStarts(ref.range, c.range) > 0) { const delta = c.text.length - c.rangeLength; @@ -84,9 +86,7 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC setInputState(s: any): void { if (!Array.isArray(s)) { - // Something went wrong - this.logService.warn('ChatDynamicVariableModel.setInputState called with invalid state: ' + JSON.stringify(s)); - return; + s = []; } this._variables = s; @@ -99,18 +99,18 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC } private updateDecorations(): void { - this.widget.inputEditor.setDecorationsByType('chat', dynamicVariableDecorationType, this._variables.map(r => ({ + this.widget.inputEditor.setDecorationsByType('chat', dynamicVariableDecorationType, this._variables.map((r): IDecorationOptions => ({ range: r.range, hoverMessage: this.getHoverForReference(r) }))); } - private getHoverForReference(ref: IDynamicVariable): string | IMarkdownString { + private getHoverForReference(ref: IDynamicVariable): IMarkdownString | undefined { const value = ref.data; if (URI.isUri(value)) { return new MarkdownString(this.labelService.getUriLabel(value, { relative: true })); } else { - return (value as any).toString(); + return undefined; } } } @@ -162,11 +162,10 @@ export class SelectAndInsertFileAction extends Action2 { // This of course assumes that the `files` variable has the behavior that it searches // through files in the workspace. if (chatVariablesService.hasVariable(SelectAndInsertFileAction.Name)) { - options = { - providerOptions: { - additionPicks: [SelectAndInsertFileAction.Item, { type: 'separator' }] - }, + const providerOptions: AnythingQuickAccessProviderRunOptions = { + additionPicks: [SelectAndInsertFileAction.Item, { type: 'separator' }] }; + options = { providerOptions }; } // TODO: have dedicated UX for this instead of using the quick access picker const picks = await quickInputService.quickAccess.pick('', options); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 647ac002..c0478be2 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -13,6 +13,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -21,9 +22,10 @@ import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/brows import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { SelectAndInsertFileAction } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { ChatAgentLocation, getFullyQualifiedId, IChatAgentData, IChatAgentNameService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestTextPart, ChatRequestVariablePart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestVariablePart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; class SlashCommandCompletions extends Disposable { @@ -39,7 +41,7 @@ class SlashCommandCompletions extends Disposable { triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget || !widget.viewModel || (widget.location !== ChatAgentLocation.Panel && widget.location !== ChatAgentLocation.Notebook) /* TODO@jrieken - enable when agents are adopted*/) { + if (!widget || !widget.viewModel) { return null; } @@ -55,7 +57,7 @@ class SlashCommandCompletions extends Disposable { return; } - const slashCommands = this.chatSlashCommandService.getCommands(); + const slashCommands = this.chatSlashCommandService.getCommands(widget.location); if (!slashCommands) { return null; } @@ -95,7 +97,7 @@ class AgentCompletions extends Disposable { triggerCharacters: ['@'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget || !widget.viewModel || (widget.location !== ChatAgentLocation.Panel && widget.location !== ChatAgentLocation.Notebook) /* TODO@jrieken - enable when agents are adopted*/) { + if (!widget || !widget.viewModel) { return null; } @@ -117,7 +119,7 @@ class AgentCompletions extends Disposable { return { suggestions: agents.map((agent, i): CompletionItem => { - const { label: agentLabel, isDupe } = getAgentCompletionDetails(agent, agents, this.chatAgentNameService); + const { label: agentLabel, isDupe } = this.getAgentCompletionDetails(agent); return { // Leading space is important because detail has no space at the start by design label: isDupe ? @@ -139,7 +141,7 @@ class AgentCompletions extends Disposable { triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget || !widget.viewModel || widget.location !== ChatAgentLocation.Panel /* TODO@jrieken - enable when agents are adopted*/) { + if (!widget || !widget.viewModel) { return; } @@ -191,7 +193,7 @@ class AgentCompletions extends Disposable { provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); const viewModel = widget?.viewModel; - if (!widget || !viewModel || widget.location !== ChatAgentLocation.Panel /* TODO@jrieken - enable when agents are adopted*/) { + if (!widget || !viewModel) { return; } @@ -209,14 +211,14 @@ class AgentCompletions extends Disposable { const getFilterText = (agent: IChatAgentData, command: string) => { // This is hacking the filter algorithm to make @terminal /explain match worse than @workspace /explain by making its match index later in the string. // When I type `/exp`, the workspace one should be sorted over the terminal one. - const dummyPrefix = agent.id === 'github.copilot.terminal' ? `0000` : ``; + const dummyPrefix = agent.id === 'github.copilot.terminalPanel' ? `0000` : ``; return `${chatSubcommandLeader}${dummyPrefix}${agent.name}.${command}`; }; const justAgents: CompletionItem[] = agents .filter(a => !a.isDefault) .map(agent => { - const { label: agentLabel, isDupe } = getAgentCompletionDetails(agent, agents, this.chatAgentNameService); + const { label: agentLabel, isDupe } = this.getAgentCompletionDetails(agent); const detail = agent.description; return { @@ -236,9 +238,9 @@ class AgentCompletions extends Disposable { return { suggestions: justAgents.concat( agents.flatMap(agent => agent.slashCommands.map((c, i) => { - const { label: agentLabel, isDupe } = getAgentCompletionDetails(agent, agents, this.chatAgentNameService); + const { label: agentLabel, isDupe } = this.getAgentCompletionDetails(agent); const withSlash = `${chatSubcommandLeader}${c.name}`; - return { + const item: CompletionItem = { label: { label: withSlash, description: agentLabel, detail: isDupe ? ` (${agent.publisherDisplayName})` : undefined }, filterText: getFilterText(agent, c.name), commitCharacters: [' '], @@ -248,12 +250,28 @@ class AgentCompletions extends Disposable { kind: CompletionItemKind.Text, // The icons are disabled here anyway sortText: `${chatSubcommandLeader}${agent.name}${c.name}`, command: { id: AssignSelectedAgentAction.ID, title: AssignSelectedAgentAction.ID, arguments: [{ agent, widget } satisfies AssignSelectedAgentActionArgs] }, - } satisfies CompletionItem; + }; + + if (agent.isDefault) { + // default agent isn't mentioned nor inserted + item.label = withSlash; + item.insertText = `${withSlash} `; + item.detail = c.description; + } + + return item; }))) }; } })); } + + private getAgentCompletionDetails(agent: IChatAgentData): { label: string; isDupe: boolean } { + const isAllowed = this.chatAgentNameService.getAgentNameRestriction(agent); + const agentLabel = `${chatAgentLeader}${isAllowed ? agent.name : getFullyQualifiedId(agent)}`; + const isDupe = isAllowed && this.chatAgentService.agentHasDupeName(agent.id); + return { label: agentLabel, isDupe }; + } } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AgentCompletions, LifecyclePhase.Eventually); @@ -297,11 +315,11 @@ class BuiltinDynamicCompletions extends Disposable { triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget || !widget.supportsFileReferences || widget.location !== ChatAgentLocation.Panel /* TODO@jrieken - enable when agents are adopted*/) { + if (!widget || !widget.supportsFileReferences) { return null; } - const range = computeCompletionRanges(model, position, BuiltinDynamicCompletions.VariableNameDef); + const range = computeCompletionRanges(model, position, BuiltinDynamicCompletions.VariableNameDef, true); if (!range) { return null; } @@ -327,12 +345,19 @@ class BuiltinDynamicCompletions extends Disposable { Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BuiltinDynamicCompletions, LifecyclePhase.Eventually); -function computeCompletionRanges(model: ITextModel, position: Position, reg: RegExp): { insert: Range; replace: Range; varWord: IWordAtPosition | null } | undefined { +function computeCompletionRanges(model: ITextModel, position: Position, reg: RegExp, onlyOnWordStart = false): { insert: Range; replace: Range; varWord: IWordAtPosition | null } | undefined { const varWord = getWordAtText(position.column, reg, model.getLineContent(position.lineNumber), 0); if (!varWord && model.getWordUntilPosition(position).word) { // inside a "normal" word return; } + if (varWord && onlyOnWordStart) { + const wordBefore = model.getWordUntilPosition({ lineNumber: position.lineNumber, column: varWord.startColumn }); + if (wordBefore.word) { + // inside a word + return; + } + } let insert: Range; let replace: Range; @@ -354,6 +379,8 @@ class VariableCompletions extends Disposable { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, + @IConfigurationService configService: IConfigurationService, + @ILanguageModelToolsService toolsService: ILanguageModelToolsService ) { super(); @@ -361,13 +388,21 @@ class VariableCompletions extends Disposable { _debugDisplayName: 'chatVariables', triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { + const locations = new Set(); + locations.add(ChatAgentLocation.Panel); + + for (const value of Object.values(ChatAgentLocation)) { + if (typeof value === 'string' && configService.getValue(`chat.experimental.variables.${value}`)) { + locations.add(value); + } + } const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget || widget.location !== ChatAgentLocation.Panel /* TODO@jrieken - enable when agents are adopted*/) { + if (!widget || !locations.has(widget.location)) { return null; } - const range = computeCompletionRanges(model, position, VariableCompletions.VariableNameDef); + const range = computeCompletionRanges(model, position, VariableCompletions.VariableNameDef, true); if (!range) { return null; } @@ -376,9 +411,10 @@ class VariableCompletions extends Disposable { const slowSupported = usedAgent ? usedAgent.agent.metadata.supportsSlowVariables : true; const usedVariables = widget.parsedInput.parts.filter((p): p is ChatRequestVariablePart => p instanceof ChatRequestVariablePart); - const variableItems = Array.from(this.chatVariablesService.getVariables()) + const usedVariableNames = new Set(usedVariables.map(v => v.variableName)); + const variableItems = Array.from(this.chatVariablesService.getVariables(widget.location)) // This doesn't look at dynamic variables like `file`, where multiple makes sense. - .filter(v => !usedVariables.some(usedVar => usedVar.variableName === v.name)) + .filter(v => !usedVariableNames.has(v.name)) .filter(v => !v.isSlow || slowSupported) .map((v): CompletionItem => { const withLeader = `${chatVariableLeader}${v.name}`; @@ -392,8 +428,28 @@ class VariableCompletions extends Disposable { }; }); + const usedTools = widget.parsedInput.parts.filter((p): p is ChatRequestToolPart => p instanceof ChatRequestToolPart); + const usedToolNames = new Set(usedTools.map(v => v.toolName)); + const toolItems: CompletionItem[] = []; + if (!usedAgent || usedAgent.agent.supportsToolReferences) { + toolItems.push(...Array.from(toolsService.getTools()) + .filter(t => t.canBeInvokedManually) + .filter(t => !usedToolNames.has(t.name ?? '')) + .map((t): CompletionItem => { + const withLeader = `${chatVariableLeader}${t.name}`; + return { + label: withLeader, + range, + insertText: withLeader + ' ', + detail: t.userDescription, + kind: CompletionItemKind.Text, + sortText: 'z' + }; + })); + } + return { - suggestions: variableItems + suggestions: [...variableItems, ...toolItems] }; } })); @@ -401,11 +457,3 @@ class VariableCompletions extends Disposable { } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(VariableCompletions, LifecyclePhase.Eventually); - -function getAgentCompletionDetails(agent: IChatAgentData, otherAgents: IChatAgentData[], chatAgentNameService: IChatAgentNameService): { label: string; isDupe: boolean } { - const isAllowed = chatAgentNameService.getAgentNameRestriction(agent); - const agentLabel = `${chatAgentLeader}${isAllowed ? agent.name : getFullyQualifiedId(agent)}`; - const isDupe = isAllowed && !!otherAgents.find(other => other.name === agent.name && other.id !== agent.id); - - return { label: agentLabel, isDupe }; -} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index bafc22d7..4af488fd 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -14,9 +14,9 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IChatWidget } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { dynamicVariableDecorationType } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; -import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestVariablePart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; const decorationDescription = 'chat'; @@ -175,8 +175,8 @@ class InputEditorDecorations extends Disposable { } } - const onlyAgentCommandAndWhitespace = agentPart && agentSubcommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart); - if (onlyAgentCommandAndWhitespace) { + const onlyAgentAndAgentCommandAndWhitespace = agentPart && agentSubcommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart); + if (onlyAgentAndAgentCommandAndWhitespace) { // Agent reference and subcommand with no other text - show the placeholder const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent, agentSubcommandPart.command.name)); const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentSubcommandPart.command.followupPlaceholder; @@ -193,17 +193,30 @@ class InputEditorDecorations extends Disposable { } } + const onlyAgentCommandAndWhitespace = agentSubcommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentSubcommandPart); + if (onlyAgentCommandAndWhitespace) { + // Agent subcommand with no other text - show the placeholder + if (agentSubcommandPart?.command.description && exactlyOneSpaceAfterPart(agentSubcommandPart)) { + placeholderDecoration = [{ + range: getRangeForPlaceholder(agentSubcommandPart), + renderOptions: { + after: { + contentText: agentSubcommandPart.command.description, + color: this.getPlaceholderColor(), + } + } + }]; + } + } + this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, placeholderDecoration ?? []); const textDecorations: IDecorationOptions[] | undefined = []; if (agentPart) { - const isDupe = !!this.chatAgentService.getAgents().find(other => other.name === agentPart.agent.name && other.id !== agentPart.agent.id); - const publisher = isDupe ? `(${agentPart.agent.publisherDisplayName}) ` : ''; - const agentHover = `${publisher}${agentPart.agent.description}`; - textDecorations.push({ range: agentPart.editorRange, hoverMessage: new MarkdownString(agentHover) }); - if (agentSubcommandPart) { - textDecorations.push({ range: agentSubcommandPart.editorRange, hoverMessage: new MarkdownString(agentSubcommandPart.command.description) }); - } + textDecorations.push({ range: agentPart.editorRange }); + } + if (agentSubcommandPart) { + textDecorations.push({ range: agentSubcommandPart.editorRange, hoverMessage: new MarkdownString(agentSubcommandPart.command.description) }); } if (slashCommandPart) { @@ -218,6 +231,11 @@ class InputEditorDecorations extends Disposable { varDecorations.push({ range: variable.editorRange }); } + const toolParts = parsedRequest.filter((p): p is ChatRequestToolPart => p instanceof ChatRequestToolPart); + for (const tool of toolParts) { + varDecorations.push({ range: tool.editorRange }); + } + this.widget.inputEditor.setDecorationsByType(decorationDescription, variableTextDecorationType, varDecorations); } } @@ -229,12 +247,22 @@ class InputEditorSlashCommandMode extends Disposable { private readonly widget: IChatWidget ) { super(); + this._register(this.widget.onDidChangeAgent(e => { + if (e.slashCommand && e.slashCommand.isSticky || !e.slashCommand && e.agent.metadata.isSticky) { + this.repopulateAgentCommand(e.agent, e.slashCommand); + } + })); this._register(this.widget.onDidSubmitAgent(e => { this.repopulateAgentCommand(e.agent, e.slashCommand); })); } private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand | undefined) { + // Make sure we don't repopulate if the user already has something in the input + if (this.widget.inputEditor.getValue().trim()) { + return; + } + let value: string | undefined; if (slashCommand && slashCommand.isSticky) { value = `${chatAgentLeader}${agent.name} ${chatSubcommandLeader}${slashCommand.name} `; @@ -278,10 +306,10 @@ class ChatTokenDeleter extends Disposable { // If this was a simple delete, try to find out whether it was inside a token if (!change.text && this.widget.viewModel) { - const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionId, previousInputValue, ChatAgentLocation.Panel, { selectedAgent: previousSelectedAgent }); + const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionId, previousInputValue, widget.location, { selectedAgent: previousSelectedAgent }); // For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping - const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart); + const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart || p instanceof ChatRequestToolPart); deletableTokens.forEach(token => { const deletedRangeOfToken = Range.intersectRanges(token.editorRange, change.range); // Part of this token was deleted, or the space after it was deleted, and the deletion range doesn't go off the front of the token, for simpler math diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorHover.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorHover.ts new file mode 100644 index 00000000..893353b5 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorHover.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import { IModelDecoration } from 'vs/editor/common/model'; +import { HoverAnchor, HoverAnchorType, HoverParticipantRegistry, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatAgentHover, getChatAgentHoverOptions } from 'vs/workbench/contrib/chat/browser/chatAgentHover'; +import { ChatEditorHoverWrapper } from 'vs/workbench/contrib/chat/browser/contrib/editorHoverWrapper'; +import { IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { extractAgentAndCommand } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import * as nls from 'vs/nls'; + +export class ChatAgentHoverParticipant implements IEditorHoverParticipant { + + public readonly hoverOrdinal: number = 1; + + constructor( + private readonly editor: ICodeEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @ICommandService private readonly commandService: ICommandService, + ) { } + + public computeSync(anchor: HoverAnchor, _lineDecorations: IModelDecoration[]): ChatAgentHoverPart[] { + if (!this.editor.hasModel()) { + return []; + } + + const widget = this.chatWidgetService.getWidgetByInputUri(this.editor.getModel().uri); + if (!widget) { + return []; + } + + const { agentPart } = extractAgentAndCommand(widget.parsedInput); + if (!agentPart) { + return []; + } + + if (Range.containsPosition(agentPart.editorRange, anchor.range.getStartPosition())) { + return [new ChatAgentHoverPart(this, Range.lift(agentPart.editorRange), agentPart.agent)]; + } + + return []; + } + + public renderHoverParts(context: IEditorHoverRenderContext, hoverParts: ChatAgentHoverPart[]): IRenderedHoverParts { + if (!hoverParts.length) { + return new RenderedHoverParts([]); + } + + const disposables = new DisposableStore(); + const hover = disposables.add(this.instantiationService.createInstance(ChatAgentHover)); + disposables.add(hover.onDidChangeContents(() => context.onContentsChanged())); + const hoverPart = hoverParts[0]; + const agent = hoverPart.agent; + hover.setAgent(agent.id); + + const actions = getChatAgentHoverOptions(() => agent, this.commandService).actions; + const wrapper = this.instantiationService.createInstance(ChatEditorHoverWrapper, hover.domNode, actions); + const wrapperNode = wrapper.domNode; + context.fragment.appendChild(wrapperNode); + const renderedHoverPart: IRenderedHoverPart = { + hoverPart, + hoverElement: wrapperNode, + dispose() { disposables.dispose(); } + }; + return new RenderedHoverParts([renderedHoverPart]); + } + + public getAccessibleContent(hoverPart: ChatAgentHoverPart): string { + return nls.localize('hoverAccessibilityChatAgent', 'There is a chat agent hover part here.'); + + } +} + +export class ChatAgentHoverPart implements IHoverPart { + + constructor( + public readonly owner: IEditorHoverParticipant, + public readonly range: Range, + public readonly agent: IChatAgentData + ) { } + + public isValidForHoverAnchor(anchor: HoverAnchor): boolean { + return ( + anchor.type === HoverAnchorType.Range + && this.range.startColumn <= anchor.range.startColumn + && this.range.endColumn >= anchor.range.endColumn + ); + } +} + +HoverParticipantRegistry.register(ChatAgentHoverParticipant); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/editorHoverWrapper.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/editorHoverWrapper.ts new file mode 100644 index 00000000..5cbc7d93 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/editorHoverWrapper.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/editorHoverWrapper'; +import * as dom from 'vs/base/browser/dom'; +import { IHoverAction } from 'vs/base/browser/ui/hover/hover'; +import { HoverAction } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; + +const $ = dom.$; +const h = dom.h; + +/** + * This borrows some of HoverWidget so that a chat editor hover can be rendered in the same way as a workbench hover. + * Maybe it can be reusable in a generic way. + */ +export class ChatEditorHoverWrapper { + public readonly domNode: HTMLElement; + + constructor( + hoverContentElement: HTMLElement, + actions: IHoverAction[] | undefined, + @IKeybindingService private readonly keybindingService: IKeybindingService, + ) { + const hoverElement = h( + '.chat-editor-hover-wrapper@root', + [h('.chat-editor-hover-wrapper-content@content')]); + this.domNode = hoverElement.root; + hoverElement.content.appendChild(hoverContentElement); + + if (actions && actions.length > 0) { + const statusBarElement = $('.hover-row.status-bar'); + const actionsElement = $('.actions'); + actions.forEach(action => { + const keybinding = this.keybindingService.lookupKeybinding(action.commandId); + const keybindingLabel = keybinding ? keybinding.getLabel() : null; + HoverAction.render(actionsElement, { + label: action.label, + commandId: action.commandId, + run: e => { + action.run(e); + }, + iconClass: action.iconClass + }, keybindingLabel); + }); + statusBarElement.appendChild(actionsElement); + this.domNode.appendChild(statusBarElement); + } + } +} diff --git a/patched-vscode/extensions/microsoft-authentication/src/browser/crypto.ts b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/media/editorHoverWrapper.css similarity index 85% rename from patched-vscode/extensions/microsoft-authentication/src/browser/crypto.ts rename to patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/media/editorHoverWrapper.css index 37a1b433..d95fd395 100644 --- a/patched-vscode/extensions/microsoft-authentication/src/browser/crypto.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/contrib/media/editorHoverWrapper.css @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export const crypto = globalThis.crypto; +.chat-editor-hover-wrapper-content { + padding: 2px 8px; +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chat.css b/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chat.css index 5310cc02..d461295f 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -40,6 +40,12 @@ display: flex; align-items: center; gap: 8px; + + /* + Rendering the avatar icon as round makes it a little larger than the .user container. + Add padding so that the focus outline doesn't run into it, and counteract it with a negative margin so it doesn't actually take up any extra space */ + padding: 2px; + margin: -2px; } .interactive-item-container .header .username { @@ -48,12 +54,13 @@ font-weight: 600; } -.interactive-item-container .header .detail-container { +.interactive-item-container .detail-container { font-size: 12px; color: var(--vscode-descriptionForeground); + overflow: hidden; } -.interactive-item-container .header .detail-container .detail .agentOrSlashCommandDetected A { +.interactive-item-container .detail-container .detail .agentOrSlashCommandDetected A { cursor: pointer; color: var(--vscode-textLink-foreground); } @@ -170,8 +177,18 @@ width: 100%; } -.interactive-item-container .chat-progress-task { - padding-bottom: 8px; +.interactive-item-container > .value .chat-used-context { + margin-bottom: 8px; +} + +.interactive-item-container .value .rendered-markdown blockquote { + margin: 0px; + padding: 0px 16px 0 10px; + border-left-width: 5px; + border-left-style: solid; + border-radius: 2px; + background: var(--vscode-textBlockQuote-background); + border-color: var(--vscode-textBlockQuote-border); } .interactive-item-container .value .rendered-markdown table { @@ -228,6 +245,14 @@ margin-bottom: 0px; } +.interactive-item-container .value .rendered-markdown hr { + border-color: rgba(0, 0, 0, 0.18); +} + +.vs-dark .interactive-item-container .value .rendered-markdown hr { + border-color: rgba(255, 255, 255, 0.18); +} + .interactive-item-container .value .rendered-markdown h1 { font-size: 20px; font-weight: 600; @@ -259,14 +284,42 @@ margin: 0; } +/* #region list indent rules */ .interactive-item-container .value .rendered-markdown ul { + /* Keep this in sync with the values for dedented codeblocks below */ padding-inline-start: 24px; } .interactive-item-container .value .rendered-markdown ol { + /* Keep this in sync with the values for dedented codeblocks below */ padding-inline-start: 28px; } +/* NOTE- We want to dedent codeblocks in lists specifically to give them the full width. No more elegant way to do this, these values +have to be updated for changes to the rules above, or to support more deeply nested lists. */ +.interactive-item-container .value .rendered-markdown ul .interactive-result-code-block { + margin-left: -24px; +} + +.interactive-item-container .value .rendered-markdown ul ul .interactive-result-code-block { + margin-left: -48px; +} + +.interactive-item-container .value .rendered-markdown ol .interactive-result-code-block { + margin-left: -28px; +} + +.interactive-item-container .value .rendered-markdown ol ol .interactive-result-code-block { + margin-left: -56px; +} + +.interactive-item-container .value .rendered-markdown ol ul .interactive-result-code-block, +.interactive-item-container .value .rendered-markdown ul ol .interactive-result-code-block { + margin-left: -52px; +} + +/* #endregion list indent rules */ + .interactive-item-container .value .rendered-markdown li { line-height: 1.3rem; } @@ -341,6 +394,34 @@ margin: 8px 0; } +.interactive-item-container.minimal { + flex-direction: row; +} + +.interactive-item-container.minimal .column.left { + padding-top: 2px; + display: inline-block; + flex-grow: 0; +} + +.interactive-item-container.minimal .column.right { + display: inline-block; + flex-grow: 1; +} + +.interactive-item-container.minimal .user > .username { + display: none; +} + +.interactive-item-container.minimal .detail-container { + font-size: unset; +} + +.interactive-item-container.minimal > .header { + position: absolute; + right: 0; +} + .interactive-session .interactive-input-and-execute-toolbar { display: flex; box-sizing: border-box; @@ -370,7 +451,7 @@ border-color: var(--vscode-focusBorder); } -.interactive-session .interactive-input-and-execute-toolbar .monaco-editor .mtk1 { +.interactive-input-and-execute-toolbar .monaco-editor .mtk1 { color: var(--vscode-input-foreground); } @@ -453,6 +534,17 @@ color: var(--vscode-interactive-session-foreground); } +.chat-attached-context .chat-attached-context-attachment .monaco-icon-name-container.warning, +.chat-attached-context .chat-attached-context-attachment .monaco-icon-suffix-container.warning, +.chat-used-context-list .monaco-icon-name-container.warning, +.chat-used-context-list .monaco-icon-suffix-container.warning { + color: var(--vscode-notificationsWarningIcon-foreground); +} + +.chat-attached-context .chat-attached-context-attachment.show-file-icons.warning { + border-color: var(--vscode-notificationsWarningIcon-foreground); +} + .chat-notification-widget .chat-warning-codicon .codicon-warning { color: var(--vscode-notificationsWarningIcon-foreground) !important; /* Have to override default styles which apply to all lists */ } @@ -505,6 +597,7 @@ .interactive-session .chat-attached-context .chat-attached-context-attachment .monaco-icon-label .monaco-button.codicon.codicon-close, .interactive-session .chat-attached-context .chat-attached-context-attachment .monaco-button.codicon.codicon-close { color: var(--vscode-descriptionForeground); + cursor: pointer; } .interactive-session .chat-attached-context .chat-attached-context-attachment .monaco-icon-label .codicon { @@ -518,6 +611,17 @@ flex-wrap: wrap; } +.interactive-session .interactive-input-part.compact .chat-attached-context { + padding-top: 8px; + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.interactive-session .interactive-item-container.interactive-request .chat-attached-context { + margin-top: -8px; +} + .interactive-session .chat-attached-context .chat-attached-context-attachment { padding: 2px; border: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-background, transparent)); @@ -526,6 +630,10 @@ max-width: 100%; } +.interactive-session .interactive-item-container.interactive-request .chat-attached-context .chat-attached-context-attachment { + padding-right: 6px; +} + .interactive-session-followups { display: flex; flex-direction: column; @@ -555,6 +663,12 @@ display: block; color: var(--vscode-textLink-foreground); font-size: 12px; + + /* clamp to max 3 lines */ + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; } .interactive-session .interactive-input-part .interactive-input-followups .interactive-session-followups code { @@ -580,12 +694,6 @@ color: var(--vscode-icon-foreground) !important; } -.interactive-item-container.filtered-response .value > .rendered-markdown { - pointer-events: none; - -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.05) 60%, rgba(0, 0, 0, 0.00) 80%); - mask-image: linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.05) 60%, rgba(0, 0, 0, 0.00) 80%); -} - /* #region Quick Chat */ .quick-input-widget .interactive-session .interactive-input-part { @@ -781,3 +889,19 @@ margin-left: 0; margin-top: 1px; } + +.chat-code-citation-label { + opacity: 0.7; + white-space: pre-wrap; +} + +.chat-code-citation-button-container { + display: inline; +} + +.chat-code-citation-button-container .monaco-button { + display: inline; + border: none; + padding: 0; + color: var(--vscode-textLink-foreground); +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chatAgentHover.css b/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chatAgentHover.css index 1599c4ff..29e38f48 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chatAgentHover.css +++ b/patched-vscode/src/vs/workbench/contrib/chat/browser/media/chatAgentHover.css @@ -22,8 +22,8 @@ outline: 1px solid var(--vscode-chat-requestBorder); } -.monaco-hover .markdown-hover .hover-contents .chat-agent-hover-icon .codicon { - font-size: 23px; +.chat-agent-hover .chat-agent-hover-icon .codicon { + font-size: 23px !important; /* Override workbench hover styles */ display: flex; justify-content: center; align-items: center; @@ -34,7 +34,7 @@ gap: 4px; } -.monaco-hover .chat-agent-hover .chat-agent-hover-publisher .codicon.codicon-extensions-verified-publisher { +.chat-agent-hover .chat-agent-hover-publisher .codicon.codicon-extensions-verified-publisher { color: var(--vscode-extensionIcon-verifiedForeground); } @@ -60,6 +60,10 @@ font-weight: 600; } +.chat-agent-hover-header .chat-agent-hover-details { + font-size: 12px; +} + .chat-agent-hover-extension { display: flex; gap: 6px; diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/annotations.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/annotations.ts index 8a57732c..449ff1bc 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/annotations.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/annotations.ts @@ -11,7 +11,7 @@ import { IChatAgentVulnerabilityDetails, IChatMarkdownContent } from 'vs/workben export const contentRefUrl = 'http://_vscodecontentref_'; // must be lowercase for URI -export function annotateSpecialMarkdownContent(response: ReadonlyArray): ReadonlyArray { +export function annotateSpecialMarkdownContent(response: ReadonlyArray): IChatProgressRenderableResponseContent[] { const result: IChatProgressRenderableResponseContent[] = []; for (const item of response) { const previousItem = result[result.length - 1]; diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatAgents.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatAgents.ts index da0e5eba..5725b2b0 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -24,10 +24,10 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { asJson, IRequestService } from 'vs/platform/request/common/request'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { CONTEXT_CHAT_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressResponseContent, IChatRequestVariableData, ISerializableChatAgentData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IRawChatCommandContribution, RawChatParticipantLocation } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; -import { IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from 'vs/workbench/contrib/chat/common/chatService'; //#region agent service, commands etc @@ -73,17 +73,34 @@ export interface IChatAgentData { isDynamic?: boolean; metadata: IChatAgentMetadata; slashCommands: IChatAgentCommand[]; - defaultImplicitVariables?: string[]; locations: ChatAgentLocation[]; + disambiguation: { categoryName: string; description: string; examples: string[] }[]; + supportsToolReferences?: boolean; } export interface IChatAgentImplementation { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideWelcomeMessage?(location: ChatAgentLocation, token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; + provideChatTitle?: (history: IChatAgentHistoryEntry[], token: CancellationToken) => Promise; provideSampleQuestions?(location: ChatAgentLocation, token: CancellationToken): ProviderResult; } +export interface IChatParticipantDetectionResult { + participant: string; + command?: string; +} + +export interface IChatParticipantMetadata { + participant: string; + command?: string; + disambiguation: { categoryName: string; description: string; examples: string[] }[]; +} + +export interface IChatParticipantDetectionProvider { + provideParticipantDetection(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation; participants: IChatParticipantMetadata[] }, token: CancellationToken): Promise; +} + export type IChatAgent = IChatAgentData & IChatAgentImplementation; export interface IChatAgentCommand extends IRawChatCommandContribution { @@ -124,20 +141,31 @@ export interface IChatAgentRequest { message: string; attempt?: number; enableCommandDetection?: boolean; + isParticipantDetected?: boolean; variables: IChatRequestVariableData; location: ChatAgentLocation; + locationData?: IChatLocationData; acceptedConfirmationData?: any[]; rejectedConfirmationData?: any[]; } +export interface IChatQuestion { + readonly prompt: string; + readonly participant?: string; + readonly command?: string; +} + +export interface IChatAgentResultTimings { + firstProgress?: number; + totalElapsed: number; +} + export interface IChatAgentResult { errorDetails?: IChatResponseErrorDetails; - timings?: { - firstProgress?: number; - totalElapsed: number; - }; + timings?: IChatAgentResultTimings; /** Extra properties that the agent can use to identify a result */ readonly metadata?: { readonly [key: string]: any }; + nextQuestion?: IChatQuestion; } export const IChatAgentService = createDecorator('chatAgentService'); @@ -167,13 +195,18 @@ export interface IChatAgentService { registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable; registerAgentCompletionProvider(id: string, provider: (query: string, token: CancellationToken) => Promise): IDisposable; getAgentCompletionItems(id: string, query: string, token: CancellationToken): Promise; + registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider): IDisposable; + detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined>; + hasChatParticipantDetectionProviders(): boolean; invokeAgent(agent: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; + getChatTitle(id: string, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; getAgent(id: string): IChatAgentData | undefined; getAgentByFullyQualifiedId(id: string): IChatAgentData | undefined; getAgents(): IChatAgentData[]; getActivatedAgents(): Array; getAgentsByName(name: string): IChatAgentData[]; + agentHasDupeName(id: string): boolean; /** * Get the default agent (only if activated) @@ -200,11 +233,13 @@ export class ChatAgentService implements IChatAgentService { readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; private readonly _hasDefaultAgent: IContextKey; + private readonly _defaultAgentRegistered: IContextKey; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { this._hasDefaultAgent = CONTEXT_CHAT_ENABLED.bindTo(this.contextKeyService); + this._defaultAgentRegistered = CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.bindTo(this.contextKeyService); } registerAgent(id: string, data: IChatAgentData): IDisposable { @@ -213,6 +248,10 @@ export class ChatAgentService implements IChatAgentService { throw new Error(`Agent already registered: ${JSON.stringify(id)}`); } + if (data.isDefault) { + this._defaultAgentRegistered.set(true); + } + const that = this; const commands = data.slashCommands; data = { @@ -223,8 +262,13 @@ export class ChatAgentService implements IChatAgentService { }; const entry = { data }; this._agents.set(id, entry); + this._onDidChangeAgents.fire(undefined); return toDisposable(() => { this._agents.delete(id); + if (data.isDefault) { + this._defaultAgentRegistered.set(false); + } + this._onDidChangeAgents.fire(undefined); }); } @@ -345,6 +389,16 @@ export class ChatAgentService implements IChatAgentService { return this.getAgents().filter(a => a.name === name); } + agentHasDupeName(id: string): boolean { + const agent = this.getAgent(id); + if (!agent) { + return false; + } + + return this.getAgentsByName(agent.name) + .filter(a => a.extensionId.value !== agent.extensionId.value).length > 0; + } + async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { const data = this._agents.get(id); if (!data?.impl) { @@ -366,6 +420,70 @@ export class ChatAgentService implements IChatAgentService { return data.impl.provideFollowups(request, result, history, token); } + + async getChatTitle(id: string, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { + const data = this._agents.get(id); + if (!data?.impl) { + throw new Error(`No activated agent with id "${id}"`); + } + + if (!data.impl?.provideChatTitle) { + return undefined; + } + + return data.impl.provideChatTitle(history, token); + } + + private _chatParticipantDetectionProviders = new Map(); + registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider) { + this._chatParticipantDetectionProviders.set(handle, provider); + return toDisposable(() => { + this._chatParticipantDetectionProviders.delete(handle); + }); + } + + hasChatParticipantDetectionProviders() { + return this._chatParticipantDetectionProviders.size > 0; + } + + async detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined> { + // TODO@joyceerhl should we have a selector to be able to narrow down which provider to use + const provider = Iterable.first(this._chatParticipantDetectionProviders.values()); + if (!provider) { + return; + } + + const participants = this.getAgents().reduce((acc, a) => { + acc.push({ participant: a.id, disambiguation: a.disambiguation ?? [] }); + for (const command of a.slashCommands) { + acc.push({ participant: a.id, command: command.name, disambiguation: command.disambiguation ?? [] }); + } + return acc; + }, []); + + const result = await provider.provideParticipantDetection(request, history, { ...options, participants }, token); + if (!result) { + return; + } + + const agent = this.getAgent(result.participant); + if (!agent) { + // Couldn't find a participant matching the participant detection result + return; + } + + if (!result.command) { + return { agent }; + } + + const command = agent?.slashCommands.find(c => c.name === result.command); + if (!command) { + // Couldn't find a slash command matching the participant detection result + return; + } + + return { agent, command }; + } } export class MergedChatAgent implements IChatAgent { @@ -373,6 +491,9 @@ export class MergedChatAgent implements IChatAgent { private readonly data: IChatAgentData, private readonly impl: IChatAgentImplementation ) { } + when?: string | undefined; + publisherDisplayName?: string | undefined; + isDynamic?: boolean | undefined; get id(): string { return this.data.id; } get name(): string { return this.data.name ?? ''; } @@ -385,8 +506,8 @@ export class MergedChatAgent implements IChatAgent { get isDefault(): boolean | undefined { return this.data.isDefault; } get metadata(): IChatAgentMetadata { return this.data.metadata; } get slashCommands(): IChatAgentCommand[] { return this.data.slashCommands; } - get defaultImplicitVariables(): string[] | undefined { return this.data.defaultImplicitVariables; } get locations(): ChatAgentLocation[] { return this.data.locations; } + get disambiguation(): { categoryName: string; description: string; examples: string[] }[] { return this.data.disambiguation; } async invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { return this.impl.invoke(request, progress, history, token); @@ -415,6 +536,10 @@ export class MergedChatAgent implements IChatAgent { return undefined; } + + toJSON(): IChatAgentData { + return this.data; + } } export const IChatAgentNameService = createDecorator('chatAgentNameService'); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatColors.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatColors.ts index 3c4cce05..15451f0d 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -39,6 +39,6 @@ export const chatAvatarBackground = registerColor( export const chatAvatarForeground = registerColor( 'chat.avatarForeground', - { dark: foreground, light: foreground, hcDark: foreground, hcLight: foreground, }, + foreground, localize('chat.avatarForeground', 'The foreground color of a chat avatar.') ); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index bd51f83c..f651a114 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -8,9 +8,11 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; export const CONTEXT_RESPONSE_VOTE = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); +export const CONTEXT_VOTE_UP_ENABLED = new RawContextKey('chatVoteUpEnabled', false, { type: 'boolean', description: localize('chatVoteUpEnabled', "True when the chat vote up action is enabled.") }); export const CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND = new RawContextKey('chatSessionResponseDetectedAgentOrCommand', false, { type: 'boolean', description: localize('chatSessionResponseDetectedAgentOrCommand', "When the agent or command was automatically detected") }); export const CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING = new RawContextKey('chatResponseSupportsIssueReporting', false, { type: 'boolean', description: localize('chatResponseSupportsIssueReporting', "True when the current chat response supports issue reporting.") }); export const CONTEXT_RESPONSE_FILTERED = new RawContextKey('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") }); +export const CONTEXT_RESPONSE_ERROR = new RawContextKey('chatSessionResponseError', false, { type: 'boolean', description: localize('chatResponseErrored', "True when the chat response resulted in an error.") }); export const CONTEXT_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") }); export const CONTEXT_RESPONSE = new RawContextKey('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") }); @@ -23,7 +25,9 @@ export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey('chatInpu export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); -export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is registered.") }); +export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); +export const CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); +export const CONTEXT_CHAT_EXTENSION_INVALID = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey('chatCursorAtTop', false); export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey('chatInputHasAgent', false); export const CONTEXT_CHAT_LOCATION = new RawContextKey('chatLocation', undefined); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatModel.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatModel.ts index 1760a265..b9578bae 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; import { asArray, firstOrDefault } from 'vs/base/common/arrays'; import { DeferredPromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -16,12 +15,14 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { URI, UriComponents, UriDto, isUriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IRange } from 'vs/editor/common/core/range'; import { TextEdit } from 'vs/editor/common/languages'; +import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatRequestTextPart, IParsedChatRequest, getPromptText, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatTask, IChatTextEdit, IChatTreeData, IChatUsedContext, IChatWarningMessage, ChatAgentVoteDirection, isIUsedContext, IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, reviveSerializedAgent } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatTask, IChatTextEdit, IChatTreeData, IChatUsedContext, IChatWarningMessage, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableEntry { @@ -33,8 +34,11 @@ export interface IChatRequestVariableEntry { range?: IOffsetRange; value: IChatRequestVariableValue; references?: IChatContentReference[]; + + // TODO are these just a 'kind'? isDynamic?: boolean; isFile?: boolean; + isTool?: boolean; } export interface IChatRequestVariableData { @@ -49,6 +53,9 @@ export interface IChatRequestModel { readonly message: IParsedChatRequest; readonly attempt: number; readonly variableData: IChatRequestVariableData; + readonly confirmation?: string; + readonly locationData?: IChatLocationData; + readonly attachedContext?: IChatRequestVariableEntry[]; readonly response?: IChatResponseModel; } @@ -80,7 +87,8 @@ export type IChatProgressRenderableResponseContent = Exclude; - asString(): string; + toMarkdown(): string; + toString(): string; } export interface IChatResponseModel { @@ -93,6 +101,7 @@ export interface IChatResponseModel { readonly agent?: IChatAgentData; readonly usedContext: IChatUsedContext | undefined; readonly contentReferences: ReadonlyArray; + readonly codeCitations: ReadonlyArray; readonly progressMessages: ReadonlyArray; readonly slashCommand?: IChatAgentCommand; readonly agentOrSlashCommandDetected: boolean; @@ -102,9 +111,11 @@ export interface IChatResponseModel { /** A stale response is one that has been persisted and rehydrated, so e.g. Commands that have their arguments stored in the EH are gone. */ readonly isStale: boolean; readonly vote: ChatAgentVoteDirection | undefined; + readonly voteDownReason: ChatAgentVoteDownReason | undefined; readonly followups?: IChatFollowup[] | undefined; readonly result?: IChatAgentResult; setVote(vote: ChatAgentVoteDirection): void; + setVoteDownReason(reason: ChatAgentVoteDownReason | undefined): void; setEditApplied(edit: IChatTextEditGroup, editCount: number): boolean; } @@ -139,11 +150,26 @@ export class ChatRequestModel implements IChatRequestModel { this._variableData = v; } + public get confirmation(): string | undefined { + return this._confirmation; + } + + public get locationData(): IChatLocationData | undefined { + return this._locationData; + } + + public get attachedContext(): IChatRequestVariableEntry[] | undefined { + return this._attachedContext; + } + constructor( private _session: ChatModel, public readonly message: IParsedChatRequest, private _variableData: IChatRequestVariableData, - private _attempt: number = 0 + private _attempt: number = 0, + private _confirmation?: string, + private _locationData?: IChatLocationData, + private _attachedContext?: IChatRequestVariableEntry[] ) { this.id = 'request_' + ChatRequestModel.nextId++; } @@ -153,22 +179,32 @@ export class ChatRequestModel implements IChatRequestModel { } } -export class Response implements IResponse { - private _onDidChangeValue = new Emitter(); +export class Response extends Disposable implements IResponse { + private _onDidChangeValue = this._register(new Emitter()); public get onDidChangeValue() { return this._onDidChangeValue.event; } - // responseParts internally tracks all the response parts, including strings which are currently resolving, so that they can be updated when they do resolve private _responseParts: IChatProgressResponseContent[]; - // responseRepr externally presents the response parts with consolidated contiguous strings (excluding tree data) - private _responseRepr!: string; + + /** + * A stringified representation of response data which might be presented to a screenreader or used when copying a response. + */ + private _responseRepr = ''; + + /** + * Just the markdown content of the response, used for determining the rendering rate of markdown + */ + private _markdownContent = ''; + + private _citations: IChatCodeCitation[] = []; get value(): IChatProgressResponseContent[] { return this._responseParts; } constructor(value: IMarkdownString | ReadonlyArray) { + super(); this._responseParts = asArray(value).map((v) => (isMarkdownString(v) ? { content: v, kind: 'markdownContent' } satisfies IChatMarkdownContent : 'kind' in v ? v : { kind: 'treeData', treeData: v })); @@ -176,10 +212,14 @@ export class Response implements IResponse { this._updateRepr(true); } - asString(): string { + override toString(): string { return this._responseRepr; } + toMarkdown(): string { + return this._markdownContent; + } + clear(): void { this._responseParts = []; this._updateRepr(true); @@ -232,7 +272,7 @@ export class Response implements IResponse { // Replace the resolving part's content with the resolved response if (typeof content === 'string') { - this._responseParts[responsePosition] = { ...progress, content: new MarkdownString(content) }; + (this._responseParts[responsePosition] as IChatTask).content = new MarkdownString(content); } this._updateRepr(false); }); @@ -243,6 +283,11 @@ export class Response implements IResponse { } } + public addCitation(citation: IChatCodeCitation) { + this._citations.push(citation); + this._updateRepr(); + } + private _updateRepr(quiet?: boolean) { this._responseRepr = this._responseParts.map(part => { if (part.kind === 'treeData') { @@ -264,6 +309,20 @@ export class Response implements IResponse { .filter(s => s.length > 0) .join('\n\n'); + this._responseRepr += this._citations.length ? '\n\n' + getCodeCitationsMessage(this._citations) : ''; + + this._markdownContent = this._responseParts.map(part => { + if (part.kind === 'inlineReference') { + return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference); + } else if (part.kind === 'markdownContent' || part.kind === 'markdownVuln') { + return part.content.value; + } else { + return ''; + } + }) + .filter(s => s.length > 0) + .join('\n\n'); + if (!quiet) { this._onDidChangeValue.fire(); } @@ -294,6 +353,10 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._vote; } + public get voteDownReason(): ChatAgentVoteDownReason | undefined { + return this._voteDownReason; + } + public get followups(): IChatFollowup[] | undefined { return this._followups; } @@ -340,6 +403,11 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._contentReferences; } + private readonly _codeCitations: IChatCodeCitation[] = []; + public get codeCitations(): ReadonlyArray { + return this._codeCitations; + } + private readonly _progressMessages: IChatProgressMessage[] = []; public get progressMessages(): ReadonlyArray { return this._progressMessages; @@ -359,6 +427,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _isComplete: boolean = false, private _isCanceled = false, private _vote?: ChatAgentVoteDirection, + private _voteDownReason?: ChatAgentVoteDownReason, private _result?: IChatAgentResult, followups?: ReadonlyArray ) { @@ -368,7 +437,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._isStale = Array.isArray(_response) && (_response.length !== 0 || isMarkdownString(_response) && _response.value.length !== 0); this._followups = followups ? [...followups] : undefined; - this._response = new Response(_response); + this._response = this._register(new Response(_response)); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); this.id = 'response_' + ChatResponseModel.nextId++; } @@ -392,6 +461,12 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel } } + applyCodeCitation(progress: IChatCodeCitation) { + this._codeCitations.push(progress); + this._response.addCitation(progress); + this._onDidChange.fire(); + } + setAgent(agent: IChatAgentData, slashCommand?: IChatAgentCommand) { this._agent = agent; this._slashCommand = slashCommand; @@ -429,6 +504,11 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._onDidChange.fire(); } + setVoteDownReason(reason: ChatAgentVoteDownReason | undefined): void { + this._voteDownReason = reason; + this._onDidChange.fire(); + } + setEditApplied(edit: IChatTextEditGroup, editCount: number): boolean { if (!this.response.value.includes(edit)) { return false; @@ -480,9 +560,11 @@ export interface ISerializableChatRequestData { followups: ReadonlyArray | undefined; isCanceled: boolean | undefined; vote: ChatAgentVoteDirection | undefined; + voteDownReason?: ChatAgentVoteDownReason; /** For backward compat: should be optional */ usedContext?: IChatUsedContext; contentReferences?: ReadonlyArray; + codeCitations?: ReadonlyArray; } export interface IExportableChatData { @@ -495,10 +577,89 @@ export interface IExportableChatData { responderAvatarIconUri: ThemeIcon | UriComponents | undefined; // Keeping Uri name for backcompat } -export interface ISerializableChatData extends IExportableChatData { +/* + NOTE: every time the serialized data format is updated, we need to create a new interface, because we may need to handle any old data format when parsing. +*/ + +export interface ISerializableChatData1 extends IExportableChatData { sessionId: string; creationDate: number; isImported: boolean; + + /** Indicates that this session was created in this window. Is cleared after the chat has been written to storage once. Needed to sync chat creations/deletions between empty windows. */ + isNew?: boolean; +} + +export interface ISerializableChatData2 extends ISerializableChatData1 { + version: 2; + lastMessageDate: number; + computedTitle: string | undefined; +} + +export interface ISerializableChatData3 extends Omit { + version: 3; + customTitle: string | undefined; +} + +/** + * Chat data that has been parsed and normalized to the current format. + */ +export type ISerializableChatData = ISerializableChatData3; + +/** + * Chat data that has been loaded but not normalized, and could be any format + */ +export type ISerializableChatDataIn = ISerializableChatData1 | ISerializableChatData2 | ISerializableChatData3; + +/** + * Normalize chat data from storage to the current format. + * TODO- ChatModel#_deserialize and reviveSerializedAgent also still do some normalization and maybe that should be done in here too. + */ +export function normalizeSerializableChatData(raw: ISerializableChatDataIn): ISerializableChatData { + normalizeOldFields(raw); + + if (!('version' in raw)) { + return { + version: 3, + ...raw, + lastMessageDate: raw.creationDate, + customTitle: undefined, + }; + } + + if (raw.version === 2) { + return { + ...raw, + version: 3, + customTitle: raw.computedTitle + }; + } + + return raw; +} + +function normalizeOldFields(raw: ISerializableChatDataIn): void { + // Fill in fields that very old chat data may be missing + if (!raw.sessionId) { + raw.sessionId = generateUuid(); + } + + if (!raw.creationDate) { + raw.creationDate = getLastYearDate(); + } + + if ('version' in raw && (raw.version === 2 || raw.version === 3)) { + if (!raw.lastMessageDate) { + // A bug led to not porting creationDate properly, and that was copied to lastMessageDate, so fix that up if missing. + raw.lastMessageDate = getLastYearDate(); + } + } +} + +function getLastYearDate(): number { + const lastYearDate = new Date(); + lastYearDate.setFullYear(lastYearDate.getFullYear() - 1); + return lastYearDate.getTime(); } export function isExportableSessionData(obj: unknown): obj is IExportableChatData { @@ -517,13 +678,24 @@ export function isSerializableSessionData(obj: unknown): obj is ISerializableCha ); } -export type IChatChangeEvent = IChatAddRequestEvent | IChatAddResponseEvent | IChatInitEvent | IChatRemoveRequestEvent; +export type IChatChangeEvent = + | IChatInitEvent + | IChatAddRequestEvent | IChatChangedRequestEvent | IChatRemoveRequestEvent + | IChatAddResponseEvent + | IChatSetAgentEvent + | IChatMoveEvent + ; export interface IChatAddRequestEvent { kind: 'addRequest'; request: IChatRequestModel; } +export interface IChatChangedRequestEvent { + kind: 'changedRequest'; + request: IChatRequestModel; +} + export interface IChatAddResponseEvent { kind: 'addResponse'; response: IChatResponseModel; @@ -553,6 +725,18 @@ export interface IChatRemoveRequestEvent { reason: ChatRequestRemovalReason; } +export interface IChatMoveEvent { + kind: 'move'; + target: URI; + range: IRange; +} + +export interface IChatSetAgentEvent { + kind: 'setAgent'; + agent: IChatAgentData; + command?: IChatAgentCommand; +} + export interface IChatInitEvent { kind: 'initialize'; } @@ -595,19 +779,28 @@ export class ChatModel extends Disposable implements IChatModel { } get requestInProgress(): boolean { - const lastRequest = this._requests[this._requests.length - 1]; - return !!lastRequest && !!lastRequest.response && !lastRequest.response.isComplete; + const lastRequest = this.lastRequest; + return !!lastRequest?.response && !lastRequest.response.isComplete; } get hasRequests(): boolean { return this._requests.length > 0; } + get lastRequest(): ChatRequestModel | undefined { + return this._requests.at(-1); + } + private _creationDate: number; get creationDate(): number { return this._creationDate; } + private _lastMessageDate: number; + get lastMessageDate(): number { + return this._lastMessageDate; + } + private get _defaultAgent() { return this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel); } @@ -647,8 +840,13 @@ export class ChatModel extends Disposable implements IChatModel { return this._isImported; } + private _customTitle: string | undefined; + get customTitle(): string | undefined { + return this._customTitle; + } + get title(): string { - return ChatModel.getDefaultTitle(this._requests); + return this._customTitle || ChatModel.getDefaultTitle(this._requests); } get initialLocation() { @@ -668,6 +866,8 @@ export class ChatModel extends Disposable implements IChatModel { this._sessionId = (isSerializableSessionData(initialData) && initialData.sessionId) || generateUuid(); this._requests = initialData ? this._deserialize(initialData) : []; this._creationDate = (isSerializableSessionData(initialData) && initialData.creationDate) || Date.now(); + this._lastMessageDate = (isSerializableSessionData(initialData) && initialData.lastMessageDate) || this._creationDate; + this._customTitle = isSerializableSessionData(initialData) ? initialData.customTitle : undefined; this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri); this._initialResponderAvatarIconUri = isUriComponents(initialData?.responderAvatarIconUri) ? URI.revive(initialData.responderAvatarIconUri) : initialData?.responderAvatarIconUri; @@ -701,15 +901,15 @@ export class ChatModel extends Disposable implements IChatModel { // Port entries from old format const result = 'responseErrorDetails' in raw ? + // eslint-disable-next-line local/code-no-dangerous-type-assertions { errorDetails: raw.responseErrorDetails } as IChatAgentResult : raw.result; - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, result, raw.followups); + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.voteDownReason, result, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.applyReference(revive(raw.usedContext)); } - if (raw.contentReferences) { - raw.contentReferences.forEach(r => request.response!.applyReference(revive(r))); - } + raw.contentReferences?.forEach(r => request.response!.applyReference(revive(r))); + raw.codeCitations?.forEach(c => request.response!.applyCodeCitation(revive(c))); } return request; }); @@ -725,7 +925,8 @@ export class ChatModel extends Disposable implements IChatModel { { variables: [] }; variableData.variables = variableData.variables.map((v): IChatRequestVariableEntry => { - if ('values' in v && Array.isArray(v.values)) { + // Old variables format + if (v && 'values' in v && Array.isArray(v.values)) { return { id: v.id ?? '', name: v.name, @@ -797,17 +998,26 @@ export class ChatModel extends Disposable implements IChatModel { return this._requests; } - addRequest(message: IParsedChatRequest, variableData: IChatRequestVariableData, attempt: number, chatAgent?: IChatAgentData, slashCommand?: IChatAgentCommand): ChatRequestModel { - const request = new ChatRequestModel(this, message, variableData, attempt); + addRequest(message: IParsedChatRequest, variableData: IChatRequestVariableData, attempt: number, chatAgent?: IChatAgentData, slashCommand?: IChatAgentCommand, confirmation?: string, locationData?: IChatLocationData, attachments?: IChatRequestVariableEntry[]): ChatRequestModel { + const request = new ChatRequestModel(this, message, variableData, attempt, confirmation, locationData, attachments); request.response = new ChatResponseModel([], this, chatAgent, slashCommand, request.id); this._requests.push(request); + this._lastMessageDate = Date.now(); this._onDidChange.fire({ kind: 'addRequest', request }); return request; } - adoptRequest(request: ChatRequestModel): void { + setCustomTitle(title: string): void { + this._customTitle = title; + } + + updateRequest(request: ChatRequestModel, variableData: IChatRequestVariableData) { + request.variableData = variableData; + this._onDidChange.fire({ kind: 'changedRequest', request }); + } + adoptRequest(request: ChatRequestModel): void { // this doesn't use `removeRequest` because it must not dispose the request object const oldOwner = request.session; const index = oldOwner._requests.findIndex(candidate => candidate.id === request.id); @@ -853,7 +1063,12 @@ export class ChatModel extends Disposable implements IChatModel { const agent = this.chatAgentService.getAgent(progress.agentId); if (agent) { request.response.setAgent(agent, progress.command); + this._onDidChange.fire({ kind: 'setAgent', agent, command: progress.command }); } + } else if (progress.kind === 'codeCitation') { + request.response.applyCodeCitation(progress); + } else if (progress.kind === 'move') { + this._onDidChange.fire({ kind: 'move', target: progress.uri, range: progress.range }); } else { this.logService.error(`Couldn't handle progress: ${JSON.stringify(progress)}`); } @@ -925,6 +1140,9 @@ export class ChatModel extends Disposable implements IChatModel { ...r.message, parts: r.message.parts.map(p => p && 'toJSON' in p ? (p.toJSON as Function)() : p) }; + const agent = r.response?.agent; + const agentJson = agent && 'toJSON' in agent ? (agent.toJSON as Function)() : + agent ? { ...agent } : undefined; return { message, variableData: r.variableData, @@ -944,10 +1162,12 @@ export class ChatModel extends Disposable implements IChatModel { followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, - agent: r.response?.agent ? { ...r.response.agent } : undefined, + voteDownReason: r.response?.voteDownReason, + agent: agentJson, slashCommand: r.response?.slashCommand, usedContext: r.response?.usedContext, - contentReferences: r.response?.contentReferences + contentReferences: r.response?.contentReferences, + codeCitations: r.response?.codeCitations }; }), }; @@ -955,10 +1175,13 @@ export class ChatModel extends Disposable implements IChatModel { toJSON(): ISerializableChatData { return { + version: 3, ...this.toExport(), sessionId: this.sessionId, creationDate: this._creationDate, - isImported: this._isImported + isImported: this._isImported, + lastMessageDate: this._lastMessageDate, + customTitle: this._customTitle }; } @@ -1006,35 +1229,6 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { } } -export function getHistoryEntriesFromModel(model: IChatModel, forAgentId: string | undefined): IChatAgentHistoryEntry[] { - const history: IChatAgentHistoryEntry[] = []; - for (const request of model.getRequests()) { - if (!request.response) { - continue; - } - - if (forAgentId && forAgentId !== request.response.agent?.id) { - // An agent only gets to see requests that were sent to this agent. - // The default agent (the undefined case) gets to see all of them. - continue; - } - - const promptTextResult = getPromptText(request.message); - const historyRequest: IChatAgentRequest = { - sessionId: model.sessionId, - requestId: request.id, - agentId: request.response.agent?.id ?? '', - message: promptTextResult.message, - command: request.response.slashCommand?.name, - variables: updateRanges(request.variableData, promptTextResult.diff), // TODO bit of a hack - location: ChatAgentLocation.Panel - }; - history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); - } - - return history; -} - export function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData { return { variables: variableData.variables.map(v => ({ @@ -1076,3 +1270,15 @@ export function appendMarkdownString(md1: IMarkdownString, md2: IMarkdownString baseUri: md1.baseUri }; } + +export function getCodeCitationsMessage(citations: ReadonlyArray): string { + if (citations.length === 0) { + return ''; + } + + const licenseTypes = citations.reduce((set, c) => set.add(c.license), new Set()); + const label = licenseTypes.size === 1 ? + localize('codeCitation', "Similar code found with 1 license type", licenseTypes.size) : + localize('codeCitations', "Similar code found with {0} license types", licenseTypes.size); + return label; +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index 66bc10c2..5051dc40 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -6,7 +6,7 @@ import { revive } from 'vs/base/common/marshalling'; import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IRange } from 'vs/editor/common/core/range'; -import { IChatAgentCommand, IChatAgentData, reviveSerializedAgent } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, reviveSerializedAgent } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatSlashData } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -66,6 +66,23 @@ export class ChatRequestVariablePart implements IParsedChatRequestPart { } } +/** + * An invocation of a tool + */ +export class ChatRequestToolPart implements IParsedChatRequestPart { + static readonly Kind = 'tool'; + readonly kind = ChatRequestToolPart.Kind; + constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly toolName: string, readonly toolId: string) { } + + get text(): string { + return `${chatVariableLeader}${this.toolName}`; + } + + get promptText(): string { + return this.text; + } +} + /** * An invocation of an agent that can be resolved by the agent service */ @@ -150,7 +167,14 @@ export function reviveParsedChatRequest(serialized: IParsedChatRequest): IParsed part.editorRange, (part as ChatRequestVariablePart).variableName, (part as ChatRequestVariablePart).variableArg, - (part as ChatRequestVariablePart).variableName || '', + (part as ChatRequestVariablePart).variableId || '', + ); + } else if (part.kind === ChatRequestToolPart.Kind) { + return new ChatRequestToolPart( + new OffsetRange(part.range.start, part.range.endExclusive), + part.editorRange, + (part as ChatRequestToolPart).toolName, + (part as ChatRequestToolPart).toolId ); } else if (part.kind === ChatRequestAgentPart.Kind) { let agent = (part as ChatRequestAgentPart).agent; @@ -194,3 +218,20 @@ export function extractAgentAndCommand(parsed: IParsedChatRequest): { agentPart: const commandPart = parsed.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); return { agentPart, commandPart }; } + +export function formatChatQuestion(chatAgentService: IChatAgentService, location: ChatAgentLocation, prompt: string, participant: string | null = null, command: string | null = null): string | undefined { + let question = ''; + if (participant && participant !== chatAgentService.getDefaultAgent(location)?.id) { + const agent = chatAgentService.getAgent(participant); + if (!agent) { + // Refers to agent that doesn't exist + return undefined; + } + + question += `${chatAgentLeader}${agent.name} `; + if (command) { + question += `${chatSubcommandLeader}${command} `; + } + } + return question + prompt; +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts index 638dce26..3a3b285a 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts @@ -10,6 +10,7 @@ export interface IRawChatCommandContribution { isSticky?: boolean; when?: string; defaultImplicitVariables?: string[]; + disambiguation?: { categoryName: string; description: string; examples: string[] }[]; } export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook'; @@ -26,6 +27,8 @@ export interface IRawChatParticipantContribution { commands?: IRawChatCommandContribution[]; defaultImplicitVariables?: string[]; locations?: RawChatParticipantLocation[]; + disambiguation?: { categoryName: string; description: string; examples: string[] }[]; + supportsToolReferences?: boolean; } /** diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index 73276b94..5100fc99 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -7,9 +7,10 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; const agentReg = /^@([\w_\-\.]+)(?=(\s|$|\b))/i; // An @-agent const variableReg = /^#([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // A #-variable with an optional numeric : arg (@response:2) @@ -24,7 +25,8 @@ export class ChatRequestParser { constructor( @IChatAgentService private readonly agentService: IChatAgentService, @IChatVariablesService private readonly variableService: IChatVariablesService, - @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService + @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService, + @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, ) { } parseChatRequest(sessionId: string, message: string, location: ChatAgentLocation = ChatAgentLocation.Panel, context?: IChatParserContext): IParsedChatRequest { @@ -43,7 +45,7 @@ export class ChatRequestParser { } else if (char === chatAgentLeader) { newPart = this.tryToParseAgent(message.slice(i), message, i, new Position(lineNumber, column), parts, location, context); } else if (char === chatSubcommandLeader) { - newPart = this.tryToParseSlashCommand(message.slice(i), message, i, new Position(lineNumber, column), parts); + newPart = this.tryToParseSlashCommand(message.slice(i), message, i, new Position(lineNumber, column), parts, location); } if (!newPart) { @@ -90,7 +92,7 @@ export class ChatRequestParser { }; } - private tryToParseAgent(message: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray, location: ChatAgentLocation, context: IChatParserContext | undefined): ChatRequestAgentPart | ChatRequestVariablePart | undefined { + private tryToParseAgent(message: string, fullMessage: string, offset: number, position: IPosition, parts: Array, location: ChatAgentLocation, context: IChatParserContext | undefined): ChatRequestAgentPart | ChatRequestVariablePart | undefined { const nextAgentMatch = message.match(agentReg); if (!nextAgentMatch) { return; @@ -109,11 +111,11 @@ export class ChatRequestParser { } // If there is more than one agent with this name, and the user picked it from the suggest widget, then the selected agent should be in the - // context and we use that one. Otherwise just pick the first. + // context and we use that one. const agent = agents.length > 1 && context?.selectedAgent ? context.selectedAgent : - agents[0]; - if (!agent || !agent.locations.includes(location)) { + agents.find((a) => a.locations.includes(location)); + if (!agent) { return; } @@ -137,7 +139,7 @@ export class ChatRequestParser { return new ChatRequestAgentPart(agentRange, agentEditorRange, agent); } - private tryToParseVariable(message: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestAgentPart | ChatRequestVariablePart | undefined { + private tryToParseVariable(message: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestAgentPart | ChatRequestVariablePart | ChatRequestToolPart | undefined { const nextVariableMatch = message.match(variableReg); if (!nextVariableMatch) { return; @@ -156,10 +158,15 @@ export class ChatRequestParser { return new ChatRequestVariablePart(varRange, varEditorRange, name, variableArg, variable.id); } + const tool = this.toolsService.getToolByName(name); + if (tool && tool.canBeInvokedManually && (!usedAgent || usedAgent.agent.supportsToolReferences)) { + return new ChatRequestToolPart(varRange, varEditorRange, name, tool.id); + } + return; } - private tryToParseSlashCommand(remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined { + private tryToParseSlashCommand(remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray, location: ChatAgentLocation): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined { const nextSlashMatch = remainingMessage.match(slashReg); if (!nextSlashMatch) { return; @@ -194,11 +201,19 @@ export class ChatRequestParser { return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand); } } else { - const slashCommands = this.slashCommandService.getCommands(); + const slashCommands = this.slashCommandService.getCommands(location); const slashCommand = slashCommands.find(c => c.command === command); if (slashCommand) { // Valid standalone slash command return new ChatRequestSlashCommandPart(slashRange, slashEditorRange, slashCommand); + } else { + // check for with default agent for this location + const defaultAgent = this.agentService.getDefaultAgent(location); + const subCommand = defaultAgent?.slashCommands.find(c => c.name === command); + if (subCommand) { + // Valid default agent subcommand + return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand); + } } } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatService.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatService.ts index a6921314..7942a114 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatService.ts @@ -10,6 +10,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IRange, Range } from 'vs/editor/common/core/range'; +import { ISelection } from 'vs/editor/common/core/selection'; import { Command, Location, TextEdit } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -74,12 +75,26 @@ export interface IChatContentVariableReference { value?: URI | Location; } +export enum ChatResponseReferencePartStatusKind { + Complete = 1, + Partial = 2, + Omitted = 3 +} + export interface IChatContentReference { - reference: URI | Location | IChatContentVariableReference; + reference: URI | Location | IChatContentVariableReference | string; iconPath?: ThemeIcon | { light: URI; dark?: URI }; + options?: { status?: { description: string; kind: ChatResponseReferencePartStatusKind } }; kind: 'reference'; } +export interface IChatCodeCitation { + value: URI; + license: string; + snippet: string; + kind: 'codeCitation'; +} + export interface IChatContentInlineReference { inlineReference: URI | Location; name?: string; @@ -149,6 +164,12 @@ export interface IChatCommandButton { kind: 'command'; } +export interface IChatMoveMessage { + uri: URI; + range: IRange; + kind: 'move'; +} + export interface IChatTextEdit { uri: URI; edits: TextEdit[]; @@ -159,6 +180,7 @@ export interface IChatConfirmation { title: string; message: string; data: any; + buttons?: string[]; isUsed?: boolean; kind: 'confirmation'; } @@ -170,6 +192,7 @@ export type IChatProgress = | IChatUsedContext | IChatContentReference | IChatContentInlineReference + | IChatCodeCitation | IChatAgentDetection | IChatProgressMessage | IChatTask @@ -177,6 +200,7 @@ export type IChatProgress = | IChatCommandButton | IChatWarningMessage | IChatTextEdit + | IChatMoveMessage | IChatConfirmation; export interface IChatFollowup { @@ -193,10 +217,22 @@ export enum ChatAgentVoteDirection { Up = 1 } +export enum ChatAgentVoteDownReason { + IncorrectCode = 'incorrectCode', + DidNotFollowInstructions = 'didNotFollowInstructions', + IncompleteCode = 'incompleteCode', + MissingContext = 'missingContext', + PoorlyWrittenOrFormatted = 'poorlyWrittenOrFormatted', + RefusedAValidRequest = 'refusedAValidRequest', + OffensiveOrUnsafe = 'offensiveOrUnsafe', + Other = 'other', + WillReportIssue = 'willReportIssue' +} + export interface IChatVoteAction { kind: 'vote'; direction: ChatAgentVoteDirection; - reportIssue?: boolean; + reason: ChatAgentVoteDownReason | undefined; } export enum ChatCopyKind { @@ -219,6 +255,8 @@ export interface IChatInsertAction { codeBlockIndex: number; totalCharacters: number; newFile?: boolean; + userAction?: string; + codeMapper?: string; } export interface IChatTerminalAction { @@ -251,6 +289,7 @@ export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertActi export interface IChatUserActionEvent { action: ChatUserAction; agentId: string | undefined; + command: string | undefined; sessionId: string; requestId: string; result: IChatAgentResult | undefined; @@ -277,6 +316,8 @@ export interface IChatCompleteResponse { export interface IChatDetail { sessionId: string; title: string; + lastMessageDate: number; + isActive: boolean; } export interface IChatProviderInfo { @@ -298,8 +339,28 @@ export interface IChatSendRequestData extends IChatSendRequestResponseState { slashCommand?: IChatAgentCommand; } +export interface IChatEditorLocationData { + type: ChatAgentLocation.Editor; + document: URI; + selection: ISelection; + wholeRange: IRange; +} + +export interface IChatNotebookLocationData { + type: ChatAgentLocation.Notebook; + sessionInputUri: URI; +} + +export interface IChatTerminalLocationData { + type: ChatAgentLocation.Terminal; + // TBD +} + +export type IChatLocationData = IChatEditorLocationData | IChatNotebookLocationData | IChatTerminalLocationData; + export interface IChatSendRequestOptions { location?: ChatAgentLocation; + locationData?: IChatLocationData; parserContext?: IChatParserContext; attempt?: number; noCommandDetection?: boolean; @@ -310,6 +371,11 @@ export interface IChatSendRequestOptions { /** The target agent ID can be specified with this property instead of using @ in 'message' */ agentId?: string; slashCommand?: string; + + /** + * The label of the confirmation action that was selected. + */ + confirmation?: string; } export const IChatService = createDecorator('IChatService'); @@ -337,6 +403,7 @@ export interface IChatService { clearSession(sessionId: string): void; addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void; getHistory(): IChatDetail[]; + setChatSessionTitle(sessionId: string, title: string): void; clearAllHistoryEntries(): void; removeHistoryEntry(sessionId: string): void; diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1ab8c2a4..2b6cfac7 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce } from 'vs/base/common/arrays'; import { DeferredPromise } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -11,26 +10,30 @@ import { ErrorNoTelemetry } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; -import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Progress } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ChatAgentLocation, IChatAgent, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, ChatWelcomeMessageModel, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatRequestVariableEntry, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { CONTEXT_VOTE_UP_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, ChatWelcomeMessageModel, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, normalizeSerializableChatData, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatCopyKind, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ChatAgentVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatServiceTelemetry } from 'vs/workbench/contrib/chat/common/chatServiceTelemetry'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; +import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const serializedChatKey = 'interactive.sessions'; @@ -51,8 +54,12 @@ type ChatProviderInvokedEvent = { requestType: 'string' | 'followup' | 'slashCommand'; chatSessionId: string; agent: string; + agentExtensionId: string | undefined; slashCommand: string | undefined; location: ChatAgentLocation; + citations: number; + numCodeBlocks: number; + isParticipantDetected: boolean; }; type ChatProviderInvokedClassification = { @@ -62,79 +69,42 @@ type ChatProviderInvokedClassification = { requestType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of request that the user made.' }; chatSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A random ID for the session.' }; agent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of agent used.' }; + agentExtensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension that contributed the agent.' }; slashCommand?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of slashCommand used.' }; - location?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The location at which chat request was made.' }; + location: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The location at which chat request was made.' }; + citations: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of public code citations that were returned with the response.' }; + numCodeBlocks: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of code blocks in the response.' }; + isParticipantDetected: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the participant was automatically detected.' }; owner: 'roblourens'; comment: 'Provides insight into the performance of Chat agents.'; }; -type ChatVoteEvent = { - direction: 'up' | 'down'; - agentId: string; -}; - -type ChatVoteClassification = { - direction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user voted up or down.' }; - agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that this vote is for.' }; - owner: 'roblourens'; - comment: 'Provides insight into the performance of Chat agents.'; -}; - -type ChatCopyEvent = { - copyKind: 'action' | 'toolbar'; - agentId: string; -}; - -type ChatCopyClassification = { - copyKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the copy was initiated.' }; - agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that the copy acted on.' }; - owner: 'roblourens'; - comment: 'Provides insight into the usage of Chat features.'; -}; - -type ChatInsertEvent = { - newFile: boolean; - agentId: string; -}; - -type ChatInsertClassification = { - newFile: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code was inserted into a new untitled file.' }; - agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that this insertion is for.' }; - owner: 'roblourens'; - comment: 'Provides insight into the usage of Chat features.'; -}; - -type ChatCommandEvent = { - commandId: string; - agentId: string; -}; - -type ChatCommandClassification = { - commandId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the command that was executed.' }; - agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' }; - owner: 'roblourens'; - comment: 'Provides insight into the usage of Chat features.'; -}; +const maxPersistedSessions = 25; -type ChatTerminalEvent = { - languageId: string; -}; +class CancellableRequest implements IDisposable { + constructor( + public readonly cancellationTokenSource: CancellationTokenSource, + public requestId?: string | undefined + ) { } -type ChatTerminalClassification = { - languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language of the code that was run in the terminal.' }; - owner: 'roblourens'; - comment: 'Provides insight into the usage of Chat features.'; -}; + dispose() { + this.cancellationTokenSource.dispose(); + } -const maxPersistedSessions = 25; + cancel() { + this.cancellationTokenSource.cancel(); + } +} export class ChatService extends Disposable implements IChatService { declare _serviceBrand: undefined; private readonly _sessionModels = this._register(new DisposableMap()); - private readonly _pendingRequests = this._register(new DisposableMap()); + private readonly _pendingRequests = this._register(new DisposableMap()); private _persistedSessions: ISerializableChatsData; + /** Just for empty windows, need to enforce that a chat was deleted, even though other windows still have it */ + private _deletedChatIds = new Set(); private _transferredSessionData: IChatTransferredSessionData | undefined; public get transferredSessionData(): IChatTransferredSessionData | undefined { @@ -148,6 +118,7 @@ export class ChatService extends Disposable implements IChatService { public readonly onDidDisposeSession = this._onDidDisposeSession.event; private readonly _sessionFollowupCancelTokens = this._register(new DisposableMap()); + private readonly _chatServiceTelemetry: ChatServiceTelemetry; constructor( @IStorageService private readonly storageService: IStorageService, @@ -159,10 +130,15 @@ export class ChatService extends Disposable implements IChatService { @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, @IChatAgentService private readonly chatAgentService: IChatAgentService, + @IWorkbenchAssignmentService workbenchAssignmentService: IWorkbenchAssignmentService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); - const sessionData = storageService.get(serializedChatKey, StorageScope.WORKSPACE, ''); + this._chatServiceTelemetry = this.instantiationService.createInstance(ChatServiceTelemetry); + const isEmptyWindow = !workspaceContextService.getWorkspace().folders.length; + const sessionData = storageService.get(serializedChatKey, isEmptyWindow ? StorageScope.APPLICATION : StorageScope.WORKSPACE, ''); if (sessionData) { this._persistedSessions = this.deserializeChats(sessionData); const countsForLog = Object.keys(this._persistedSessions).length; @@ -182,6 +158,10 @@ export class ChatService extends Disposable implements IChatService { } this._register(storageService.onWillSaveState(() => this.saveState())); + + const voteUpEnabled = CONTEXT_VOTE_UP_ENABLED.bindTo(contextKeyService); + workbenchAssignmentService.getTreatment('chatVoteUpEnabled') + .then(value => voteUpEnabled.set(!!value)); } isEnabled(location: ChatAgentLocation): boolean { @@ -189,59 +169,103 @@ export class ChatService extends Disposable implements IChatService { } private saveState(): void { - let allSessions: (ChatModel | ISerializableChatData)[] = Array.from(this._sessionModels.values()) + const liveChats = Array.from(this._sessionModels.values()) .filter(session => session.initialLocation === ChatAgentLocation.Panel) .filter(session => session.getRequests().length > 0); - allSessions = allSessions.concat( - Object.values(this._persistedSessions) - .filter(session => !this._sessionModels.has(session.sessionId)) - .filter(session => session.requests.length)); - allSessions.sort((a, b) => (b.creationDate ?? 0) - (a.creationDate ?? 0)); - allSessions = allSessions.slice(0, maxPersistedSessions); - if (allSessions.length) { - this.trace('onWillSaveState', `Persisting ${allSessions.length} sessions`); + + const isEmptyWindow = !this.workspaceContextService.getWorkspace().folders.length; + if (isEmptyWindow) { + this.syncEmptyWindowChats(liveChats); + } else { + let allSessions: (ChatModel | ISerializableChatData)[] = liveChats; + allSessions = allSessions.concat( + Object.values(this._persistedSessions) + .filter(session => !this._sessionModels.has(session.sessionId)) + .filter(session => session.requests.length)); + allSessions.sort((a, b) => (b.creationDate ?? 0) - (a.creationDate ?? 0)); + allSessions = allSessions.slice(0, maxPersistedSessions); + if (allSessions.length) { + this.trace('onWillSaveState', `Persisting ${allSessions.length} sessions`); + } + + const serialized = JSON.stringify(allSessions); + + if (allSessions.length) { + this.trace('onWillSaveState', `Persisting ${serialized.length} chars`); + } + + this.storageService.store(serializedChatKey, serialized, StorageScope.WORKSPACE, StorageTarget.MACHINE); } - const serialized = JSON.stringify(allSessions); + this._deletedChatIds.clear(); + } - if (allSessions.length) { - this.trace('onWillSaveState', `Persisting ${serialized.length} chars`); + private syncEmptyWindowChats(thisWindowChats: ChatModel[]): void { + // Note- an unavoidable race condition exists here. If there are multiple empty windows open, and the user quits the application, then the focused + // window may lose active chats, because all windows are reading and writing to storageService at the same time. This can't be fixed without some + // kind of locking, but in reality, the focused window will likely have run `saveState` at some point, like on a window focus change, and it will + // generally be fine. + const sessionData = this.storageService.get(serializedChatKey, StorageScope.APPLICATION, ''); + + const originalPersistedSessions = this._persistedSessions; + let persistedSessions: ISerializableChatsData; + if (sessionData) { + persistedSessions = this.deserializeChats(sessionData); + const countsForLog = Object.keys(persistedSessions).length; + if (countsForLog > 0) { + this.trace('constructor', `Restored ${countsForLog} persisted sessions`); + } + } else { + persistedSessions = {}; } - this.storageService.store(serializedChatKey, serialized, StorageScope.WORKSPACE, StorageTarget.MACHINE); + this._deletedChatIds.forEach(id => delete persistedSessions[id]); + + // Has the chat in this window been updated, and then closed? Overwrite the old persisted chats. + Object.values(originalPersistedSessions).forEach(session => { + const persistedSession = persistedSessions[session.sessionId]; + if (persistedSession && session.requests.length > persistedSession.requests.length) { + // We will add a 'modified date' at some point, but comparing the number of requests is good enough + persistedSessions[session.sessionId] = session; + } else if (!persistedSession && session.isNew) { + // This session was created in this window, and hasn't been persisted yet + session.isNew = false; + persistedSessions[session.sessionId] = session; + } + }); + + this._persistedSessions = persistedSessions; + + // Add this window's active chat models to the set to persist. + // Having the same session open in two empty windows at the same time can lead to data loss, this is acceptable + const allSessions: Record = { ...this._persistedSessions }; + for (const chat of thisWindowChats) { + allSessions[chat.sessionId] = chat; + } + + let sessionsList = Object.values(allSessions); + sessionsList.sort((a, b) => (b.creationDate ?? 0) - (a.creationDate ?? 0)); + sessionsList = sessionsList.slice(0, maxPersistedSessions); + const data = JSON.stringify(sessionsList); + this.storageService.store(serializedChatKey, data, StorageScope.APPLICATION, StorageTarget.MACHINE); } notifyUserAction(action: IChatUserActionEvent): void { - if (action.action.kind === 'vote') { - this.telemetryService.publicLog2('interactiveSessionVote', { - direction: action.action.direction === ChatAgentVoteDirection.Up ? 'up' : 'down', - agentId: action.agentId ?? '' - }); - } else if (action.action.kind === 'copy') { - this.telemetryService.publicLog2('interactiveSessionCopy', { - copyKind: action.action.copyKind === ChatCopyKind.Action ? 'action' : 'toolbar', - agentId: action.agentId ?? '' - }); - } else if (action.action.kind === 'insert') { - this.telemetryService.publicLog2('interactiveSessionInsert', { - newFile: !!action.action.newFile, - agentId: action.agentId ?? '' - }); - } else if (action.action.kind === 'command') { - // TODO not currently called - const command = CommandsRegistry.getCommand(action.action.commandButton.command.id); - const commandId = command ? action.action.commandButton.command.id : 'INVALID'; - this.telemetryService.publicLog2('interactiveSessionCommand', { - commandId, - agentId: action.agentId ?? '' - }); - } else if (action.action.kind === 'runInTerminal') { - this.telemetryService.publicLog2('interactiveSessionRunInTerminal', { - languageId: action.action.languageId ?? '' - }); + this._chatServiceTelemetry.notifyUserAction(action); + this._onDidPerformUserAction.fire(action); + } + + setChatSessionTitle(sessionId: string, title: string): void { + const model = this._sessionModels.get(sessionId); + if (model) { + model.setCustomTitle(title); + return; } - this._onDidPerformUserAction.fire(action); + const session = this._persistedSessions[sessionId]; + if (session) { + session.customTitle = title; + } } private trace(method: string, message?: string): void { @@ -258,7 +282,7 @@ export class ChatService extends Disposable implements IChatService { private deserializeChats(sessionData: string): ISerializableChatsData { try { - const arrayOfSessions: ISerializableChatData[] = revive(JSON.parse(sessionData)); // Revive serialized URIs in session data + const arrayOfSessions: ISerializableChatDataIn[] = revive(JSON.parse(sessionData)); // Revive serialized URIs in session data if (!Array.isArray(arrayOfSessions)) { throw new Error('Expected array'); } @@ -278,7 +302,7 @@ export class ChatService extends Disposable implements IChatService { } } - acc[session.sessionId] = session; + acc[session.sessionId] = normalizeSerializableChatData(session); return acc; }, {}); return sessions; @@ -312,28 +336,45 @@ export class ChatService extends Disposable implements IChatService { * Imported chat sessions are also excluded from the result. */ getHistory(): IChatDetail[] { - const sessions = Object.values(this._persistedSessions) - .filter(session => session.requests.length > 0); - sessions.sort((a, b) => (b.creationDate ?? 0) - (a.creationDate ?? 0)); + const persistedSessions = Object.values(this._persistedSessions) + .filter(session => session.requests.length > 0) + .filter(session => !this._sessionModels.has(session.sessionId)); - return sessions - .filter(session => !this._sessionModels.has(session.sessionId)) + const persistedSessionItems = persistedSessions .filter(session => !session.isImported) - .map(item => { - const title = ChatModel.getDefaultTitle(item.requests); + .map(session => { + const title = session.customTitle ?? ChatModel.getDefaultTitle(session.requests); return { - sessionId: item.sessionId, - title - }; + sessionId: session.sessionId, + title, + lastMessageDate: session.lastMessageDate, + isActive: false, + } satisfies IChatDetail; }); + const liveSessionItems = Array.from(this._sessionModels.values()) + .filter(session => !session.isImported) + .map(session => { + const title = session.title || localize('newChat', "New Chat"); + return { + sessionId: session.sessionId, + title, + lastMessageDate: session.lastMessageDate, + isActive: true, + } satisfies IChatDetail; + }); + return [...liveSessionItems, ...persistedSessionItems]; } removeHistoryEntry(sessionId: string): void { - delete this._persistedSessions[sessionId]; - this.saveState(); + if (this._persistedSessions[sessionId]) { + this._deletedChatIds.add(sessionId); + delete this._persistedSessions[sessionId]; + this.saveState(); + } } clearAllHistoryEntries(): void { + Object.values(this._persistedSessions).forEach(session => this._deletedChatIds.add(session.sessionId)); this._persistedSessions = {}; this.saveState(); } @@ -431,13 +472,17 @@ export class ChatService extends Disposable implements IChatService { model.removeRequest(request.id, ChatRequestRemovalReason.Resend); - await this._sendRequestAsync(model, model.sessionId, request.message, attempt, enableCommandDetection, defaultAgent, location, options).responseCompletePromise; + const resendOptions: IChatSendRequestOptions = { + ...options, + locationData: request.locationData, + attachedContext: request.attachedContext, + }; + await this._sendRequestAsync(model, model.sessionId, request.message, attempt, enableCommandDetection, defaultAgent, location, resendOptions).responseCompletePromise; } async sendRequest(sessionId: string, request: string, options?: IChatSendRequestOptions): Promise { - this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); - if (!request.trim()) { + if (!request.trim() && !options?.slashCommand && !options?.agentId) { this.trace('sendRequest', 'Rejected empty message'); return; } @@ -500,6 +545,7 @@ export class ChatService extends Disposable implements IChatService { const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart); + const requests = [...model.getRequests()]; let gotProgress = false; const requestType = commandPart ? 'slashCommand' : 'string'; @@ -533,6 +579,9 @@ export class ChatService extends Disposable implements IChatService { completeResponseCreated(); }; + let detectedAgent: IChatAgentData | undefined; + let detectedCommand: IChatAgentCommand | undefined; + const stopWatch = new StopWatch(false); const listener = token.onCancellationRequested(() => { this.trace('sendRequest', `Request for session ${model.sessionId} was cancelled`); @@ -543,9 +592,13 @@ export class ChatService extends Disposable implements IChatService { result: 'cancelled', requestType, agent: agentPart?.agent.id ?? '', + agentExtensionId: agentPart?.agent.extensionId.value ?? '', slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId, location, + citations: request?.response?.codeCitations.length ?? 0, + numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length, + isParticipantDetected: !!detectedAgent }); model.cancelRequest(request); @@ -554,53 +607,68 @@ export class ChatService extends Disposable implements IChatService { try { let rawResult: IChatAgentResult | null | undefined; let agentOrCommandFollowups: Promise | undefined = undefined; + let chatTitlePromise: Promise | undefined; if (agentPart || (defaultAgent && !commandPart)) { - const agent = (agentPart?.agent ?? defaultAgent)!; - await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); - const history = getHistoryEntriesFromModel(model, agentPart?.agent.id); + const prepareChatAgentRequest = async (agent: IChatAgentData, command?: IChatAgentCommand, enableCommandDetection?: boolean, chatRequest?: ChatRequestModel, isParticipantDetected?: boolean) => { + const initVariableData: IChatRequestVariableData = { variables: [] }; + request = chatRequest ?? model.addRequest(parsedRequest, initVariableData, attempt, agent, command, options?.confirmation, options?.locationData, options?.attachedContext); + + // Variables may have changed if the agent and slash command changed, so resolve them again even if we already had a chatRequest + const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, request.attachedContext, model, progressCallback, token); + model.updateRequest(request, variableData); + const promptTextResult = getPromptText(request.message); + const updatedVariableData = updateRanges(variableData, promptTextResult.diff); // TODO bit of a hack + + return { + sessionId, + requestId: request.id, + agentId: agent.id, + message: promptTextResult.message, + command: command?.name, + variables: updatedVariableData, + enableCommandDetection, + isParticipantDetected, + attempt, + location, + locationData: request.locationData, + acceptedConfirmationData: options?.acceptedConfirmationData, + rejectedConfirmationData: options?.rejectedConfirmationData, + } satisfies IChatAgentRequest; + }; - const initVariableData: IChatRequestVariableData = { variables: [] }; - request = model.addRequest(parsedRequest, initVariableData, attempt, agent, agentSlashCommandPart?.command); - completeResponseCreated(); - const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, options?.attachedContext, model, progressCallback, token); - request.variableData = variableData; - - const promptTextResult = getPromptText(request.message); - const updatedVariableData = updateRanges(variableData, promptTextResult.diff); // TODO bit of a hack - - // TODO- should figure out how to get rid of implicit variables for inline chat - const implicitVariablesEnabled = (location === ChatAgentLocation.Editor || location === ChatAgentLocation.Notebook); - if (implicitVariablesEnabled) { - const implicitVariables = agent.defaultImplicitVariables; - if (implicitVariables) { - const resolvedImplicitVariables = await Promise.all(implicitVariables.map(async v => { - const id = this.chatVariablesService.getVariable(v)?.id ?? ''; - const value = await this.chatVariablesService.resolveVariable(v, parsedRequest.text, model, progressCallback, token); - return value ? { id, name: v, value } satisfies IChatRequestVariableEntry : - undefined; - })); - updatedVariableData.variables.push(...coalesce(resolvedImplicitVariables)); + if (this.configurationService.getValue('chat.experimental.detectParticipant.enabled') !== false && this.chatAgentService.hasChatParticipantDetectionProviders() && !agentPart && !commandPart && enableCommandDetection) { + // We have no agent or command to scope history with, pass the full history to the participant detection provider + const defaultAgentHistory = this.getHistoryEntriesFromModel(requests, model.sessionId, location, defaultAgent.id); + + // Prepare the request object that we will send to the participant detection provider + const chatAgentRequest = await prepareChatAgentRequest(defaultAgent, agentSlashCommandPart?.command, enableCommandDetection, undefined, false); + + const result = await this.chatAgentService.detectAgentOrCommand(chatAgentRequest, defaultAgentHistory, { location }, token); + if (result && this.chatAgentService.getAgent(result.agent.id)?.locations?.includes(location)) { + // Update the response in the ChatModel to reflect the detected agent and command + request.response?.setAgent(result.agent, result.command); + detectedAgent = result.agent; + detectedCommand = result.command; } } - const requestProps: IChatAgentRequest = { - sessionId, - requestId: request.id, - agentId: agent.id, - message: promptTextResult.message, - command: agentSlashCommandPart?.command.name, - variables: updatedVariableData, - enableCommandDetection, - attempt, - location, - acceptedConfirmationData: options?.acceptedConfirmationData, - rejectedConfirmationData: options?.rejectedConfirmationData, - }; + const agent = (detectedAgent ?? agentPart?.agent ?? defaultAgent)!; + const command = detectedCommand ?? agentSlashCommandPart?.command; + await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); + // Recompute history in case the agent or command changed + const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id); + const requestProps = await prepareChatAgentRequest(agent, command, enableCommandDetection, request /* Reuse the request object if we already created it for participant detection */, !!detectedAgent); + const pendingRequest = this._pendingRequests.get(sessionId); + if (pendingRequest && !pendingRequest.requestId) { + pendingRequest.requestId = requestProps.requestId; + } + completeResponseCreated(); const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken); + chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, location, agent.id), CancellationToken.None) : undefined; } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { variables: [] }, attempt); completeResponseCreated(); @@ -611,13 +679,13 @@ export class ChatService extends Disposable implements IChatService { if (!request.response) { continue; } - history.push({ role: ChatMessageRole.User, content: request.message.text }); - history.push({ role: ChatMessageRole.Assistant, content: request.response.response.asString() }); + history.push({ role: ChatMessageRole.User, content: [{ type: 'text', value: request.message.text }] }); + history.push({ role: ChatMessageRole.Assistant, content: [{ type: 'text', value: request.response.response.toString() }] }); } const message = parsedRequest.text; const commandResult = await this.chatSlashCommandService.executeCommand(commandPart.slashCommand.command, message.substring(commandPart.slashCommand.command.length + 1).trimStart(), new Progress(p => { progressCallback(p); - }), history, token); + }), history, location, token); agentOrCommandFollowups = Promise.resolve(commandResult?.followUp); rawResult = {}; @@ -637,15 +705,20 @@ export class ChatService extends Disposable implements IChatService { rawResult.errorDetails && gotProgress ? 'errorWithOutput' : rawResult.errorDetails ? 'error' : 'success'; + const commandForTelemetry = agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command; this.telemetryService.publicLog2('interactiveSessionProviderInvoked', { timeToFirstProgress: rawResult.timings?.firstProgress, totalTime: rawResult.timings?.totalElapsed, result, requestType, agent: agentPart?.agent.id ?? '', - slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, + agentExtensionId: agentPart?.agent.extensionId.value ?? '', + slashCommand: commandForTelemetry, chatSessionId: model.sessionId, - location + isParticipantDetected: !!detectedAgent, + location, + citations: request.response?.codeCitations.length ?? 0, + numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length }); model.setResponse(request, rawResult); completeResponseCreated(); @@ -655,8 +728,14 @@ export class ChatService extends Disposable implements IChatService { if (agentOrCommandFollowups) { agentOrCommandFollowups.then(followups => { model.setFollowups(request, followups); + this._chatServiceTelemetry.retrievedFollowups(agentPart?.agent.id ?? '', commandForTelemetry, followups?.length ?? 0); }); } + chatTitlePromise?.then(title => { + if (title) { + model.setCustomTitle(title); + } + }); } } catch (err) { const result = 'error'; @@ -666,23 +745,27 @@ export class ChatService extends Disposable implements IChatService { result, requestType, agent: agentPart?.agent.id ?? '', + agentExtensionId: agentPart?.agent.extensionId.value ?? '', slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId, - location + location, + citations: 0, + numCodeBlocks: 0, + isParticipantDetected: !!detectedAgent }); - const rawResult: IChatAgentResult = { errorDetails: { message: err.message } }; + this.logService.error(`Error while handling chat request: ${toErrorMessage(err, true)}`); if (request) { + const rawResult: IChatAgentResult = { errorDetails: { message: err.message } }; model.setResponse(request, rawResult); + completeResponseCreated(); + model.completeResponse(request); } - completeResponseCreated(); - this.logService.error(`Error while handling chat request: ${toErrorMessage(err)}`); - model.completeResponse(request); } finally { listener.dispose(); } }; const rawResponsePromise = sendRequestInternal(); - this._pendingRequests.set(model.sessionId, source); + this._pendingRequests.set(model.sessionId, new CancellableRequest(source)); rawResponsePromise.finally(() => { this._pendingRequests.deleteAndDispose(model.sessionId); }); @@ -692,6 +775,36 @@ export class ChatService extends Disposable implements IChatService { }; } + private getHistoryEntriesFromModel(requests: IChatRequestModel[], sessionId: string, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] { + const history: IChatAgentHistoryEntry[] = []; + for (const request of requests) { + if (!request.response) { + continue; + } + + const defaultAgentId = this.chatAgentService.getDefaultAgent(location)?.id; + if (forAgentId !== request.response.agent?.id && forAgentId !== defaultAgentId) { + // An agent only gets to see requests that were sent to this agent. + // The default agent (the undefined case) gets to see all of them. + continue; + } + + const promptTextResult = getPromptText(request.message); + const historyRequest: IChatAgentRequest = { + sessionId: sessionId, + requestId: request.id, + agentId: request.response.agent?.id ?? '', + message: promptTextResult.message, + command: request.response.slashCommand?.name, + variables: updateRanges(request.variableData, promptTextResult.diff), // TODO bit of a hack + location: ChatAgentLocation.Panel + }; + history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); + } + + return history; + } + async removeRequest(sessionId: string, requestId: string): Promise { const model = this._sessionModels.get(sessionId); if (!model) { @@ -700,6 +813,12 @@ export class ChatService extends Disposable implements IChatService { await model.waitForInitialization(); + const pendingRequest = this._pendingRequests.get(sessionId); + if (pendingRequest?.requestId === requestId) { + pendingRequest.cancel(); + this._pendingRequests.deleteAndDispose(sessionId); + } + model.removeRequest(requestId); } @@ -720,6 +839,7 @@ export class ChatService extends Disposable implements IChatService { if (request.response && !request.response.isComplete) { const cts = this._pendingRequests.deleteAndLeak(oldOwner.sessionId); if (cts) { + cts.requestId = request.id; this._pendingRequests.set(target.sessionId, cts); } } @@ -769,7 +889,9 @@ export class ChatService extends Disposable implements IChatService { if (model.initialLocation === ChatAgentLocation.Panel) { // Turn all the real objects into actual JSON, otherwise, calling 'revive' may fail when it tries to // assign values to properties that are getters- microsoft/vscode-copilot-release#1233 - this._persistedSessions[sessionId] = JSON.parse(JSON.stringify(model)); + const sessionData: ISerializableChatData = JSON.parse(JSON.stringify(model)); + sessionData.isNew = true; + this._persistedSessions[sessionId] = sessionData; } this._sessionModels.deleteAndDispose(sessionId); @@ -800,3 +922,26 @@ export class ChatService extends Disposable implements IChatService { this.trace('transferChatSession', `Transferred session ${model.sessionId} to workspace ${toWorkspace.toString()}`); } } + +function getCodeBlocks(text: string): string[] { + const lines = text.split('\n'); + const codeBlockLanguages: string[] = []; + + let codeBlockState: undefined | { readonly delimiter: string; readonly languageId: string }; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + if (codeBlockState) { + if (new RegExp(`^\\s*${codeBlockState.delimiter}\\s*$`).test(line)) { + codeBlockLanguages.push(codeBlockState.languageId); + codeBlockState = undefined; + } + } else { + const match = line.match(/^(\s*)(`{3,}|~{3,})(\w*)/); + if (match) { + codeBlockState = { delimiter: match[2], languageId: match[3] }; + } + } + } + return codeBlockLanguages; +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatServiceTelemetry.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatServiceTelemetry.ts new file mode 100644 index 00000000..2b7ff3cc --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatServiceTelemetry.ts @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IChatUserActionEvent, ChatAgentVoteDirection, ChatCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; + +type ChatVoteEvent = { + direction: 'up' | 'down'; + agentId: string; + command: string | undefined; + reason: string | undefined; +}; + +type ChatVoteClassification = { + direction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user voted up or down.' }; + agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that this vote is for.' }; + command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the slash command that this vote is for.' }; + reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason selected by the user for voting down.' }; + owner: 'roblourens'; + comment: 'Provides insight into the performance of Chat agents.'; +}; + +type ChatCopyEvent = { + copyKind: 'action' | 'toolbar'; + agentId: string; + command: string | undefined; +}; + +type ChatCopyClassification = { + copyKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the copy was initiated.' }; + agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that the copy acted on.' }; + command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the slash command the copy acted on.' }; + owner: 'roblourens'; + comment: 'Provides insight into the usage of Chat features.'; +}; + +type ChatInsertEvent = { + newFile: boolean; + agentId: string; + command: string | undefined; + userAction: string | undefined; + codeMapper: string | undefined; +}; + +type ChatInsertClassification = { + newFile: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code was inserted into a new untitled file.' }; + agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that this insertion is for.' }; + command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the slash command that this insertion is for.' }; + userAction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Id of the UI command that was used to do the insert.' }; + codeMapper: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The code mapper that wa used to compute the edit.' }; + owner: 'roblourens'; + comment: 'Provides insight into the usage of Chat features.'; +}; + +type ChatCommandEvent = { + commandId: string; + agentId: string; + command: string | undefined; +}; + +type ChatCommandClassification = { + commandId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the command that was executed.' }; + agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' }; + command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the related slash command.' }; + owner: 'roblourens'; + comment: 'Provides insight into the usage of Chat features.'; +}; + +type ChatFollowupEvent = { + agentId: string; + command: string | undefined; +}; + +type ChatFollowupClassification = { + agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' }; + command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the related slash command.' }; + owner: 'roblourens'; + comment: 'Provides insight into the usage of Chat features.'; +}; + +type ChatTerminalEvent = { + languageId: string; + agentId: string; + command: string | undefined; +}; + +type ChatTerminalClassification = { + languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language of the code that was run in the terminal.' }; + agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' }; + command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the related slash command.' }; + owner: 'roblourens'; + comment: 'Provides insight into the usage of Chat features.'; +}; + +type ChatFollowupsRetrievedEvent = { + agentId: string; + command: string | undefined; + numFollowups: number; +}; + +type ChatFollowupsRetrievedClassification = { + agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' }; + command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the related slash command.' }; + numFollowups: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of followup prompts returned by the agent.' }; + owner: 'roblourens'; + comment: 'Provides insight into the usage of Chat features.'; +}; + +export class ChatServiceTelemetry { + constructor( + @ITelemetryService private readonly telemetryService: ITelemetryService, + ) { } + + notifyUserAction(action: IChatUserActionEvent): void { + if (action.action.kind === 'vote') { + this.telemetryService.publicLog2('interactiveSessionVote', { + direction: action.action.direction === ChatAgentVoteDirection.Up ? 'up' : 'down', + agentId: action.agentId ?? '', + command: action.command, + reason: action.action.reason, + }); + } else if (action.action.kind === 'copy') { + this.telemetryService.publicLog2('interactiveSessionCopy', { + copyKind: action.action.copyKind === ChatCopyKind.Action ? 'action' : 'toolbar', + agentId: action.agentId ?? '', + command: action.command, + }); + } else if (action.action.kind === 'insert') { + this.telemetryService.publicLog2('interactiveSessionInsert', { + newFile: !!action.action.newFile, + userAction: action.action.userAction, + codeMapper: action.action.codeMapper, + agentId: action.agentId ?? '', + command: action.command, + }); + } else if (action.action.kind === 'command') { + // TODO not currently called + const command = CommandsRegistry.getCommand(action.action.commandButton.command.id); + const commandId = command ? action.action.commandButton.command.id : 'INVALID'; + this.telemetryService.publicLog2('interactiveSessionCommand', { + commandId, + agentId: action.agentId ?? '', + command: action.command, + }); + } else if (action.action.kind === 'runInTerminal') { + this.telemetryService.publicLog2('interactiveSessionRunInTerminal', { + languageId: action.action.languageId ?? '', + agentId: action.agentId ?? '', + command: action.command, + }); + } else if (action.action.kind === 'followUp') { + this.telemetryService.publicLog2('chatFollowupClicked', { + agentId: action.agentId ?? '', + command: action.command, + }); + } + } + + retrievedFollowups(agentId: string, command: string | undefined, numFollowups: number): void { + this.telemetryService.publicLog2('chatFollowupsRetrieved', { + agentId, + command, + numFollowups, + }); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 2d43f1c0..3b19cd51 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -11,6 +11,7 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { IChatFollowup, IChatProgress, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; //#region slash service, commands etc @@ -18,18 +19,18 @@ export interface IChatSlashData { command: string; detail: string; sortText?: string; - /** * Whether the command should execute as soon * as it is entered. Defaults to `false`. */ executeImmediately?: boolean; + locations: ChatAgentLocation[]; } export interface IChatSlashFragment { content: string | { treeData: IChatResponseProgressFileTreeData }; } -export type IChatSlashCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> }; +export type IChatSlashCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], location: ChatAgentLocation, token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> }; export const IChatSlashCommandService = createDecorator('chatSlashCommandService'); @@ -40,8 +41,8 @@ export interface IChatSlashCommandService { _serviceBrand: undefined; readonly onDidChangeCommands: Event; registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable; - executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void>; - getCommands(): Array; + executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], location: ChatAgentLocation, token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void>; + getCommands(location: ChatAgentLocation): Array; hasCommand(id: string): boolean; } @@ -80,15 +81,15 @@ export class ChatSlashCommandService extends Disposable implements IChatSlashCom }); } - getCommands(): Array { - return Array.from(this._commands.values(), v => v.data); + getCommands(location: ChatAgentLocation): Array { + return Array.from(this._commands.values(), v => v.data).filter(c => c.locations.includes(location)); } hasCommand(id: string): boolean { return this._commands.has(id); } - async executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> { + async executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], location: ChatAgentLocation, token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> { const data = this._commands.get(id); if (!data) { throw new Error('No command with id ${id} NOT registered'); @@ -100,6 +101,6 @@ export class ChatSlashCommandService extends Disposable implements IChatSlashCom throw new Error(`No command with id ${id} NOT resolved`); } - return await data.command(prompt, progress, history, token); + return await data.command(prompt, progress, history, location, token); } } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatVariables.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatVariables.ts index 1df71e98..8f8e394a 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -23,7 +23,6 @@ export interface IChatVariableData { description: string; modelDescription?: string; isSlow?: boolean; - hidden?: boolean; canTakeArgument?: boolean; } @@ -44,7 +43,7 @@ export interface IChatVariablesService { registerVariable(data: IChatVariableData, resolver: IChatVariableResolver): IDisposable; hasVariable(name: string): boolean; getVariable(name: string): IChatVariableData | undefined; - getVariables(): Iterable>; + getVariables(location: ChatAgentLocation): Iterable>; getDynamicVariables(sessionId: string): ReadonlyArray; // should be its own service? attachContext(name: string, value: string | URI | Location | unknown, location: ChatAgentLocation): void; diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 130ef769..9d52570e 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -6,18 +6,19 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; -import { marked } from 'vs/base/common/marked/marked'; +import * as marked from 'vs/base/common/marked/marked'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { annotateVulnerabilitiesInText } from 'vs/workbench/contrib/chat/common/annotations'; import { getFullyQualifiedId, IChatAgentCommand, IChatAgentData, IChatAgentNameService, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatTextEditGroup, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModelInitState, IChatModel, IChatProgressRenderableResponseContent, IChatRequestModel, IChatRequestVariableEntry, IChatResponseModel, IChatTextEditGroup, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { ChatAgentVoteDirection, IChatCommandButton, IChatConfirmation, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTask, IChatUsedContext, IChatWarningMessage } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatCodeCitation, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseErrorDetails, IChatTask, IChatUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { CodeBlockModelCollection } from './codeBlockModelCollection'; +import { hash } from 'vs/base/common/hash'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { return !!item && typeof item === 'object' && 'message' in item; @@ -68,7 +69,10 @@ export interface IChatRequestViewModel { readonly message: IParsedChatRequest | IChatFollowup; readonly messageText: string; readonly attempt: number; + readonly variables: IChatRequestVariableEntry[]; currentRenderedHeight: number | undefined; + readonly contentReferences?: ReadonlyArray; + readonly confirmation?: string; } export interface IChatResponseMarkdownRenderData { @@ -78,6 +82,13 @@ export interface IChatResponseMarkdownRenderData { originalMarkdown: IMarkdownString; } +export interface IChatResponseMarkdownRenderData2 { + renderedWordCount: number; + lastRenderTime: number; + isFullyRendered: boolean; + originalMarkdown: IMarkdownString; +} + export interface IChatProgressMessageRenderData { progressMessage: IChatProgressMessage; @@ -101,13 +112,36 @@ export interface IChatTaskRenderData { progressLength: number; } -export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData | IChatCommandButton | IChatTextEditGroup | IChatConfirmation | IChatTaskRenderData | IChatWarningMessage; export interface IChatResponseRenderData { - renderedParts: IChatRenderData[]; + renderedParts: IChatRendererContent[]; + + renderedWordCount: number; + lastRenderTime: number; } +/** + * Content type for references used during rendering, not in the model + */ +export interface IChatReferences { + references: ReadonlyArray; + kind: 'references'; +} + +/** + * Content type for citations used during rendering, not in the model + */ +export interface IChatCodeCitations { + citations: ReadonlyArray; + kind: 'codeCitations'; +} + +/** + * Type for content parts rendered by IChatListRenderer + */ +export type IChatRendererContent = IChatProgressRenderableResponseContent | IChatReferences | IChatCodeCitations; + export interface IChatLiveUpdateData { - loadingStartTime: number; + firstWordTime: number; lastUpdateTime: number; impliedWordLoadRate: number; lastWordCount: number; @@ -129,11 +163,13 @@ export interface IChatResponseViewModel { readonly response: IResponse; readonly usedContext: IChatUsedContext | undefined; readonly contentReferences: ReadonlyArray; + readonly codeCitations: ReadonlyArray; readonly progressMessages: ReadonlyArray; readonly isComplete: boolean; readonly isCanceled: boolean; readonly isStale: boolean; readonly vote: ChatAgentVoteDirection | undefined; + readonly voteDownReason: ChatAgentVoteDownReason | undefined; readonly replyFollowups?: IChatFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; readonly result?: IChatAgentResult; @@ -141,6 +177,7 @@ export interface IChatResponseViewModel { renderData?: IChatResponseRenderData; currentRenderedHeight: number | undefined; setVote(vote: ChatAgentVoteDirection): void; + setVoteDownReason(reason: ChatAgentVoteDownReason | undefined): void; usedReferencesExpanded?: boolean; vulnerabilitiesListExpanded: boolean; setEditApplied(edit: IChatTextEditGroup, editCount: number): void; @@ -271,38 +308,13 @@ export class ChatViewModel extends Disposable implements IChatViewModel { } let codeBlockIndex = 0; - const renderer = new marked.Renderer(); - renderer.code = (value, languageId) => { - languageId ??= ''; - this.codeBlockModelCollection.update(this._model.sessionId, model, codeBlockIndex++, { text: value, languageId }); - return ''; - }; - - marked.parse(this.ensureFencedCodeBlocksTerminated(content), { renderer }); - } - - /** - * Marked doesn't consistently render fenced code blocks that aren't terminated. - * - * Try to close them ourselves to workaround this. - */ - private ensureFencedCodeBlocksTerminated(content: string): string { - const lines = content.split('\n'); - let inCodeBlock = false; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.startsWith('```')) { - inCodeBlock = !inCodeBlock; + marked.walkTokens(marked.lexer(content), token => { + if (token.type === 'code') { + const lang = token.lang || ''; + const text = token.text; + this.codeBlockModelCollection.update(this._model.sessionId, model, codeBlockIndex++, { text, languageId: lang }); } - } - - // If we're still in a code block at the end of the content, add a closing fence - if (inCodeBlock) { - lines.push('```'); - } - - return lines.join('\n'); + }); } } @@ -312,7 +324,7 @@ export class ChatRequestViewModel implements IChatRequestViewModel { } get dataId() { - return this.id + `_${ChatModelInitState[this._model.session.initState]}`; + return this.id + `_${ChatModelInitState[this._model.session.initState]}_${hash(this.variables)}`; } get sessionId() { @@ -339,6 +351,18 @@ export class ChatRequestViewModel implements IChatRequestViewModel { return this._model.attempt; } + get variables() { + return this._model.variableData.variables; + } + + get contentReferences() { + return this._model.response?.contentReferences; + } + + get confirmation() { + return this._model.confirmation; + } + currentRenderedHeight: number | undefined; constructor( @@ -409,6 +433,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.contentReferences; } + get codeCitations(): ReadonlyArray { + return this._model.codeCitations; + } + get progressMessages(): ReadonlyArray { return this._model.progressMessages; } @@ -437,6 +465,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.vote; } + get voteDownReason() { + return this._model.voteDownReason; + } + get requestId() { return this._model.requestId; } @@ -484,7 +516,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi if (!_model.isComplete) { this._contentUpdateTimings = { - loadingStartTime: Date.now(), + firstWordTime: 0, lastUpdateTime: Date.now(), impliedWordLoadRate: 0, lastWordCount: 0 @@ -492,15 +524,17 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi } this._register(_model.onDidChange(() => { + // This should be true, if the model is changing if (this._contentUpdateTimings) { - // This should be true, if the model is changing const now = Date.now(); - const wordCount = countWords(_model.response.asString()); - const timeDiff = now - this._contentUpdateTimings.loadingStartTime; + const wordCount = countWords(_model.response.toString()); + + // Apply a min time difference, or the rate is typically too high for first few words + const timeDiff = Math.max(now - this._contentUpdateTimings.firstWordTime, 250); const impliedWordLoadRate = this._contentUpdateTimings.lastWordCount / (timeDiff / 1000); - this.trace('onDidChange', `Update- got ${this._contentUpdateTimings.lastWordCount} words over ${timeDiff}ms = ${impliedWordLoadRate} words/s. ${wordCount} words are now available.`); + this.trace('onDidChange', `Update- got ${this._contentUpdateTimings.lastWordCount} words over last ${timeDiff}ms = ${impliedWordLoadRate} words/s. ${wordCount} words are now available.`); this._contentUpdateTimings = { - loadingStartTime: this._contentUpdateTimings.loadingStartTime, + firstWordTime: this._contentUpdateTimings.firstWordTime === 0 && this.response.value.some(v => v.kind === 'markdownContent') ? now : this._contentUpdateTimings.firstWordTime, lastUpdateTime: now, impliedWordLoadRate, lastWordCount: wordCount @@ -525,6 +559,11 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi this._model.setVote(vote); } + setVoteDownReason(reason: ChatAgentVoteDownReason | undefined): void { + this._modelChangeCount++; + this._model.setVoteDownReason(reason); + } + setEditApplied(edit: IChatTextEditGroup, editCount: number) { this._modelChangeCount++; this._model.setEditApplied(edit, editCount); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/chatWordCounter.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/chatWordCounter.ts index 94870296..c1989d4f 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/chatWordCounter.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/chatWordCounter.ts @@ -5,13 +5,48 @@ export interface IWordCountResult { value: string; - actualWordCount: number; + returnedWordCount: number; + totalWordCount: number; isFullString: boolean; } +const r = String.raw; + +/** + * Matches `[text](link title?)` or `[text]( title?)` + * + * Taken from vscode-markdown-languageservice + */ +const linkPattern = + r`(? + /**/r`(?:` + + /*****/r`[^\[\]\\]|` + // Non-bracket chars, or... + /*****/r`\\.|` + // Escaped char, or... + /*****/r`\[[^\[\]]*\]` + // Matched bracket pair + /**/r`)*` + + r`\])` + // <-- close prefix match + + // Destination + r`(\(\s*)` + // Pre href + /**/r`(` + + /*****/r`[^\s\(\)<](?:[^\s\(\)]|\([^\s\(\)]*?\))*|` + // Link without whitespace, or... + /*****/r`<(?:\\[<>]|[^<>])+>` + // In angle brackets + /**/r`)` + + + // Title + /**/r`\s*(?:"[^"]*"|'[^']*'|\([^\(\)]*\))?\s*` + + r`\)`; + export function getNWords(str: string, numWordsToCount: number): IWordCountResult { - // Match words and markdown style links - const allWordMatches = Array.from(str.matchAll(/\[([^\]]+)\]\(([^)]+)\)|[^\s\|\-]+/g)); + // This regex matches each word and skips over whitespace and separators. A word is: + // A markdown link + // One chinese character + // One or more + - =, handled so that code like "a=1+2-3" is broken up better + // One or more characters that aren't whitepace or any of the above + const allWordMatches = Array.from(str.matchAll(new RegExp(linkPattern + r`|\p{sc=Han}|=+|\++|-+|[^\s\|\p{sc=Han}|=|\+|\-]+`, 'gu'))); const targetWords = allWordMatches.slice(0, numWordsToCount); @@ -22,12 +57,13 @@ export function getNWords(str: string, numWordsToCount: number): IWordCountResul const value = str.substring(0, endIndex); return { value, - actualWordCount: targetWords.length === 0 ? (value.length ? 1 : 0) : targetWords.length, - isFullString: endIndex >= str.length + returnedWordCount: targetWords.length === 0 ? (value.length ? 1 : 0) : targetWords.length, + isFullString: endIndex >= str.length, + totalWordCount: allWordMatches.length }; } export function countWords(str: string): number { const result = getNWords(str, Number.MAX_SAFE_INTEGER); - return result.actualWordCount; + return result.returnedWordCount; } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts index a857b64c..d167cb4b 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts @@ -22,6 +22,13 @@ export class CodeBlockModelCollection extends Disposable { vulns: readonly IMarkdownVulnerability[]; }>(); + /** + * Max number of models to keep in memory. + * + * Currently always maintains the most recently created models. + */ + private readonly maxModelCount = 100; + constructor( @ILanguageService private readonly languageService: ILanguageService, @ITextModelService private readonly textModelService: ITextModelService @@ -52,9 +59,28 @@ export class CodeBlockModelCollection extends Disposable { const uri = this.getUri(sessionId, chat, codeBlockIndex); const ref = this.textModelService.createModelReference(uri); this._models.set(uri, { model: ref, vulns: [] }); + + while (this._models.size > this.maxModelCount) { + const first = Array.from(this._models.keys()).at(0); + if (!first) { + break; + } + this.delete(first); + } + return { model: ref.then(ref => ref.object), vulns: [] }; } + private delete(codeBlockUri: URI) { + const entry = this._models.get(codeBlockUri); + if (!entry) { + return; + } + + entry.model.then(ref => ref.dispose()); + this._models.delete(codeBlockUri); + } + clear(): void { this._models.forEach(async entry => (await entry.model).dispose()); this._models.clear(); @@ -116,6 +142,10 @@ export class CodeBlockModelCollection extends Disposable { return { references: chat.contentReferences.map(ref => { + if (typeof ref.reference === 'string') { + return; + } + const uriOrLocation = 'variableName' in ref.reference ? ref.reference.value : ref.reference; diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts new file mode 100644 index 00000000..9a951927 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -0,0 +1,178 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RunOnceScheduler } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { URI } from 'vs/base/common/uri'; +import { ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; + +export interface IToolData { + id: string; + name?: string; + icon?: { dark: URI; light?: URI } | ThemeIcon; + when?: ContextKeyExpression; + displayName?: string; + userDescription?: string; + modelDescription: string; + parametersSchema?: IJSONSchema; + canBeInvokedManually?: boolean; +} + +interface IToolEntry { + data: IToolData; + impl?: IToolImpl; +} + +export interface IToolInvocation { + callId: string; + toolId: string; + parameters: any; + tokenBudget?: number; +} + +export interface IToolResult { + [contentType: string]: any; + string: string; +} + +export interface IToolImpl { + invoke(dto: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise; +} + +export const ILanguageModelToolsService = createDecorator('ILanguageModelToolsService'); + +export type CountTokensCallback = (input: string, token: CancellationToken) => Promise; + +export interface ILanguageModelToolsService { + _serviceBrand: undefined; + onDidChangeTools: Event; + registerToolData(toolData: IToolData): IDisposable; + registerToolImplementation(name: string, tool: IToolImpl): IDisposable; + getTools(): Iterable>; + getTool(id: string): IToolData | undefined; + getToolByName(name: string): IToolData | undefined; + invokeTool(dto: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise; +} + +export class LanguageModelToolsService extends Disposable implements ILanguageModelToolsService { + _serviceBrand: undefined; + + private _onDidChangeTools = new Emitter(); + readonly onDidChangeTools = this._onDidChangeTools.event; + + /** Throttle tools updates because it sends all tools and runs on context key updates */ + private _onDidChangeToolsScheduler = new RunOnceScheduler(() => this._onDidChangeTools.fire(), 750); + + private _tools = new Map(); + private _toolContextKeys = new Set(); + + constructor( + @IExtensionService private readonly _extensionService: IExtensionService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + ) { + super(); + + this._register(this._contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(this._toolContextKeys)) { + // Not worth it to compute a delta here unless we have many tools changing often + this._onDidChangeToolsScheduler.schedule(); + } + })); + } + + registerToolData(toolData: IToolData): IDisposable { + if (this._tools.has(toolData.id)) { + throw new Error(`Tool "${toolData.id}" is already registered.`); + } + + this._tools.set(toolData.id, { data: toolData }); + this._onDidChangeToolsScheduler.schedule(); + + toolData.when?.keys().forEach(key => this._toolContextKeys.add(key)); + + return toDisposable(() => { + this._tools.delete(toolData.id); + this._refreshAllToolContextKeys(); + this._onDidChangeToolsScheduler.schedule(); + }); + } + + private _refreshAllToolContextKeys() { + this._toolContextKeys.clear(); + for (const tool of this._tools.values()) { + tool.data.when?.keys().forEach(key => this._toolContextKeys.add(key)); + } + } + + registerToolImplementation(name: string, tool: IToolImpl): IDisposable { + const entry = this._tools.get(name); + if (!entry) { + throw new Error(`Tool "${name}" was not contributed.`); + } + + if (entry.impl) { + throw new Error(`Tool "${name}" already has an implementation.`); + } + + entry.impl = tool; + return toDisposable(() => { + entry.impl = undefined; + }); + } + + getTools(): Iterable> { + const toolDatas = Iterable.map(this._tools.values(), i => i.data); + return Iterable.filter(toolDatas, toolData => !toolData.when || this._contextKeyService.contextMatchesRules(toolData.when)); + } + + getTool(id: string): IToolData | undefined { + return this._getToolEntry(id)?.data; + } + + private _getToolEntry(id: string): IToolEntry | undefined { + const entry = this._tools.get(id); + if (entry && (!entry.data.when || this._contextKeyService.contextMatchesRules(entry.data.when))) { + return entry; + } else { + return undefined; + } + } + + getToolByName(name: string): IToolData | undefined { + for (const toolData of this.getTools()) { + if (toolData.name === name) { + return toolData; + } + } + return undefined; + } + + async invokeTool(dto: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise { + // When invoking a tool, don't validate the "when" clause. An extension may have invoked a tool just as it was becoming disabled, and just let it go through rather than throw and break the chat. + let tool = this._tools.get(dto.toolId); + if (!tool) { + throw new Error(`Tool ${dto.toolId} was not contributed`); + } + + if (!tool.impl) { + await this._extensionService.activateByEvent(`onLanguageModelTool:${dto.toolId}`); + + // Extension should activate and register the tool implementation + tool = this._tools.get(dto.toolId); + if (!tool?.impl) { + throw new Error(`Tool ${dto.toolId} does not have an implementation registered.`); + } + } + + return tool.impl.invoke(dto, countTokens, token); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/languageModels.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/languageModels.ts index 1276da1b..c63b1633 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -13,7 +13,6 @@ import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgress } from 'vs/platform/progress/common/progress'; import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -23,14 +22,43 @@ export const enum ChatMessageRole { Assistant, } +export interface IChatMessageTextPart { + type: 'text'; + value: string; +} + +export interface IChatMessageToolResultPart { + type: 'tool_result'; + toolCallId: string; + value: any; + isError?: boolean; +} + +export type IChatMessagePart = IChatMessageTextPart | IChatMessageToolResultPart | IChatResponseToolUsePart; + export interface IChatMessage { + readonly name?: string | undefined; readonly role: ChatMessageRole; - readonly content: string; + readonly content: IChatMessagePart[]; +} + +export interface IChatResponseTextPart { + type: 'text'; + value: string; +} + +export interface IChatResponseToolUsePart { + type: 'tool_use'; + name: string; + toolCallId: string; + parameters: any; } +export type IChatResponsePart = IChatResponseTextPart | IChatResponseToolUsePart; + export interface IChatResponseFragment { index: number; - part: string; + part: IChatResponsePart; } export interface ILanguageModelChatMetadata { @@ -51,9 +79,14 @@ export interface ILanguageModelChatMetadata { }; } +export interface ILanguageModelChatResponse { + stream: AsyncIterable; + result: Promise; +} + export interface ILanguageModelChat { metadata: ILanguageModelChatMetadata; - provideChatResponse(messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + sendChatRequest(messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: any }, token: CancellationToken): Promise; provideTokenCount(message: string | IChatMessage, token: CancellationToken): Promise; } @@ -91,7 +124,7 @@ export interface ILanguageModelsService { registerLanguageModelChat(identifier: string, provider: ILanguageModelChat): IDisposable; - makeLanguageModelChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + sendChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; computeTokenLength(identifier: string, message: string | IChatMessage, token: CancellationToken): Promise; } @@ -250,12 +283,12 @@ export class LanguageModelsService implements ILanguageModelsService { }); } - makeLanguageModelChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { + async sendChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { const provider = this._providers.get(identifier); if (!provider) { throw new Error(`Chat response provider with identifier ${identifier} is not registered.`); } - return provider.provideChatResponse(messages, from, options, progress, token); + return provider.sendChatRequest(messages, from, options, token); } computeTokenLength(identifier: string, message: string | IChatMessage, token: CancellationToken): Promise { diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts new file mode 100644 index 00000000..03c45c65 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { DisposableMap } from 'vs/base/common/lifecycle'; +import { joinPath } from 'vs/base/common/resources'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { localize } from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ILanguageModelToolsService, IToolData } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; +import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +interface IRawToolContribution { + id: string; + name?: string; + icon?: string | { light: string; dark: string }; + when?: string; + displayName?: string; + userDescription?: string; + modelDescription: string; + parametersSchema?: IJSONSchema; + canBeInvokedManually?: boolean; +} + +const languageModelToolsExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'languageModelTools', + activationEventsGenerator: (contributions: IRawToolContribution[], result) => { + for (const contrib of contributions) { + result.push(`onLanguageModelTool:${contrib.id}`); + } + }, + jsonSchema: { + description: localize('vscode.extension.contributes.tools', 'Contributes a tool that can be invoked by a language model.'), + type: 'array', + items: { + additionalProperties: false, + type: 'object', + defaultSnippets: [{ body: { name: '', description: '' } }], + required: ['id', 'modelDescription'], + properties: { + id: { + description: localize('toolId', "A unique id for this tool."), + type: 'string', + // Borrow OpenAI's requirement for tool names + pattern: '^[\\w-]+$' + }, + name: { + description: localize('toolName', "If {0} is enabled for this tool, the user may use '#' with this name to invoke the tool in a query. Otherwise, the name is not required. Name must not contain whitespace.", '`canBeInvokedManually`'), + type: 'string', + pattern: '^[\\w-]+$' + }, + displayName: { + description: localize('toolDisplayName', "A human-readable name for this tool that may be used to describe it in the UI."), + type: 'string' + }, + userDescription: { + description: localize('toolUserDescription', "A description of this tool that may be shown to the user."), + type: 'string' + }, + modelDescription: { + description: localize('toolModelDescription', "A description of this tool that may be passed to a language model."), + type: 'string' + }, + parametersSchema: { + description: localize('parametersSchema', "A JSON schema for the parameters this tool accepts."), + type: 'object', + $ref: 'http://json-schema.org/draft-07/schema#' + }, + canBeInvokedManually: { + description: localize('canBeInvokedManually', "Whether this tool can be invoked manually by the user through the chat UX."), + type: 'boolean' + }, + icon: { + description: localize('icon', "An icon that represents this tool. Either a file path, an object with file paths for dark and light themes, or a theme icon reference, like `\\$(zap)`"), + anyOf: [{ + type: 'string' + }, + { + type: 'object', + properties: { + light: { + description: localize('icon.light', 'Icon path when a light theme is used'), + type: 'string' + }, + dark: { + description: localize('icon.dark', 'Icon path when a dark theme is used'), + type: 'string' + } + } + }] + }, + when: { + markdownDescription: localize('condition', "Condition which must be true for this tool to be enabled. Note that a tool may still be invoked by another extension even when its `when` condition is false."), + type: 'string' + } + } + } + } +}); + +function toToolKey(extensionIdentifier: ExtensionIdentifier, toolName: string) { + return `${extensionIdentifier.value}/${toolName}`; +} + +export class LanguageModelToolsExtensionPointHandler implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.toolsExtensionPointHandler'; + + private _registrationDisposables = new DisposableMap(); + + constructor( + @ILanguageModelToolsService languageModelToolsService: ILanguageModelToolsService, + @ILogService logService: ILogService, + ) { + languageModelToolsExtensionPoint.setHandler((extensions, delta) => { + for (const extension of delta.added) { + for (const rawTool of extension.value) { + if (!rawTool.id || !rawTool.modelDescription) { + logService.error(`Extension '${extension.description.identifier.value}' CANNOT register tool without name and modelDescription: ${JSON.stringify(rawTool)}`); + continue; + } + + if (!rawTool.id.match(/^[\w-]+$/)) { + logService.error(`Extension '${extension.description.identifier.value}' CANNOT register tool with invalid id: ${rawTool.id}. The id must match /^[\\w-]+$/.`); + continue; + } + + if (rawTool.canBeInvokedManually && !rawTool.name) { + logService.error(`Extension '${extension.description.identifier.value}' CANNOT register tool with 'canBeInvokedManually' set without a name: ${JSON.stringify(rawTool)}`); + continue; + } + + const rawIcon = rawTool.icon; + let icon: IToolData['icon'] | undefined; + if (typeof rawIcon === 'string') { + icon = ThemeIcon.fromString(rawIcon) ?? { + dark: joinPath(extension.description.extensionLocation, rawIcon), + light: joinPath(extension.description.extensionLocation, rawIcon) + }; + } else if (rawIcon) { + icon = { + dark: joinPath(extension.description.extensionLocation, rawIcon.dark), + light: joinPath(extension.description.extensionLocation, rawIcon.light) + }; + } + + const tool: IToolData = { + ...rawTool, + icon, + when: rawTool.when ? ContextKeyExpr.deserialize(rawTool.when) : undefined, + }; + const disposable = languageModelToolsService.registerToolData(tool); + this._registrationDisposables.set(toToolKey(extension.description.identifier, rawTool.id), disposable); + } + } + + for (const extension of delta.removed) { + for (const tool of extension.value) { + this._registrationDisposables.deleteAndDispose(toToolKey(extension.description.identifier, tool.id)); + } + } + }); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/common/voiceChatService.ts b/patched-vscode/src/vs/workbench/contrib/chat/common/voiceChatService.ts index c162f585..ae70f2f5 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/common/voiceChatService.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/common/voiceChatService.ts @@ -70,13 +70,13 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly COMMAND_PREFIX = chatSubcommandLeader; private static readonly PHRASES_LOWER = { - [VoiceChatService.AGENT_PREFIX]: 'at', - [VoiceChatService.COMMAND_PREFIX]: 'slash' + [this.AGENT_PREFIX]: 'at', + [this.COMMAND_PREFIX]: 'slash' }; private static readonly PHRASES_UPPER = { - [VoiceChatService.AGENT_PREFIX]: 'At', - [VoiceChatService.COMMAND_PREFIX]: 'Slash' + [this.AGENT_PREFIX]: 'At', + [this.COMMAND_PREFIX]: 'Slash' }; private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/patched-vscode/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 1c0cdf3f..b0455f49 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -43,7 +43,7 @@ import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IVoiceChatService, VoiceChatInProgress as GlobalVoiceChatInProgress } from 'vs/workbench/contrib/chat/common/voiceChatService'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, MENU_INLINE_CHAT_WIDGET_SECONDARY } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextInProgress, SpeechToTextStatus, TextToSpeechStatus, TextToSpeechInProgress as GlobalTextToSpeechInProgress } from 'vs/workbench/contrib/speech/common/speechService'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -388,11 +388,8 @@ class VoiceChatSessions { if (!response) { return; } - - if ( - !this.accessibilityService.isScreenReaderOptimized() && // do not auto synthesize when screen reader is active - this.configurationService.getValue(AccessibilityVoiceSettingId.AutoSynthesize) === true - ) { + const autoSynthesize = this.configurationService.getValue<'on' | 'off' | 'auto'>(AccessibilityVoiceSettingId.AutoSynthesize); + if (autoSynthesize === 'on' || autoSynthesize === 'auto' && !this.accessibilityService.isScreenReaderOptimized()) { let context: IVoiceChatSessionController | 'focused'; if (controller.context === 'inline') { // TODO@bpasero this is ugly, but the lightweight inline chat turns into @@ -805,7 +802,7 @@ class ChatSynthesizerSessions { let totalOffset = 0; let complete = false; do { - const responseLength = response.response.asString().length; + const responseLength = response.response.toString().length; const { chunk, offset } = this.parseNextChatResponseChunk(response, totalOffset); totalOffset = offset; complete = response.isComplete; @@ -818,7 +815,7 @@ class ChatSynthesizerSessions { return; } - if (!complete && responseLength === response.response.asString().length) { + if (!complete && responseLength === response.response.toString().length) { await raceCancellation(Event.toPromise(response.onDidChange), token); // wait for the response to change } } while (!token.isCancellationRequested && !complete); @@ -827,7 +824,7 @@ class ChatSynthesizerSessions { private parseNextChatResponseChunk(response: IChatResponseModel, offset: number): { readonly chunk: string | undefined; readonly offset: number } { let chunk: string | undefined = undefined; - const text = response.response.asString(); + const text = response.response.toString(); if (response.isComplete) { chunk = text.substring(offset); @@ -880,7 +877,7 @@ export class ReadChatResponseAloud extends Action2 { title: localize2('workbench.action.chat.readChatResponseAloud', "Read Aloud"), icon: Codicon.unmute, precondition: CanVoiceChat, - menu: { + menu: [{ id: MenuId.ChatMessageTitle, when: ContextKeyExpr.and( CanVoiceChat, @@ -889,7 +886,16 @@ export class ReadChatResponseAloud extends Action2 { CONTEXT_RESPONSE_FILTERED.negate() // and not when response is filtered ), group: 'navigation' - } + }, { + id: MENU_INLINE_CHAT_WIDGET_SECONDARY, + when: ContextKeyExpr.and( + CanVoiceChat, + CONTEXT_RESPONSE, // only for responses + ScopedChatSynthesisInProgress.negate(), // but not when already in progress + CONTEXT_RESPONSE_FILTERED.negate() // and not when response is filtered + ), + group: 'navigation' + }] }); } @@ -968,6 +974,15 @@ export class StopReadChatItemAloud extends Action2 { CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered ), group: 'navigation' + }, + { + id: MENU_INLINE_CHAT_WIDGET_SECONDARY, + when: ContextKeyExpr.and( + ScopedChatSynthesisInProgress, // only when in progress + CONTEXT_RESPONSE, // only for responses + CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered + ), + group: 'navigation' } ] }); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML.0.snap index 0d3458d7..9bfd3b94 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML.0.snap @@ -1 +1 @@ -
    1<canvas>2<details>3</details></canvas>4
    \ No newline at end of file +

    1<canvas>2</canvas>

    <details>3</details>4

    \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML_with_attributes.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML_with_attributes.0.snap index 3bb96899..c0b5a277 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML_with_attributes.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_invalid_HTML_with_attributes.0.snap @@ -1 +1 @@ -
    1<details id="id1" style="display: none">2<details id="my id 2">3</details></details>4
    \ No newline at end of file +

    1

    <details id="id1" style="display: none">2<details id="my id 2">3</details></details>4

    \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_remote_images.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_remote_images.0.snap index 34a719b0..1241ef62 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_remote_images.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_remote_images.0.snap @@ -1 +1 @@ -
    <img src="http://disallowed.com/image.jpg">
    \ No newline at end of file +

    <img src="http://disallowed.com/image.jpg">

    \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap index 023b2e6a..5b482726 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_self-closing_elements.0.snap @@ -1 +1 @@ -
    <area>

    <input type="text" value="test">
    \ No newline at end of file +

    <area>



    <input type="text" value="test">

    \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.0.snap new file mode 100644 index 00000000..89991e76 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.0.snap @@ -0,0 +1 @@ +

    hello

    \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.1.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.1.snap new file mode 100644 index 00000000..b9ca8267 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/__snapshots__/ChatMarkdownRenderer_supportHtml_with_one-line_markdown.1.snap @@ -0,0 +1,4 @@ +
      +
    1. hello test text
    2. +
    +
    \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts index e9975f25..657ed1c9 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts @@ -27,6 +27,18 @@ suite('ChatMarkdownRenderer', () => { await assertSnapshot(result.element.textContent); }); + test('supportHtml with one-line markdown', async () => { + const md = new MarkdownString('**hello**'); + md.supportHtml = true; + const result = store.add(testRenderer.render(md)); + await assertSnapshot(result.element.outerHTML); + + const md2 = new MarkdownString('1. [_hello_](https://example.com) test **text**'); + md2.supportHtml = true; + const result2 = store.add(testRenderer.render(md2)); + await assertSnapshot(result2.element.outerHTML); + }); + test('invalid HTML', async () => { const md = new MarkdownString('12
    3
    4'); md.supportHtml = true; diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index dcbe7a17..2bc3618a 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -16,8 +16,10 @@ import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/c import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import { MockChatWidgetService } from 'vs/workbench/contrib/chat/test/browser/mockChatWidget'; import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; +import { MockLanguageModelToolsService } from 'vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestViewsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -29,7 +31,7 @@ suite('ChatVariables', function () { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); setup(function () { - service = new ChatVariablesService(new MockChatWidgetService(), new TestViewsService()); + service = new ChatVariablesService(new MockChatWidgetService(), new TestViewsService(), new MockLanguageModelToolsService()); instantiationService = testDisposables.add(new TestInstantiationService()); instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); @@ -37,6 +39,7 @@ suite('ChatVariables', function () { instantiationService.stub(IChatVariablesService, service); instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(ILanguageModelToolsService, new MockLanguageModelToolsService()); instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService)); }); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts new file mode 100644 index 00000000..eecd3d05 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/browser/languageModelToolsService.test.ts @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { ContextKeyEqualsExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IToolData, IToolImpl, IToolInvocation, LanguageModelToolsService } from '../../common/languageModelToolsService'; + +suite('LanguageModelToolsService', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + let contextKeyService: IContextKeyService; + let service: LanguageModelToolsService; + + setup(() => { + const extensionService = new TestExtensionService(); + contextKeyService = store.add(new ContextKeyService(new TestConfigurationService())); + service = store.add(new LanguageModelToolsService(extensionService, contextKeyService)); + }); + + test('registerToolData', () => { + const toolData: IToolData = { + id: 'testTool', + modelDescription: 'Test Tool' + }; + + const disposable = service.registerToolData(toolData); + assert.strictEqual(service.getTool('testTool')?.id, 'testTool'); + disposable.dispose(); + assert.strictEqual(service.getTool('testTool'), undefined); + }); + + test('registerToolImplementation', () => { + const toolData: IToolData = { + id: 'testTool', + modelDescription: 'Test Tool' + }; + + store.add(service.registerToolData(toolData)); + + const toolImpl: IToolImpl = { + invoke: async () => ({ string: 'result' }) + }; + + store.add(service.registerToolImplementation('testTool', toolImpl)); + assert.strictEqual(service.getTool('testTool')?.id, 'testTool'); + }); + + test('getTools', () => { + contextKeyService.createKey('testKey', true); + const toolData1: IToolData = { + id: 'testTool1', + modelDescription: 'Test Tool 1', + when: ContextKeyEqualsExpr.create('testKey', false) + }; + + const toolData2: IToolData = { + id: 'testTool2', + modelDescription: 'Test Tool 2', + when: ContextKeyEqualsExpr.create('testKey', true) + }; + + const toolData3: IToolData = { + id: 'testTool3', + modelDescription: 'Test Tool 3' + }; + + store.add(service.registerToolData(toolData1)); + store.add(service.registerToolData(toolData2)); + store.add(service.registerToolData(toolData3)); + + const tools = Array.from(service.getTools()); + assert.strictEqual(tools.length, 2); + assert.strictEqual(tools[0].id, 'testTool2'); + assert.strictEqual(tools[1].id, 'testTool3'); + }); + + test('invokeTool', async () => { + const toolData: IToolData = { + id: 'testTool', + modelDescription: 'Test Tool' + }; + + store.add(service.registerToolData(toolData)); + + const toolImpl: IToolImpl = { + invoke: async (invocation) => { + assert.strictEqual(invocation.callId, '1'); + assert.strictEqual(invocation.toolId, 'testTool'); + assert.deepStrictEqual(invocation.parameters, { a: 1 }); + return { string: 'result' }; + } + }; + + store.add(service.registerToolImplementation('testTool', toolImpl)); + + const dto: IToolInvocation = { + callId: '1', + toolId: 'testTool', + tokenBudget: 100, + parameters: { + a: 1 + } + }; + + const result = await service.invokeTool(dto, async () => 0, CancellationToken.None); + assert.strictEqual(result.string, 'result'); + }); +}); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap index bffaba0f..a71a6928 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap @@ -42,7 +42,8 @@ name: "subCommand", description: "" } - ] + ], + disambiguation: [ ] }, kind: "agent" }, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap index b2392476..4ca12379 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap @@ -42,7 +42,8 @@ name: "subCommand", description: "" } - ] + ], + disambiguation: [ ] }, kind: "agent" }, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap index 1965499f..ca95c7e1 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap @@ -28,7 +28,8 @@ name: "subCommand", description: "" } - ] + ], + disambiguation: [ ] }, kind: "agent" }, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap index 4ec681f8..c062f9c1 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap @@ -28,7 +28,8 @@ name: "subCommand", description: "" } - ] + ], + disambiguation: [ ] }, kind: "agent" }, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap index fde8b695..22098b4e 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap @@ -28,7 +28,8 @@ name: "subCommand", description: "" } - ] + ], + disambiguation: [ ] }, kind: "agent" }, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap index 0a210c51..c2d61319 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap @@ -28,7 +28,8 @@ name: "subCommand", description: "" } - ] + ], + disambiguation: [ ] }, kind: "agent" }, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap index 87455850..2362eb81 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap @@ -28,7 +28,8 @@ name: "subCommand", description: "" } - ] + ], + disambiguation: [ ] }, kind: "agent" }, diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap index 38328830..eef26b50 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap @@ -33,7 +33,8 @@ extensionDisplayName: "", locations: [ "panel" ], metadata: { }, - slashCommands: [ ] + slashCommands: [ ], + disambiguation: [ ] }, kind: "agent" }, @@ -59,6 +60,7 @@ followups: undefined, isCanceled: false, vote: undefined, + voteDownReason: undefined, agent: { name: "ChatProviderWithUsedContext", id: "ChatProviderWithUsedContext", @@ -71,21 +73,14 @@ extensionDisplayName: "", locations: [ "panel" ], metadata: { }, - slashCommands: [ ] + slashCommands: [ ], + disambiguation: [ ] }, slashCommand: undefined, usedContext: { documents: [ { - uri: { - scheme: "file", - authority: "", - path: "/test/path/to/file", - query: "", - fragment: "", - _formatted: null, - _fsPath: null - }, + uri: URI(file:///test/path/to/file), version: 3, ranges: [ { @@ -99,7 +94,8 @@ ], kind: "usedContext" }, - contentReferences: [ ] + contentReferences: [ ], + codeCitations: [ ] } ] } \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap index cf9cc42d..f739ca6f 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap @@ -32,7 +32,8 @@ extensionDisplayName: "", locations: [ "panel" ], metadata: { requester: { name: "test" } }, - slashCommands: [ ] + slashCommands: [ ], + disambiguation: [ ] }, kind: "agent" }, @@ -56,9 +57,17 @@ variableData: { variables: [ ] }, response: [ ], result: { metadata: { metadataKey: "value" } }, - followups: undefined, + followups: [ + { + kind: "reply", + message: "Something else", + agentId: "", + tooltip: "a tooltip" + } + ], isCanceled: false, vote: undefined, + voteDownReason: undefined, agent: { name: "ChatProviderWithUsedContext", id: "ChatProviderWithUsedContext", @@ -71,21 +80,14 @@ extensionDisplayName: "", locations: [ "panel" ], metadata: { requester: { name: "test" } }, - slashCommands: [ ] + slashCommands: [ ], + disambiguation: [ ] }, slashCommand: undefined, usedContext: { documents: [ { - uri: { - scheme: "file", - authority: "", - path: "/test/path/to/file", - query: "", - fragment: "", - _formatted: null, - _fsPath: null - }, + uri: URI(file:///test/path/to/file), version: 3, ranges: [ { @@ -99,7 +101,56 @@ ], kind: "usedContext" }, - contentReferences: [ ] + contentReferences: [ ], + codeCitations: [ ] + }, + { + message: { + parts: [ + { + range: { + start: 0, + endExclusive: 14 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 15 + }, + text: "test request 2", + kind: "text" + } + ], + text: "test request 2" + }, + variableData: { variables: [ ] }, + response: [ ], + result: { }, + followups: [ ], + isCanceled: false, + vote: undefined, + voteDownReason: undefined, + agent: { + name: "testAgent", + id: "testAgent", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionPublisherId: "", + publisherDisplayName: "", + extensionDisplayName: "", + locations: [ "panel" ], + metadata: { requester: { name: "test" } }, + slashCommands: [ ], + disambiguation: [ ], + isDefault: true + }, + slashCommand: undefined, + usedContext: undefined, + contentReferences: [ ], + codeCitations: [ ] } ] } \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap index a749780f..357a8c56 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap @@ -32,7 +32,8 @@ extensionDisplayName: "", locations: [ "panel" ], metadata: { }, - slashCommands: [ ] + slashCommands: [ ], + disambiguation: [ ] }, kind: "agent" }, @@ -59,6 +60,7 @@ followups: undefined, isCanceled: false, vote: undefined, + voteDownReason: undefined, agent: { name: "ChatProviderWithUsedContext", id: "ChatProviderWithUsedContext", @@ -71,11 +73,13 @@ extensionDisplayName: "", locations: [ "panel" ], metadata: { }, - slashCommands: [ ] + slashCommands: [ ], + disambiguation: [ ] }, slashCommand: undefined, usedContext: undefined, - contentReferences: [ ] + contentReferences: [ ], + codeCitations: [ ] } ] } \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap index 5847d813..b26d8433 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/__snapshots__/Response_inline_reference.0.snap @@ -9,15 +9,7 @@ kind: "markdownContent" }, { - inlineReference: { - scheme: "https", - authority: "microsoft.com", - path: "/", - query: "", - fragment: "", - _formatted: null, - _fsPath: null - }, + inlineReference: URI(https://microsoft.com/), kind: "inlineReference" }, { diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatAgents.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatAgents.test.ts index 21bd0b39..07684e0c 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatAgents.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatAgents.test.ts @@ -8,7 +8,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ChatAgentService, IChatAgentData, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import * as assert from 'assert'; +import assert from 'assert'; const testAgentId = 'testAgent'; const testAgentData: IChatAgentData = { @@ -20,6 +20,7 @@ const testAgentData: IChatAgentData = { locations: [], metadata: {}, slashCommands: [], + disambiguation: [], }; suite('ChatAgents', function () { diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index b9f38a04..c37b05e8 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { URI } from 'vs/base/common/uri'; @@ -17,7 +17,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModel, Response } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ISerializableChatData1, ISerializableChatData2, ISerializableChatData3, normalizeSerializableChatData, Response } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -149,24 +149,24 @@ suite('ChatModel', () => { model2.acceptResponseProgress(request1, { content: new MarkdownString('Hello'), kind: 'markdownContent' }); - assert.strictEqual(request1.response.response.asString(), 'Hello'); + assert.strictEqual(request1.response.response.toString(), 'Hello'); }); }); suite('Response', () => { - ensureNoDisposablesAreLeakedInTestSuite(); + const store = ensureNoDisposablesAreLeakedInTestSuite(); test('mergeable markdown', async () => { - const response = new Response([]); + const response = store.add(new Response([])); response.updateContent({ content: new MarkdownString('markdown1'), kind: 'markdownContent' }); response.updateContent({ content: new MarkdownString('markdown2'), kind: 'markdownContent' }); await assertSnapshot(response.value); - assert.strictEqual(response.asString(), 'markdown1markdown2'); + assert.strictEqual(response.toString(), 'markdown1markdown2'); }); test('not mergeable markdown', async () => { - const response = new Response([]); + const response = store.add(new Response([])); const md1 = new MarkdownString('markdown1'); md1.supportHtml = true; response.updateContent({ content: md1, kind: 'markdownContent' }); @@ -175,10 +175,108 @@ suite('Response', () => { }); test('inline reference', async () => { - const response = new Response([]); + const response = store.add(new Response([])); response.updateContent({ content: new MarkdownString('text before'), kind: 'markdownContent' }); response.updateContent({ inlineReference: URI.parse('https://microsoft.com'), kind: 'inlineReference' }); response.updateContent({ content: new MarkdownString('text after'), kind: 'markdownContent' }); await assertSnapshot(response.value); }); }); + +suite('normalizeSerializableChatData', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('v1', () => { + const v1Data: ISerializableChatData1 = { + creationDate: Date.now(), + initialLocation: undefined, + isImported: false, + requesterAvatarIconUri: undefined, + requesterUsername: 'me', + requests: [], + responderAvatarIconUri: undefined, + responderUsername: 'bot', + sessionId: 'session1', + welcomeMessage: [] + }; + + const newData = normalizeSerializableChatData(v1Data); + assert.strictEqual(newData.creationDate, v1Data.creationDate); + assert.strictEqual(newData.lastMessageDate, v1Data.creationDate); + assert.strictEqual(newData.version, 3); + assert.ok('customTitle' in newData); + }); + + test('v2', () => { + const v2Data: ISerializableChatData2 = { + version: 2, + creationDate: 100, + lastMessageDate: Date.now(), + initialLocation: undefined, + isImported: false, + requesterAvatarIconUri: undefined, + requesterUsername: 'me', + requests: [], + responderAvatarIconUri: undefined, + responderUsername: 'bot', + sessionId: 'session1', + welcomeMessage: [], + computedTitle: 'computed title' + }; + + const newData = normalizeSerializableChatData(v2Data); + assert.strictEqual(newData.version, 3); + assert.strictEqual(newData.creationDate, v2Data.creationDate); + assert.strictEqual(newData.lastMessageDate, v2Data.lastMessageDate); + assert.strictEqual(newData.customTitle, v2Data.computedTitle); + }); + + test('old bad data', () => { + const v1Data: ISerializableChatData1 = { + // Testing the scenario where these are missing + sessionId: undefined!, + creationDate: undefined!, + + initialLocation: undefined, + isImported: false, + requesterAvatarIconUri: undefined, + requesterUsername: 'me', + requests: [], + responderAvatarIconUri: undefined, + responderUsername: 'bot', + welcomeMessage: [] + }; + + const newData = normalizeSerializableChatData(v1Data); + assert.strictEqual(newData.version, 3); + assert.ok(newData.creationDate > 0); + assert.ok(newData.lastMessageDate > 0); + assert.ok(newData.sessionId); + }); + + test('v3 with bug', () => { + const v3Data: ISerializableChatData3 = { + // Test case where old data was wrongly normalized and these fields were missing + creationDate: undefined!, + lastMessageDate: undefined!, + + version: 3, + initialLocation: undefined, + isImported: false, + requesterAvatarIconUri: undefined, + requesterUsername: 'me', + requests: [], + responderAvatarIconUri: undefined, + responderUsername: 'bot', + sessionId: 'session1', + welcomeMessage: [], + customTitle: 'computed title' + }; + + const newData = normalizeSerializableChatData(v3Data); + assert.strictEqual(newData.version, 3); + assert.ok(newData.creationDate > 0); + assert.ok(newData.lastMessageDate > 0); + assert.ok(newData.sessionId); + }); +}); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index c0b7a954..7bcd890d 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -16,7 +16,9 @@ import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestP import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; +import { MockLanguageModelToolsService } from 'vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService'; import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -34,6 +36,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(ILanguageModelToolsService, new MockLanguageModelToolsService()); instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService)); varService = mockObject()({}); @@ -117,7 +120,7 @@ suite('ChatRequestParser', () => { }); const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => { - return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands } satisfies IChatAgentData; + return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands, disambiguation: [], } satisfies IChatAgentData; }; test('agent with subcommand after text', async () => { diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 1e9ad196..7701d6b7 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -3,12 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { assertSnapshot } from 'vs/base/test/common/snapshot'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -26,6 +28,8 @@ import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/ import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/mockChatVariables'; +import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; +import { NullWorkbenchAssignmentService } from 'vs/workbench/services/assignment/test/common/nullAssignmentService'; import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -41,6 +45,7 @@ const chatAgentWithUsedContext: IChatAgent = { locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [], + disambiguation: [], async invoke(request, progress, history, token) { progress({ documents: [ @@ -62,6 +67,21 @@ const chatAgentWithUsedContext: IChatAgent = { }, }; +function getAgentData(id: string) { + return { + name: id, + id: id, + extensionId: nullExtensionDescription.identifier, + extensionPublisherId: '', + publisherDisplayName: '', + extensionDisplayName: '', + locations: [ChatAgentLocation.Panel], + metadata: {}, + slashCommands: [], + disambiguation: [], + }; +} + suite('ChatService', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -73,6 +93,7 @@ suite('ChatService', () => { setup(async () => { instantiationService = testDisposables.add(new TestInstantiationService(new ServiceCollection( [IChatVariablesService, new MockChatVariablesService()], + [IWorkbenchAssignmentService, new NullWorkbenchAssignmentService()] ))); instantiationService.stub(IStorageService, storageService = testDisposables.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); @@ -82,18 +103,19 @@ suite('ChatService', () => { instantiationService.stub(IViewsService, new TestExtensionService()); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); + instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IChatService, new MockChatService()); chatAgentService = instantiationService.createInstance(ChatAgentService); instantiationService.stub(IChatAgentService, chatAgentService); - const agent = { + const agent: IChatAgentImplementation = { async invoke(request, progress, history, token) { return {}; }, - } satisfies IChatAgentImplementation; - testDisposables.add(chatAgentService.registerAgent('testAgent', { name: 'testAgent', id: 'testAgent', isDefault: true, extensionId: nullExtensionDescription.identifier, extensionPublisherId: '', publisherDisplayName: '', extensionDisplayName: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [] })); - testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContextId, { name: chatAgentWithUsedContextId, id: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, extensionPublisherId: '', publisherDisplayName: '', extensionDisplayName: '', locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [] })); + }; + testDisposables.add(chatAgentService.registerAgent('testAgent', { ...getAgentData('testAgent'), isDefault: true })); + testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContextId, getAgentData(chatAgentWithUsedContextId))); testDisposables.add(chatAgentService.registerAgentImplementation('testAgent', agent)); chatAgentService.updateAgent('testAgent', { requester: { name: 'test' } }); }); @@ -127,7 +149,7 @@ suite('ChatService', () => { await testService.addCompleteRequest(model.sessionId, 'test request', undefined, 0, { message: 'test response' }); assert.strictEqual(model.getRequests().length, 1); assert.ok(model.getRequests()[0].response); - assert.strictEqual(model.getRequests()[0].response?.response.asString(), 'test response'); + assert.strictEqual(model.getRequests()[0].response?.response.toString(), 'test response'); }); test('sendRequest fails', async () => { @@ -141,6 +163,45 @@ suite('ChatService', () => { await assertSnapshot(model.toExport()); }); + test('history', async () => { + const historyLengthAgent: IChatAgentImplementation = { + async invoke(request, progress, history, token) { + return { + metadata: { historyLength: history.length } + }; + }, + }; + + testDisposables.add(chatAgentService.registerAgent('defaultAgent', { ...getAgentData('defaultAgent'), isDefault: true })); + testDisposables.add(chatAgentService.registerAgent('agent2', getAgentData('agent2'))); + testDisposables.add(chatAgentService.registerAgentImplementation('defaultAgent', historyLengthAgent)); + testDisposables.add(chatAgentService.registerAgentImplementation('agent2', historyLengthAgent)); + + const testService = testDisposables.add(instantiationService.createInstance(ChatService)); + const model = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + + // Send a request to default agent + const response = await testService.sendRequest(model.sessionId, `test request`, { agentId: 'defaultAgent' }); + assert(response); + await response.responseCompletePromise; + assert.strictEqual(model.getRequests().length, 1); + assert.strictEqual(model.getRequests()[0].response?.result?.metadata?.historyLength, 0); + + // Send a request to agent2- it can't see the default agent's message + const response2 = await testService.sendRequest(model.sessionId, `test request`, { agentId: 'agent2' }); + assert(response2); + await response2.responseCompletePromise; + assert.strictEqual(model.getRequests().length, 2); + assert.strictEqual(model.getRequests()[1].response?.result?.metadata?.historyLength, 0); + + // Send a request to defaultAgent - the default agent can see agent2's message + const response3 = await testService.sendRequest(model.sessionId, `test request`, { agentId: 'defaultAgent' }); + assert(response3); + await response3.responseCompletePromise; + assert.strictEqual(model.getRequests().length, 3); + assert.strictEqual(model.getRequests()[2].response?.result?.metadata?.historyLength, 2); + }); + test('can serialize', async () => { testDisposables.add(chatAgentService.registerAgentImplementation(chatAgentWithUsedContextId, chatAgentWithUsedContext)); chatAgentService.updateAgent(chatAgentWithUsedContextId, { requester: { name: 'test' } }); @@ -154,9 +215,13 @@ suite('ChatService', () => { const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; - assert.strictEqual(model.getRequests().length, 1); + const response2 = await testService.sendRequest(model.sessionId, `test request 2`); + assert(response2); + await response2.responseCompletePromise; + assert.strictEqual(model.getRequests().length, 2); + await assertSnapshot(model.toExport()); }); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatWordCounter.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatWordCounter.test.ts index 314b4589..a013abde 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatWordCounter.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/chatWordCounter.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; @@ -13,31 +13,60 @@ suite('ChatWordCounter', () => { function doTest(str: string, nWords: number, resultStr: string) { const result = getNWords(str, nWords); assert.strictEqual(result.value, resultStr); - assert.strictEqual(result.actualWordCount, nWords); + assert.strictEqual(result.returnedWordCount, nWords); } - test('getNWords, matching actualWordCount', () => { - const cases: [string, number, string][] = [ - ['hello world', 1, 'hello'], - ['hello', 1, 'hello'], - ['hello world', 0, ''], - ['here\'s, some. punctuation?', 3, 'here\'s, some. punctuation?'], - ['| markdown | _table_ | header |', 3, '| markdown | _table_ | header'], - ['| --- | --- | --- |', 1, '| --- | --- | --- |'], - [' \t some \n whitespace \n\n\nhere ', 3, ' \t some \n whitespace \n\n\nhere'], - ]; - - cases.forEach(([str, nWords, result]) => doTest(str, nWords, result)); - }); + suite('getNWords', () => { + test('matching actualWordCount', () => { + const cases: [string, number, string][] = [ + ['hello world', 1, 'hello'], + ['hello', 1, 'hello'], + ['hello world', 0, ''], + ['here\'s, some. punctuation?', 3, 'here\'s, some. punctuation?'], + ['| markdown | _table_ | header |', 3, '| markdown | _table_ | header'], + ['| --- | --- | --- |', 1, '| ---'], + ['| --- | --- | --- |', 3, '| --- | --- | ---'], + [' \t some \n whitespace \n\n\nhere ', 3, ' \t some \n whitespace \n\n\nhere'], + ]; + + cases.forEach(([str, nWords, result]) => doTest(str, nWords, result)); + }); + + test('matching links', () => { + const cases: [string, number, string][] = [ + ['[hello](https://example.com) world', 1, '[hello](https://example.com)'], + ['[hello](https://example.com) world', 2, '[hello](https://example.com) world'], + ['oh [hello](https://example.com "title") world', 1, 'oh'], + ['oh [hello](https://example.com "title") world', 2, 'oh [hello](https://example.com "title")'], + // Parens in link destination + ['[hello](https://example.com?()) world', 1, '[hello](https://example.com?())'], + // Escaped brackets in link text + ['[he \\[l\\] \\]lo](https://example.com?()) world', 1, '[he \\[l\\] \\]lo](https://example.com?())'], + ]; + + cases.forEach(([str, nWords, result]) => doTest(str, nWords, result)); + }); - test('getNWords, matching links', () => { - const cases: [string, number, string][] = [ - ['[hello](https://example.com) world', 1, '[hello](https://example.com)'], - ['[hello](https://example.com) world', 2, '[hello](https://example.com) world'], - ['oh [hello](https://example.com "title") world', 1, 'oh'], - ['oh [hello](https://example.com "title") world', 2, 'oh [hello](https://example.com "title")'], - ]; + test('code', () => { + const cases: [string, number, string][] = [ + ['let a=1-2', 2, 'let a'], + ['let a=1-2', 3, 'let a='], + ['let a=1-2', 4, 'let a=1'], + ['const myVar = 1+2', 4, 'const myVar = 1'], + ['
    ', 3, '
    ', 4, '
    '], + ]; - cases.forEach(([str, nWords, result]) => doTest(str, nWords, result)); + cases.forEach(([str, nWords, result]) => doTest(str, nWords, result)); + }); + + test('chinese characters', () => { + const cases: [string, number, string][] = [ + ['我喜欢中国èœ', 3, '我喜欢'], + ]; + + cases.forEach(([str, nWords, result]) => doTest(str, nWords, result)); + }); }); + }); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts index 9bf3d639..4018b862 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/languageModels.test.ts @@ -3,12 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; +import { AsyncIterableSource, DeferredPromise, timeout } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; -import { languageModelExtensionPoint, LanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels'; +import { ChatMessageRole, IChatResponseFragment, languageModelExtensionPoint, LanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels'; import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -51,7 +53,7 @@ suite('LanguageModels', function () { maxInputTokens: 100, maxOutputTokens: 100, }, - provideChatResponse: async () => { + sendChatRequest: async () => { throw new Error(); }, provideTokenCount: async () => { @@ -70,7 +72,7 @@ suite('LanguageModels', function () { maxInputTokens: 100, maxOutputTokens: 100, }, - provideChatResponse: async () => { + sendChatRequest: async () => { throw new Error(); }, provideTokenCount: async () => { @@ -103,5 +105,56 @@ suite('LanguageModels', function () { assert.deepStrictEqual(result2.length, 0); }); + test('sendChatRequest returns a response-stream', async function () { + store.add(languageModels.registerLanguageModelChat('actual', { + metadata: { + extension: nullExtensionDescription.identifier, + name: 'Pretty Name', + vendor: 'test-vendor', + family: 'actual-family', + version: 'actual-version', + id: 'actual-lm', + maxInputTokens: 100, + maxOutputTokens: 100, + }, + sendChatRequest: async (messages, _from, _options, token) => { + // const message = messages.at(-1); + + const defer = new DeferredPromise(); + const stream = new AsyncIterableSource(); + + (async () => { + while (!token.isCancellationRequested) { + stream.emitOne({ index: 0, part: { type: 'text', value: Date.now().toString() } }); + await timeout(10); + } + defer.complete(undefined); + })(); + + return { + stream: stream.asyncIterable, + result: defer.p + }; + }, + provideTokenCount: async () => { + throw new Error(); + } + })); + + const models = await languageModels.selectLanguageModels({ identifier: 'actual-lm' }); + assert.ok(models.length === 1); + + const first = models[0]; + + const cts = new CancellationTokenSource(); + + const request = await languageModels.sendChatRequest(first, nullExtensionDescription.identifier, [{ role: ChatMessageRole.User, content: [{ type: 'text', value: 'hello' }] }], {}, cts.token); + + assert.ok(request); + + cts.dispose(true); + + await request.result; + }); }); diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index c7cc9199..d7529dec 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -80,4 +80,8 @@ export class MockChatService implements IChatService { transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { throw new Error('Method not implemented.'); } + + setChatSessionTitle(sessionId: string, title: string): void { + throw new Error('Method not implemented.'); + } } diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts new file mode 100644 index 00000000..52f91227 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolImpl, IToolInvocation, IToolResult } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; + +export class MockLanguageModelToolsService implements ILanguageModelToolsService { + _serviceBrand: undefined; + + constructor() { } + + onDidChangeTools: Event = Event.None; + + registerToolData(toolData: IToolData): IDisposable { + return Disposable.None; + } + + registerToolImplementation(name: string, tool: IToolImpl): IDisposable { + return Disposable.None; + } + + getTools(): Iterable> { + return []; + } + + getTool(id: string): IToolData | undefined { + return undefined; + } + + getToolByName(name: string): IToolData | undefined { + return undefined; + } + + async invokeTool(dto: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise { + return { + string: '' + }; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts index 2ff31b01..b97a0c0f 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; @@ -12,7 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentCompletionItem, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentCompletionItem, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService, IChatParticipantDetectionProvider } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChatService'; @@ -36,6 +36,19 @@ suite('VoiceChat', () => { constructor(readonly id: string, readonly slashCommands: IChatAgentCommand[]) { this.name = id; } + fullName?: string | undefined; + description?: string | undefined; + when?: string | undefined; + publisherDisplayName?: string | undefined; + isDefault?: boolean | undefined; + isDynamic?: boolean | undefined; + disambiguation: { categoryName: string; description: string; examples: string[] }[] = []; + provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { + throw new Error('Method not implemented.'); + } + provideSampleQuestions?(location: ChatAgentLocation, token: CancellationToken): ProviderResult { + throw new Error('Method not implemented.'); + } invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } provideWelcomeMessage?(location: ChatAgentLocation, token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); } metadata = {}; @@ -52,6 +65,15 @@ suite('VoiceChat', () => { ]; class TestChatAgentService implements IChatAgentService { + hasChatParticipantDetectionProviders(): boolean { + throw new Error('Method not implemented.'); + } + registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider): IDisposable { + throw new Error('Method not implemented.'); + } + detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined> { + throw new Error('Method not implemented.'); + } _serviceBrand: undefined; readonly onDidChangeAgents = Event.None; registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable { throw new Error(); } @@ -70,6 +92,8 @@ suite('VoiceChat', () => { getAgentByFullyQualifiedId(id: string): IChatAgentData | undefined { throw new Error('Method not implemented.'); } registerAgentCompletionProvider(id: string, provider: (query: string, token: CancellationToken) => Promise): IDisposable { throw new Error('Method not implemented.'); } getAgentCompletionItems(id: string, query: string, token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + agentHasDupeName(id: string): boolean { throw new Error('Method not implemented.'); } + getChatTitle(id: string, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } } class TestSpeechService implements ISpeechService { diff --git a/patched-vscode/src/vs/workbench/contrib/chat/test/electron-sandbox/voiceChatActions.test.ts b/patched-vscode/src/vs/workbench/contrib/chat/test/electron-sandbox/voiceChatActions.test.ts index 249fd8e4..85d4a9cd 100644 --- a/patched-vscode/src/vs/workbench/contrib/chat/test/electron-sandbox/voiceChatActions.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/chat/test/electron-sandbox/voiceChatActions.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { parseNextChatResponseChunk } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; diff --git a/patched-vscode/src/vs/workbench/contrib/codeActions/browser/codeActions.contribution.ts b/patched-vscode/src/vs/workbench/contrib/codeActions/browser/codeActions.contribution.ts index afcf9915..b8158d25 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeActions/browser/codeActions.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeActions/browser/codeActions.contribution.ts @@ -11,7 +11,7 @@ import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from ' import { DocumentationExtensionPoint, documentationExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/documentationExtensionPoint'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { CodeActionsContribution, editorConfiguration } from './codeActionsContribution'; +import { CodeActionsContribution, editorConfiguration, notebookEditorConfiguration } from './codeActionsContribution'; import { CodeActionDocumentationContribution } from './documentationContribution'; const codeActionsExtensionPoint = ExtensionsRegistry.registerExtensionPoint(codeActionsExtensionPointDescriptor); @@ -20,6 +20,9 @@ const documentationExtensionPoint = ExtensionsRegistry.registerExtensionPoint(Extensions.Configuration) .registerConfiguration(editorConfiguration); +Registry.as(Extensions.Configuration) + .registerConfiguration(notebookEditorConfiguration); + class WorkbenchConfigurationContribution { constructor( @IInstantiationService instantiationService: IInstantiationService, diff --git a/patched-vscode/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts b/patched-vscode/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts index 38fd5502..3f075cb5 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts @@ -8,6 +8,7 @@ import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { codeActionCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/common/types'; import * as nls from 'vs/nls'; @@ -34,15 +35,26 @@ const createCodeActionsAutoSave = (description: string): IJSONSchema => { }; }; -const codeActionsOnSaveDefaultProperties = Object.freeze({ - 'source.fixAll': createCodeActionsAutoSave(nls.localize('codeActionsOnSave.fixAll', "Controls whether auto fix action should be run on file save.")), -}); +const createNotebookCodeActionsAutoSave = (description: string): IJSONSchema => { + return { + type: ['string', 'boolean'], + enum: ['explicit', 'never', true, false], + enumDescriptions: [ + nls.localize('explicit', 'Triggers Code Actions only when explicitly saved.'), + nls.localize('never', 'Never triggers Code Actions on save.'), + nls.localize('explicitBoolean', 'Triggers Code Actions only when explicitly saved. This value will be deprecated in favor of "explicit".'), + nls.localize('neverBoolean', 'Triggers Code Actions only when explicitly saved. This value will be deprecated in favor of "never".') + ], + default: 'explicit', + description: description + }; +}; + const codeActionsOnSaveSchema: IConfigurationPropertySchema = { oneOf: [ { type: 'object', - properties: codeActionsOnSaveDefaultProperties, additionalProperties: { type: 'string' }, @@ -69,18 +81,57 @@ export const editorConfiguration = Object.freeze({ } }); +const notebookCodeActionsOnSaveSchema: IConfigurationPropertySchema = { + oneOf: [ + { + type: 'object', + additionalProperties: { + type: 'string' + }, + }, + { + type: 'array', + items: { type: 'string' } + } + ], + markdownDescription: nls.localize('notebook.codeActionsOnSave', 'Run a series of Code Actions for a notebook on save. Code Actions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `"notebook.source.organizeImports": "explicit"`'), + type: 'object', + additionalProperties: { + type: ['string', 'boolean'], + enum: ['explicit', 'never', true, false], + // enum: ['explicit', 'always', 'never'], -- autosave support needs to be built first + // nls.localize('always', 'Always triggers Code Actions on save, including autosave, focus, and window change events.'), + }, + default: {} +}; + +export const notebookEditorConfiguration = Object.freeze({ + ...editorConfigurationBaseNode, + properties: { + 'notebook.codeActionsOnSave': notebookCodeActionsOnSaveSchema + } +}); + export class CodeActionsContribution extends Disposable implements IWorkbenchContribution { private _contributedCodeActions: CodeActionsExtensionPoint[] = []; + private settings: Set = new Set(); private readonly _onDidChangeContributions = this._register(new Emitter()); constructor( codeActionsExtensionPoint: IExtensionPoint, @IKeybindingService keybindingService: IKeybindingService, + @ILanguageFeaturesService private readonly languageFeatures: ILanguageFeaturesService ) { super(); + // TODO: @justschen caching of code actions based on extensions loaded: https://github.com/microsoft/vscode/issues/216019 + languageFeatures.codeActionProvider.onDidChange(() => { + this.updateSettingsFromCodeActionProviders(); + this.updateConfigurationSchemaFromContribs(); + }, 2000); + codeActionsExtensionPoint.setHandler(extensionPoints => { this._contributedCodeActions = extensionPoints.flatMap(x => x.value).filter(x => Array.isArray(x.actions)); this.updateConfigurationSchema(this._contributedCodeActions); @@ -93,26 +144,54 @@ export class CodeActionsContribution extends Disposable implements IWorkbenchCon }); } + private updateSettingsFromCodeActionProviders(): void { + const providers = this.languageFeatures.codeActionProvider.allNoModel(); + providers.forEach(provider => { + if (provider.providedCodeActionKinds) { + provider.providedCodeActionKinds.forEach(kind => { + if (!this.settings.has(kind) && CodeActionKind.Source.contains(new HierarchicalKind(kind))) { + this.settings.add(kind); + } + }); + } + }); + } + private updateConfigurationSchema(codeActionContributions: readonly CodeActionsExtensionPoint[]) { - const newProperties: IJSONSchemaMap = { ...codeActionsOnSaveDefaultProperties }; + const newProperties: IJSONSchemaMap = {}; + const newNotebookProperties: IJSONSchemaMap = {}; for (const [sourceAction, props] of this.getSourceActions(codeActionContributions)) { + this.settings.add(sourceAction); newProperties[sourceAction] = createCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title)); + newNotebookProperties[sourceAction] = createNotebookCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title)); } codeActionsOnSaveSchema.properties = newProperties; + notebookCodeActionsOnSaveSchema.properties = newNotebookProperties; + Registry.as(Extensions.Configuration) + .notifyConfigurationSchemaUpdated(editorConfiguration); + } + + private updateConfigurationSchemaFromContribs() { + const properties: IJSONSchemaMap = { ...codeActionsOnSaveSchema.properties }; + const notebookProperties: IJSONSchemaMap = { ...notebookCodeActionsOnSaveSchema.properties }; + for (const codeActionKind of this.settings) { + if (!properties[codeActionKind]) { + properties[codeActionKind] = createCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", codeActionKind)); + notebookProperties[codeActionKind] = createNotebookCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", codeActionKind)); + } + } + codeActionsOnSaveSchema.properties = properties; + notebookCodeActionsOnSaveSchema.properties = notebookProperties; Registry.as(Extensions.Configuration) .notifyConfigurationSchemaUpdated(editorConfiguration); } private getSourceActions(contributions: readonly CodeActionsExtensionPoint[]) { - const defaultKinds = Object.keys(codeActionsOnSaveDefaultProperties).map(value => new HierarchicalKind(value)); const sourceActions = new Map(); for (const contribution of contributions) { for (const action of contribution.actions) { const kind = new HierarchicalKind(action.kind); - if (CodeActionKind.Source.contains(kind) - // Exclude any we already included by default - && !defaultKinds.some(defaultKind => defaultKind.contains(kind)) - ) { + if (CodeActionKind.Source.contains(kind)) { sourceActions.set(kind.value, action); } } diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/diffEditorAccessibilityHelp.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/diffEditorAccessibilityHelp.ts index ef4ce769..2f3564d2 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/diffEditorAccessibilityHelp.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/diffEditorAccessibilityHelp.ts @@ -7,7 +7,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor/commands'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { localize } from 'vs/nls'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ContextKeyEqualsExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -36,13 +36,13 @@ export class DiffEditorAccessibilityHelp implements IAccessibleViewImplentation return; } - const switchSides = localize('msg3', "Run the command Diff Editor: Switch Side to toggle between the original and modified editors."); + const switchSides = localize('msg3', "Run the command Diff Editor: Switch Side{0} to toggle between the original and modified editors.", ''); const diffEditorActiveAnnouncement = localize('msg5', "The setting, accessibility.verbosity.diffEditorActive, controls if a diff editor announcement is made when it becomes the active editor."); const keys = ['accessibility.signals.diffLineDeleted', 'accessibility.signals.diffLineInserted', 'accessibility.signals.diffLineModified']; const content = [ localize('msg1', "You are in a diff editor."), - localize('msg2', "View the next or previous diff in diff review mode, which is optimized for screen readers.", AccessibleDiffViewerNext.id, AccessibleDiffViewerPrev.id), + localize('msg2', "View the next{0} or previous{1} diff in diff review mode, which is optimized for screen readers.", '', ''), switchSides, diffEditorActiveAnnouncement, localize('msg4', "To control which accessibility signals should be played, the following settings can be configured: {0}.", keys.join(', ')), @@ -51,15 +51,12 @@ export class DiffEditorAccessibilityHelp implements IAccessibleViewImplentation if (commentCommandInfo) { content.push(commentCommandInfo); } - return { - id: AccessibleViewProviderId.DiffEditor, - verbositySettingKey: AccessibilityVerbositySettingId.DiffEditor, - provideContent: () => content.join('\n\n'), - onClose: () => { - codeEditor.focus(); - }, - options: { type: AccessibleViewType.Help } - }; + return new AccessibleContentProvider( + AccessibleViewProviderId.DiffEditor, + { type: AccessibleViewType.Help }, + () => content.join('\n'), + () => codeEditor.focus(), + AccessibilityVerbositySettingId.DiffEditor, + ); } - dispose() { } } diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 052608ed..15fba768 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -33,7 +33,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont const isEmbeddedDiffEditor = this._diffEditor instanceof EmbeddedDiffEditorWidget; if (!isEmbeddedDiffEditor) { - const computationResult = observableFromEvent(e => this._diffEditor.onDidUpdateDiff(e), () => /** @description diffEditor.diffComputationResult */ this._diffEditor.getDiffComputationResult()); + const computationResult = observableFromEvent(this, e => this._diffEditor.onDidUpdateDiff(e), () => /** @description diffEditor.diffComputationResult */ this._diffEditor.getDiffComputationResult()); const onlyWhiteSpaceChange = computationResult.map(r => r && !r.identical && r.changes2.length === 0); this._register(autorunWithStore((reader, store) => { diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index b047830a..952a32ea 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -29,33 +29,16 @@ import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLa import { OS } from 'vs/base/common/platform'; import { status } from 'vs/base/browser/ui/aria/aria'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; import { LOG_MODE_ID, OUTPUT_MODE_ID } from 'vs/workbench/services/output/common/output'; import { SEARCH_RESULT_LANGUAGE_ID } from 'vs/workbench/services/search/common/search'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { ChatAgentLocation, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; const $ = dom.$; -// TODO@joyceerhl remove this after a few iterations -Registry.as(Extensions.ConfigurationMigration) - .registerConfigurationMigrations([{ - key: 'workbench.editor.untitled.hint', - migrateFn: (value, _accessor) => ([ - [emptyTextEditorHintSetting, { value }], - ['workbench.editor.untitled.hint', { value: undefined }] - ]) - }, - { - key: 'accessibility.verbosity.untitledHint', - migrateFn: (value, _accessor) => ([ - [AccessibilityVerbositySettingId.EmptyEditorHint, { value }], - ['accessibility.verbosity.untitledHint', { value: undefined }] - ]) - }]); - export interface IEmptyTextEditorHintOptions { readonly clickable?: boolean; } @@ -79,6 +62,7 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { @IChatAgentService private readonly chatAgentService: IChatAgentService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IProductService protected readonly productService: IProductService, + @IContextMenuService private readonly contextMenuService: IContextMenuService ) { this.toDispose = []; this.toDispose.push(this.editor.onDidChangeModel(() => this.update())); @@ -147,7 +131,7 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { } const hasEditorAgents = Boolean(this.chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)); - const shouldRenderDefaultHint = model?.uri.scheme === Schemas.untitled && languageId === PLAINTEXT_LANGUAGE_ID && hasEditorAgents; + const shouldRenderDefaultHint = model?.uri.scheme === Schemas.untitled && languageId === PLAINTEXT_LANGUAGE_ID; return hasEditorAgents || shouldRenderDefaultHint; } @@ -164,7 +148,8 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { this.keybindingService, this.chatAgentService, this.telemetryService, - this.productService + this.productService, + this.contextMenuService ); } else if (!shouldRenderHint && this.textHintContentWidget) { this.textHintContentWidget.dispose(); @@ -197,7 +182,8 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { private readonly keybindingService: IKeybindingService, private readonly chatAgentService: IChatAgentService, private readonly telemetryService: ITelemetryService, - private readonly productService: IProductService + private readonly productService: IProductService, + private readonly contextMenuService: IContextMenuService, ) { this.toDispose = new DisposableStore(); this.toDispose.add(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { @@ -218,6 +204,36 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { return EmptyTextEditorHintContentWidget.ID; } + private _disableHint(e?: MouseEvent) { + const disableHint = () => { + this.configurationService.updateValue(emptyTextEditorHintSetting, 'hidden'); + this.dispose(); + this.editor.focus(); + }; + + if (!e) { + disableHint(); + return; + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => { return new StandardMouseEvent(dom.getActiveWindow(), e); }, + getActions: () => { + return [{ + id: 'workench.action.disableEmptyEditorHint', + label: localize('disableEditorEmptyHint', "Disable Empty Editor Hint"), + tooltip: localize('disableEditorEmptyHint', "Disable Empty Editor Hint"), + enabled: true, + class: undefined, + run: () => { + disableHint(); + } + } + ]; + } + }); + } + private _getHintInlineChat(providers: IChatAgent[]) { const providerName = (providers.length === 1 ? providers[0].fullName : undefined) ?? this.productService.nameShort; @@ -257,6 +273,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { const hintPart = $('a', undefined, fragment); hintPart.style.fontStyle = 'italic'; hintPart.style.cursor = 'pointer'; + this.toDispose.add(dom.addDisposableListener(hintPart, dom.EventType.CONTEXT_MENU, (e) => this._disableHint(e))); this.toDispose.add(dom.addDisposableListener(hintPart, dom.EventType.CLICK, handleClick)); return hintPart; } else { @@ -275,6 +292,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { if (this.options.clickable) { label.element.style.cursor = 'pointer'; + this.toDispose.add(dom.addDisposableListener(label.element, dom.EventType.CONTEXT_MENU, (e) => this._disableHint(e))); this.toDispose.add(dom.addDisposableListener(label.element, dom.EventType.CLICK, handleClick)); } @@ -297,7 +315,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { hintElement.appendChild(rendered); } - return { ariaLabel, hintHandler, hintElement }; + return { ariaLabel, hintElement }; } private _getHintDefault() { @@ -315,7 +333,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { chooseEditorOnClickOrTap(event.browserEvent); break; case '3': - dontShowOnClickOrTap(); + this._disableHint(); break; } } @@ -330,7 +348,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { id: ChangeLanguageAction.ID, from: 'hint' }); - await this.commandService.executeCommand(ChangeLanguageAction.ID, { from: 'hint' }); + await this.commandService.executeCommand(ChangeLanguageAction.ID); this.editor.focus(); }; @@ -360,12 +378,6 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { } }; - const dontShowOnClickOrTap = () => { - this.configurationService.updateValue(emptyTextEditorHintSetting, 'hidden'); - this.dispose(); - this.editor.focus(); - }; - const hintMsg = localize({ key: 'message', comment: [ @@ -387,7 +399,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { anchor.style.cursor = 'pointer'; const id = keybindingsLookup.shift(); const title = id && this.keybindingService.lookupKeybinding(id)?.getLabel(); - hintHandler.disposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), anchor, title ?? '')); + hintHandler.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), anchor, title ?? '')); } return { hintElement, ariaLabel }; diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index bfbdc1d3..c7390207 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -278,9 +278,7 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa override dispose() { super.dispose(); - if (this._domNode && this._domNode.parentElement) { - this._domNode.parentElement.removeChild(this._domNode); - } + this._domNode?.remove(); } public isVisible(): boolean { diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 8b2fa1d6..535ccd77 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -16,7 +16,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { SemanticTokensLegend, SemanticTokens } from 'vs/editor/common/languages'; +import { SemanticTokensLegend, SemanticTokens, TreeSitterTokenizationRegistry } from 'vs/editor/common/languages'; import { FontStyle, ColorId, StandardTokenType, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -31,6 +31,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { SEMANTIC_HIGHLIGHTING_SETTING_ID, IEditorSemanticHighlightingOptions } from 'vs/editor/contrib/semanticTokens/common/semanticTokensConfig'; import { Schemas } from 'vs/base/common/network'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; +// eslint-disable-next-line local/code-import-patterns +import type { Parser } from '@vscode/tree-sitter-wasm'; const $ = dom.$; @@ -44,6 +47,7 @@ class InspectEditorTokensController extends Disposable implements IEditorContrib private _editor: ICodeEditor; private _textMateService: ITextMateTokenizationService; + private _treeSitterService: ITreeSitterParserService; private _themeService: IWorkbenchThemeService; private _languageService: ILanguageService; private _notificationService: INotificationService; @@ -54,6 +58,7 @@ class InspectEditorTokensController extends Disposable implements IEditorContrib constructor( editor: ICodeEditor, @ITextMateTokenizationService textMateService: ITextMateTokenizationService, + @ITreeSitterParserService treeSitterService: ITreeSitterParserService, @ILanguageService languageService: ILanguageService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, @INotificationService notificationService: INotificationService, @@ -63,6 +68,7 @@ class InspectEditorTokensController extends Disposable implements IEditorContrib super(); this._editor = editor; this._textMateService = textMateService; + this._treeSitterService = treeSitterService; this._themeService = themeService; this._languageService = languageService; this._notificationService = notificationService; @@ -91,7 +97,7 @@ class InspectEditorTokensController extends Disposable implements IEditorContrib // disable in notebooks return; } - this._widget = new InspectEditorTokensWidget(this._editor, this._textMateService, this._languageService, this._themeService, this._notificationService, this._configurationService, this._languageFeaturesService); + this._widget = new InspectEditorTokensWidget(this._editor, this._textMateService, this._treeSitterService, this._languageService, this._themeService, this._notificationService, this._configurationService, this._languageFeaturesService); } public stop(): void { @@ -188,6 +194,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { private readonly _languageService: ILanguageService; private readonly _themeService: IWorkbenchThemeService; private readonly _textMateService: ITextMateTokenizationService; + private readonly _treeSitterService: ITreeSitterParserService; private readonly _notificationService: INotificationService; private readonly _configurationService: IConfigurationService; private readonly _languageFeaturesService: ILanguageFeaturesService; @@ -198,6 +205,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { constructor( editor: IActiveCodeEditor, textMateService: ITextMateTokenizationService, + treeSitterService: ITreeSitterParserService, languageService: ILanguageService, themeService: IWorkbenchThemeService, notificationService: INotificationService, @@ -210,6 +218,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { this._languageService = languageService; this._themeService = themeService; this._textMateService = textMateService; + this._treeSitterService = treeSitterService; this._notificationService = notificationService; this._configurationService = configurationService; this._languageFeaturesService = languageFeaturesService; @@ -238,6 +247,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { private _beginCompute(position: Position): void { const grammar = this._textMateService.createTokenizer(this._model.getLanguageId()); const semanticTokens = this._computeSemanticTokens(position); + const tree = this._treeSitterService.getParseResult(this._model); dom.clearNode(this._domNode); this._domNode.appendChild(document.createTextNode(nls.localize('inspectTMScopesWidget.loading', "Loading..."))); @@ -246,7 +256,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { if (this._isDisposed) { return; } - this._compute(grammar, semanticTokens, position); + this._compute(grammar, semanticTokens, tree?.tree, position); this._domNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; this._editor.layoutContentWidget(this); }, (err) => { @@ -267,10 +277,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return this._themeService.getColorTheme().semanticHighlighting; } - private _compute(grammar: IGrammar | null, semanticTokens: SemanticTokensResult | null, position: Position) { + private _compute(grammar: IGrammar | null, semanticTokens: SemanticTokensResult | null, tree: Parser.Tree | undefined, position: Position) { const textMateTokenInfo = grammar && this._getTokensAtPosition(grammar, position); const semanticTokenInfo = semanticTokens && this._getSemanticTokenAtPosition(semanticTokens, position); - if (!textMateTokenInfo && !semanticTokenInfo) { + const treeSitterTokenInfo = tree && this._getTreeSitterTokenAtPosition(tree, position); + if (!textMateTokenInfo && !semanticTokenInfo && !treeSitterTokenInfo) { dom.reset(this._domNode, 'No grammar or semantic tokens available.'); return; } @@ -389,6 +400,40 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { )); } } + + if (treeSitterTokenInfo) { + dom.append(this._domNode, $('hr.tiw-metadata-separator')); + const table = dom.append(this._domNode, $('table.tiw-metadata-table')); + const tbody = dom.append(table, $('tbody')); + + dom.append(tbody, $('tr', undefined, + $('td.tiw-metadata-key', undefined, 'tree-sitter token' as string), + $('td.tiw-metadata-value', undefined, `${treeSitterTokenInfo.text}`) + )); + const scopes = new Array(); + let node = treeSitterTokenInfo; + while (node.parent) { + scopes.push(node.type); + node = node.parent; + if (node) { + scopes.push($('br')); + } + } + + dom.append(tbody, $('tr', undefined, + $('td.tiw-metadata-key', undefined, 'tree-sitter scopes' as string), + $('td.tiw-metadata-value.tiw-metadata-scopes', undefined, ...scopes), + )); + + const tokenizationSupport = TreeSitterTokenizationRegistry.get(this._model.getLanguageId()); + const captures = tokenizationSupport?.captureAtPosition(position.lineNumber, position.column, this._model); + if (captures && captures.length > 0) { + dom.append(tbody, $('tr', undefined, + $('td.tiw-metadata-key', undefined, 'foreground'), + $('td.tiw-metadata-value', undefined, captures[0].name), + )); + } + } } private _formatMetadata(semantic?: IDecodedMetadata, tm?: IDecodedMetadata): Array { @@ -603,6 +648,28 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return null; } + private _walkTreeforPosition(cursor: Parser.TreeCursor, pos: Position): Parser.SyntaxNode | null { + const offset = this._model.getOffsetAt(pos); + cursor.gotoFirstChild(); + let goChild: boolean = false; + let lastGoodNode: Parser.SyntaxNode | null = null; + do { + if (cursor.currentNode.startIndex <= offset && offset < cursor.currentNode.endIndex) { + goChild = true; + lastGoodNode = cursor.currentNode; + } else { + goChild = false; + } + } while (goChild ? cursor.gotoFirstChild() : cursor.gotoNextSibling()); + return lastGoodNode; + } + + private _getTreeSitterTokenAtPosition(tree: Parser.Tree, pos: Position): Parser.SyntaxNode | null { + const cursor = tree.walk(); + + return this._walkTreeforPosition(cursor, pos); + } + private _renderTokenStyleDefinition(definition: TokenStyleDefinition | undefined, property: keyof TokenStyleData): Array { const elements = new Array(); if (definition === undefined) { diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index 2a40f9d9..ab35c780 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -123,7 +123,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess //#endregion - protected override provideWithoutTextEditor(picker: IQuickPick): IDisposable { + protected override provideWithoutTextEditor(picker: IQuickPick): IDisposable { if (this.canPickWithOutlineService()) { return this.doGetOutlinePicks(picker); } @@ -134,7 +134,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess return this.editorService.activeEditorPane ? this.outlineService.canCreateOutline(this.editorService.activeEditorPane) : false; } - private doGetOutlinePicks(picker: IQuickPick): IDisposable { + private doGetOutlinePicks(picker: IQuickPick): IDisposable { const pane = this.editorService.activeEditorPane; if (!pane) { return Disposable.None; diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index d04fd459..53bb3421 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { HierarchicalKind } from 'vs/base/common/hierarchicalKind'; import { Disposable } from 'vs/base/common/lifecycle'; +import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; import { IActiveCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -27,9 +28,12 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep, Progress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; import { SaveReason } from 'vs/workbench/common/editor'; import { getModifiedRanges } from 'vs/workbench/contrib/format/browser/formatModified'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ITextFileEditorModel, ITextFileSaveParticipant, ITextFileSaveParticipantContext, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -267,13 +271,54 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { } } -class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { +class CodeActionOnSaveParticipant extends Disposable implements ITextFileSaveParticipant { constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, - ) { } + @IHostService private readonly hostService: IHostService, + @IEditorService private readonly editorService: IEditorService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @ITelemetryService private readonly telemetryService: ITelemetryService + ) { + super(); + + this._register(this.hostService.onDidChangeFocus(() => { this.triggerCodeActionsCommand(); })); + this._register(this.editorService.onDidActiveEditorChange(() => { this.triggerCodeActionsCommand(); })); + } + + private async triggerCodeActionsCommand() { + if (this.configurationService.getValue('editor.codeActions.triggerOnFocusChange') && this.configurationService.getValue('files.autoSave') === 'afterDelay') { + const model = this.codeEditorService.getActiveCodeEditor()?.getModel(); + if (!model) { + return undefined; + } + + const settingsOverrides = { overrideIdentifier: model.getLanguageId(), resource: model.uri }; + const setting = this.configurationService.getValue<{ [kind: string]: string | boolean } | string[]>('editor.codeActionsOnSave', settingsOverrides); + + if (!setting) { + return undefined; + } + + if (Array.isArray(setting)) { + return undefined; + } + + const settingItems: string[] = Object.keys(setting).filter(x => setting[x] && setting[x] === 'always' && CodeActionKind.Source.contains(new HierarchicalKind(x))); + + const cancellationTokenSource = new CancellationTokenSource(); + + const codeActionKindList = []; + for (const item of settingItems) { + codeActionKindList.push(new HierarchicalKind(item)); + } + + // run code actions based on what is found from setting === 'always', no exclusions. + await this.applyOnSaveActions(model, codeActionKindList, [], Progress.None, cancellationTokenSource.token); + } + } async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, progress: IProgress, token: CancellationToken): Promise { if (!model.textEditorModel) { @@ -366,7 +411,27 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { }; for (const codeActionKind of codeActionsOnSave) { + const sw = new StopWatch(); + const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, getActionProgress, token); + + // Telemetry for duration of each code action on save. + type CodeActionOnSave = { + codeAction: string; + duration: number; + }; + type CodeActionOnSaveClassification = { + owner: 'justschen'; + comment: 'Information about the code action that was accepted on save.'; + codeAction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Kind of the code action setting that is run.' }; + duration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Duration it took for TS to return the action to run for each kind. ' }; + }; + + this.telemetryService.publicLog2('codeAction.appliedOnSave', { + codeAction: codeActionKind.value, + duration: sw.elapsed() + }); + if (token.isCancellationRequested) { actionsToRun.dispose(); return; diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index f7660482..912b9a49 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -13,6 +13,9 @@ import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEdito import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { selectionBackground, inputBackground, inputForeground, editorSelectionBackground } from 'vs/platform/theme/common/colorRegistry'; export function getSimpleEditorOptions(configurationService: IConfigurationService): IEditorOptions { return { @@ -60,3 +63,35 @@ export function getSimpleCodeEditorWidgetOptions(): ICodeEditorWidgetOptions { ]) }; } + +/** + * Should be called to set the styling on editors that are appearing as just input boxes + * @param editorContainerSelector An element selector that will match the container of the editor + */ +export function setupSimpleEditorSelectionStyling(editorContainerSelector: string): IDisposable { + // Override styles in selections.ts + return registerThemingParticipant((theme, collector) => { + const selectionBackgroundColor = theme.getColor(selectionBackground); + + if (selectionBackgroundColor) { + // Override inactive selection bg + const inputBackgroundColor = theme.getColor(inputBackground); + if (inputBackgroundColor) { + collector.addRule(`${editorContainerSelector} .monaco-editor-background { background-color: ${inputBackgroundColor}; } `); + collector.addRule(`${editorContainerSelector} .monaco-editor .selected-text { background-color: ${inputBackgroundColor.transparent(0.4)}; }`); + } + + // Override selected fg + const inputForegroundColor = theme.getColor(inputForeground); + if (inputForegroundColor) { + collector.addRule(`${editorContainerSelector} .monaco-editor .view-line span.inline-selected-text { color: ${inputForegroundColor}; }`); + } + + collector.addRule(`${editorContainerSelector} .monaco-editor .focused .selected-text { background-color: ${selectionBackgroundColor}; }`); + } else { + // Use editor selection color if theme has not set a selection background color + collector.addRule(`${editorContainerSelector} .monaco-editor .focused .selected-text { background-color: ${theme.getColor(editorSelectionBackground)}; }`); + } + }); + +} diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 6fbc04ff..64bfec8b 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -3,42 +3,41 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./suggestEnabledInput'; import { $, Dimension, append } from 'vs/base/browser/dom'; +import { DEFAULT_FONT_FAMILY } from 'vs/base/browser/fonts'; +import { IHistoryNavigationWidget } from 'vs/base/browser/history'; import { Widget } from 'vs/base/browser/ui/widget'; import { Emitter, Event } from 'vs/base/common/event'; +import { HistoryNavigator } from 'vs/base/common/history'; import { KeyCode } from 'vs/base/common/keyCodes'; import { mixin } from 'vs/base/common/objects'; import { isMacintosh } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; +import 'vs/css!./suggestEnabledInput'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; +import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/core/wordHelper'; import * as languages from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IModelService } from 'vs/editor/common/services/model'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IHistoryNavigationContext, registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ColorIdentifier, asCssVariable, asCssVariableWithDefault, editorSelectionBackground, inputBackground, inputBorder, inputForeground, inputPlaceholderForeground, selectionBackground } from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ColorIdentifier, asCssVariable, asCssVariableWithDefault, inputBackground, inputBorder, inputForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; -import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { DEFAULT_FONT_FAMILY } from 'vs/base/browser/fonts'; -import { HistoryNavigator } from 'vs/base/common/history'; -import { registerAndCreateHistoryNavigationContext, IHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget'; -import { IHistoryNavigationWidget } from 'vs/base/browser/history'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/core/wordHelper'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { getSimpleEditorOptions, setupSimpleEditorSelectionStyling } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; export interface SuggestResultsProvider { /** @@ -162,7 +161,7 @@ export class SuggestEnabledInput extends Widget { const scopedContextKeyService = this.getScopedContextKeyService(contextKeyService); const instantiationService = scopedContextKeyService - ? defaultInstantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])) + ? this._register(defaultInstantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService]))) : defaultInstantiationService; this.inputWidget = this._register(instantiationService.createInstance(CodeEditorWidget, this.stylingContainer, @@ -462,34 +461,7 @@ export class ContextScopedSuggestEnabledInputWithHistory extends SuggestEnabledI } } -// Override styles in selections.ts -registerThemingParticipant((theme, collector) => { - const selectionBackgroundColor = theme.getColor(selectionBackground); - - if (selectionBackgroundColor) { - // Override inactive selection bg - const inputBackgroundColor = theme.getColor(inputBackground); - if (inputBackgroundColor) { - collector.addRule(`.suggest-input-container .monaco-editor .selected-text { background-color: ${inputBackgroundColor.transparent(0.4)}; }`); - } - - // Override selected fg - const inputForegroundColor = theme.getColor(inputForeground); - if (inputForegroundColor) { - collector.addRule(`.suggest-input-container .monaco-editor .view-line span.inline-selected-text { color: ${inputForegroundColor}; }`); - } - - const backgroundColor = theme.getColor(inputBackground); - if (backgroundColor) { - collector.addRule(`.suggest-input-container .monaco-editor-background { background-color: ${backgroundColor}; } `); - } - collector.addRule(`.suggest-input-container .monaco-editor .focused .selected-text { background-color: ${selectionBackgroundColor}; }`); - } else { - // Use editor selection color if theme has not set a selection background color - collector.addRule(`.suggest-input-container .monaco-editor .focused .selected-text { background-color: ${theme.getColor(editorSelectionBackground)}; }`); - } -}); - +setupSimpleEditorSelectionStyling('.suggest-input-container'); function getSuggestEnabledInputOptions(ariaLabel?: string): IEditorOptions { return { diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts index f5b2c84e..5b508162 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize, localize2 } from 'vs/nls'; +import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; +import { localize, localize2 } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; export class ToggleMultiCursorModifierAction extends Action2 { @@ -39,7 +40,7 @@ export class ToggleMultiCursorModifierAction extends Action2 { const multiCursorModifier = new RawContextKey('multiCursorModifier', 'altKey'); -class MultiCursorModifierContextKeyController implements IWorkbenchContribution { +class MultiCursorModifierContextKeyController extends Disposable implements IWorkbenchContribution { private readonly _multiCursorModifier: IContextKey; @@ -47,14 +48,15 @@ class MultiCursorModifierContextKeyController implements IWorkbenchContribution @IConfigurationService private readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService ) { + super(); this._multiCursorModifier = multiCursorModifier.bindTo(contextKeyService); this._update(); - configurationService.onDidChangeConfiguration((e) => { + this._register(configurationService.onDidChangeConfiguration((e) => { if (e.affectsConfiguration('editor.multiCursorModifier')) { this._update(); } - }); + })); } private _update(): void { diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 7cdd7d91..043e3f5c 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -3,25 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; +import { Codicon } from 'vs/base/common/codicons'; +import { Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution, registerDiffEditorContribution, EditorContributionInstantiation } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorContributionInstantiation, ServicesAccessor, registerDiffEditorContribution, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IDiffEditorContribution, IEditorContribution } from 'vs/editor/common/editorCommon'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; +import * as nls from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { Codicon } from 'vs/base/common/codicons'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Event } from 'vs/base/common/event'; -import { addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; -import { mainWindow } from 'vs/base/browser/window'; const transientWordWrapState = 'transientWordWrapState'; const isWordWrapMinifiedKey = 'isWordWrapMinified'; @@ -271,7 +271,7 @@ class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchCo disposables.add(addDisposableListener(window, 'focus', () => this._update(), true)); disposables.add(addDisposableListener(window, 'blur', () => this._update(), true)); }, { window: mainWindow, disposables: this._store })); - this._editorService.onDidActiveEditorChange(() => this._update()); + this._register(this._editorService.onDidActiveEditorChange(() => this._update())); this._canToggleWordWrap = CAN_TOGGLE_WORD_WRAP.bindTo(this._contextService); this._editorWordWrap = EDITOR_WORD_WRAP.bindTo(this._contextService); this._activeEditor = null; diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts new file mode 100644 index 00000000..af972772 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WorkerDescriptor } from 'vs/base/browser/defaultWorkerFactory'; +import { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { ILogService } from 'vs/platform/log/common/log'; + +export class WorkbenchEditorWorkerService extends EditorWorkerService { + constructor( + @IModelService modelService: IModelService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @ILogService logService: ILogService, + @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + ) { + const workerDescriptor = new WorkerDescriptor('vs/editor/common/services/editorSimpleWorker', 'TextEditorWorker'); + super(workerDescriptor, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts index f8960304..42cc4253 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { FinalNewLineParticipant, TrimFinalNewLinesParticipant, TrimWhitespaceParticipant } from 'vs/workbench/contrib/codeEditor/browser/saveParticipants'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts b/patched-vscode/src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts index 10b9058b..1bd9688e 100644 --- a/patched-vscode/src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; @@ -23,6 +23,7 @@ import { EncodedTokenizationResult, IState, ITokenizationSupport, TokenizationRe import { NullState } from 'vs/editor/common/languages/nullTokenize'; import { MetadataConsts, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { ITextModel } from 'vs/editor/common/model'; +import { FileAccess } from 'vs/base/common/network'; function getIRange(range: IRange): IRange { return { @@ -56,7 +57,7 @@ function registerLanguageConfiguration(instantiationService: TestInstantiationSe let configPath: string; switch (languageId) { case LanguageId.TypeScript: - configPath = path.join('extensions', 'typescript-basics', 'language-configuration.json'); + configPath = FileAccess.asFileUri('vs/workbench/contrib/codeEditor/test/node/language-configuration.json').fsPath; break; default: throw new Error('Unknown languageId'); diff --git a/patched-vscode/src/vs/workbench/contrib/codeEditor/test/node/language-configuration.json b/patched-vscode/src/vs/workbench/contrib/codeEditor/test/node/language-configuration.json new file mode 100644 index 00000000..25a23685 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/codeEditor/test/node/language-configuration.json @@ -0,0 +1,250 @@ +{ + // Note that this file should stay in sync with 'javascript-language-basics/javascript-language-configuration.json' + "comments": { + "lineComment": "//", + "blockComment": [ + "/*", + "*/" + ] + }, + "brackets": [ + [ + "${", + "}" + ], + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] + ], + "autoClosingPairs": [ + { + "open": "{", + "close": "}" + }, + { + "open": "[", + "close": "]" + }, + { + "open": "(", + "close": ")" + }, + { + "open": "'", + "close": "'", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + }, + { + "open": "`", + "close": "`", + "notIn": [ + "string", + "comment" + ] + }, + { + "open": "/**", + "close": " */", + "notIn": [ + "string" + ] + } + ], + "surroundingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "'", + "'" + ], + [ + "\"", + "\"" + ], + [ + "`", + "`" + ], + [ + "<", + ">" + ] + ], + "colorizedBracketPairs": [ + [ + "(", + ")" + ], + [ + "[", + "]" + ], + [ + "{", + "}" + ], + [ + "<", + ">" + ] + ], + "autoCloseBefore": ";:.,=}])>` \n\t", + "folding": { + "markers": { + "start": "^\\s*//\\s*#?region\\b", + "end": "^\\s*//\\s*#?endregion\\b" + } + }, + "wordPattern": { + "pattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\@\\~\\!\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>/\\?\\s]+)", + }, + "indentationRules": { + "decreaseIndentPattern": { + "pattern": "^\\s*[\\}\\]\\)].*$" + }, + "increaseIndentPattern": { + "pattern": "^.*(\\{[^}]*|\\([^)]*|\\[[^\\]]*)$" + }, + // e.g. * ...| or */| or *-----*/| + "unIndentedLinePattern": { + "pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$|^(\\t|[ ])*[ ]\\*/\\s*$|^(\\t|[ ])*\\*([ ]([^\\*]|\\*(?!/))*)?$" + }, + "indentNextLinePattern": { + "pattern": "^((.*=>\\s*)|((.*[^\\w]+|\\s*)(if|while|for)\\s*\\(.*\\)\\s*))$" + } + }, + "onEnterRules": [ + { + // e.g. /** | */ + "beforeText": { + "pattern": "^\\s*/\\*\\*(?!/)([^\\*]|\\*(?!/))*$" + }, + "afterText": { + "pattern": "^\\s*\\*/$" + }, + "action": { + "indent": "indentOutdent", + "appendText": " * " + } + }, + { + // e.g. /** ...| + "beforeText": { + "pattern": "^\\s*/\\*\\*(?!/)([^\\*]|\\*(?!/))*$" + }, + "action": { + "indent": "none", + "appendText": " * " + } + }, + { + // e.g. * ...| + "beforeText": { + "pattern": "^(\\t|[ ])*\\*([ ]([^\\*]|\\*(?!/))*)?$" + }, + "previousLineText": { + "pattern": "(?=^(\\s*(/\\*\\*|\\*)).*)(?=(?!(\\s*\\*/)))" + }, + "action": { + "indent": "none", + "appendText": "* " + } + }, + { + // e.g. */| + "beforeText": { + "pattern": "^(\\t|[ ])*[ ]\\*/\\s*$" + }, + "action": { + "indent": "none", + "removeText": 1 + }, + }, + { + // e.g. *-----*/| + "beforeText": { + "pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$" + }, + "action": { + "indent": "none", + "removeText": 1 + }, + }, + { + "beforeText": { + "pattern": "^\\s*(\\bcase\\s.+:|\\bdefault:)$" + }, + "afterText": { + "pattern": "^(?!\\s*(\\bcase\\b|\\bdefault\\b))" + }, + "action": { + "indent": "indent" + } + }, + { + // Decrease indentation after single line if/else if/else, for, or while + "previousLineText": "^\\s*(((else ?)?if|for|while)\\s*\\(.*\\)\\s*|else\\s*)$", + // But make sure line doesn't have braces or is not another if statement + "beforeText": "^\\s+([^{i\\s]|i(?!f\\b))", + "action": { + "indent": "outdent" + } + }, + // Indent when pressing enter from inside () + { + "beforeText": "^.*\\([^\\)]*$", + "afterText": "^\\s*\\).*$", + "action": { + "indent": "indentOutdent", + "appendText": "\t", + } + }, + // Indent when pressing enter from inside {} + { + "beforeText": "^.*\\{[^\\}]*$", + "afterText": "^\\s*\\}.*$", + "action": { + "indent": "indentOutdent", + "appendText": "\t", + } + }, + // Indent when pressing enter from inside [] + { + "beforeText": "^.*\\[[^\\]]*$", + "afterText": "^\\s*\\].*$", + "action": { + "indent": "indentOutdent", + "appendText": "\t", + } + }, + ] +} diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentColors.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentColors.ts index 08d44a32..b66b9590 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentColors.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentColors.ts @@ -13,11 +13,11 @@ import { IColorTheme } from 'vs/platform/theme/common/themeService'; const resolvedCommentViewIcon = registerColor('commentsView.resolvedIcon', { dark: disabledForeground, light: disabledForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentIcon', 'Icon color for resolved comments.')); const unresolvedCommentViewIcon = registerColor('commentsView.unresolvedIcon', { dark: listFocusOutline, light: listFocusOutline, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentIcon', 'Icon color for unresolved comments.')); -registerColor('editorCommentsWidget.replyInputBackground', { dark: peekViewTitleBackground, light: peekViewTitleBackground, hcDark: peekViewTitleBackground, hcLight: peekViewTitleBackground }, nls.localize('commentReplyInputBackground', 'Background color for comment reply input box.')); +registerColor('editorCommentsWidget.replyInputBackground', peekViewTitleBackground, nls.localize('commentReplyInputBackground', 'Background color for comment reply input box.')); const resolvedCommentBorder = registerColor('editorCommentsWidget.resolvedBorder', { dark: resolvedCommentViewIcon, light: resolvedCommentViewIcon, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentBorder', 'Color of borders and arrow for resolved comments.')); const unresolvedCommentBorder = registerColor('editorCommentsWidget.unresolvedBorder', { dark: unresolvedCommentViewIcon, light: unresolvedCommentViewIcon, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentBorder', 'Color of borders and arrow for unresolved comments.')); -export const commentThreadRangeBackground = registerColor('editorCommentsWidget.rangeBackground', { dark: transparent(unresolvedCommentBorder, .1), light: transparent(unresolvedCommentBorder, .1), hcDark: transparent(unresolvedCommentBorder, .1), hcLight: transparent(unresolvedCommentBorder, .1) }, nls.localize('commentThreadRangeBackground', 'Color of background for comment ranges.')); -export const commentThreadRangeActiveBackground = registerColor('editorCommentsWidget.rangeActiveBackground', { dark: transparent(unresolvedCommentBorder, .1), light: transparent(unresolvedCommentBorder, .1), hcDark: transparent(unresolvedCommentBorder, .1), hcLight: transparent(unresolvedCommentBorder, .1) }, nls.localize('commentThreadActiveRangeBackground', 'Color of background for currently selected or hovered comment range.')); +export const commentThreadRangeBackground = registerColor('editorCommentsWidget.rangeBackground', transparent(unresolvedCommentBorder, .1), nls.localize('commentThreadRangeBackground', 'Color of background for comment ranges.')); +export const commentThreadRangeActiveBackground = registerColor('editorCommentsWidget.rangeActiveBackground', transparent(unresolvedCommentBorder, .1), nls.localize('commentThreadActiveRangeBackground', 'Color of background for currently selected or hovered comment range.')); const commentThreadStateBorderColors = new Map([ [languages.CommentThreadState.Unresolved, unresolvedCommentBorder], diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts index 92b52ac5..725b522f 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts @@ -14,11 +14,11 @@ import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { CommentThreadState } from 'vs/editor/common/languages'; export const overviewRulerCommentingRangeForeground = registerColor('editorGutter.commentRangeForeground', { dark: opaque(listInactiveSelectionBackground, editorBackground), light: darken(opaque(listInactiveSelectionBackground, editorBackground), .05), hcDark: Color.white, hcLight: Color.black }, nls.localize('editorGutterCommentRangeForeground', 'Editor gutter decoration color for commenting ranges. This color should be opaque.')); -const overviewRulerCommentForeground = registerColor('editorOverviewRuler.commentForeground', { dark: overviewRulerCommentingRangeForeground, light: overviewRulerCommentingRangeForeground, hcDark: overviewRulerCommentingRangeForeground, hcLight: overviewRulerCommentingRangeForeground }, nls.localize('editorOverviewRuler.commentForeground', 'Editor overview ruler decoration color for resolved comments. This color should be opaque.')); -const overviewRulerCommentUnresolvedForeground = registerColor('editorOverviewRuler.commentUnresolvedForeground', { dark: overviewRulerCommentForeground, light: overviewRulerCommentForeground, hcDark: overviewRulerCommentForeground, hcLight: overviewRulerCommentForeground }, nls.localize('editorOverviewRuler.commentUnresolvedForeground', 'Editor overview ruler decoration color for unresolved comments. This color should be opaque.')); +const overviewRulerCommentForeground = registerColor('editorOverviewRuler.commentForeground', overviewRulerCommentingRangeForeground, nls.localize('editorOverviewRuler.commentForeground', 'Editor overview ruler decoration color for resolved comments. This color should be opaque.')); +const overviewRulerCommentUnresolvedForeground = registerColor('editorOverviewRuler.commentUnresolvedForeground', overviewRulerCommentForeground, nls.localize('editorOverviewRuler.commentUnresolvedForeground', 'Editor overview ruler decoration color for unresolved comments. This color should be opaque.')); const editorGutterCommentGlyphForeground = registerColor('editorGutter.commentGlyphForeground', { dark: editorForeground, light: editorForeground, hcDark: Color.black, hcLight: Color.white }, nls.localize('editorGutterCommentGlyphForeground', 'Editor gutter decoration color for commenting glyphs.')); -registerColor('editorGutter.commentUnresolvedGlyphForeground', { dark: editorGutterCommentGlyphForeground, light: editorGutterCommentGlyphForeground, hcDark: editorGutterCommentGlyphForeground, hcLight: editorGutterCommentGlyphForeground }, nls.localize('editorGutterCommentUnresolvedGlyphForeground', 'Editor gutter decoration color for commenting glyphs for unresolved comment threads.')); +registerColor('editorGutter.commentUnresolvedGlyphForeground', editorGutterCommentGlyphForeground, nls.localize('editorGutterCommentUnresolvedGlyphForeground', 'Editor gutter decoration color for commenting glyphs for unresolved comment threads.')); export class CommentGlyphWidget { public static description = 'comment-glyph-widget'; diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentNode.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentNode.ts index 47e52854..f7f6688c 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -122,7 +122,7 @@ export class CommentNode extends Disposable { super(); this._domNode = dom.$('div.review-comment'); - this._contextKeyService = contextKeyService.createScoped(this._domNode); + this._contextKeyService = this._register(contextKeyService.createScoped(this._domNode)); this._commentContextValue = CommentContextKeys.commentContext.bindTo(this._contextKeyService); if (this.comment.contextValue) { this._commentContextValue.set(this.comment.contextValue); diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentReply.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentReply.ts index 9c9c8e24..4f50bfb1 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -22,8 +22,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; @@ -63,7 +61,6 @@ export class CommentReply extends Disposable { focus: boolean, private _actionRunDelegate: (() => void) | null, @ICommentService private commentService: ICommentService, - @IThemeService private themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, @IKeybindingService private keybindingService: IKeybindingService, @IHoverService private hoverService: IHoverService, @@ -213,32 +210,12 @@ export class CommentReply extends Disposable { } setCommentEditorDecorations() { - const model = this.commentEditor.getModel(); - if (model) { - const valueLength = model.getValueLength(); - const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0; - const placeholder = valueLength > 0 - ? '' - : hasExistingComments - ? (this._commentOptions?.placeHolder || nls.localize('reply', "Reply...")) - : (this._commentOptions?.placeHolder || nls.localize('newComment', "Type a new comment")); - const decorations = [{ - range: { - startLineNumber: 0, - endLineNumber: 0, - startColumn: 0, - endColumn: 1 - }, - renderOptions: { - after: { - contentText: placeholder, - color: `${resolveColorValue(editorForeground, this.themeService.getColorTheme())?.transparent(0.4)}` - } - } - }]; + const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0; + const placeholder = hasExistingComments + ? (this._commentOptions?.placeHolder || nls.localize('reply', "Reply...")) + : (this._commentOptions?.placeHolder || nls.localize('newComment', "Type a new comment")); - this.commentEditor.setDecorationsByType('review-zone-widget', COMMENTEDITOR_DECORATION_KEY, decorations); - } + this.commentEditor.updateOptions({ placeholder }); } private createTextModelListener(commentEditor: ICodeEditor, commentForm: HTMLElement) { @@ -368,7 +345,7 @@ export class CommentReply extends Disposable { private createReplyButton(commentEditor: ICodeEditor, commentForm: HTMLElement) { this._reviewThreadReplyButton = dom.append(commentForm, dom.$(`button.review-thread-reply-button.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`)); - this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this._reviewThreadReplyButton, this._commentOptions?.prompt || nls.localize('reply', "Reply..."))); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this._reviewThreadReplyButton, this._commentOptions?.prompt || nls.localize('reply', "Reply..."))); this._reviewThreadReplyButton.textContent = this._commentOptions?.prompt || nls.localize('reply', "Reply..."); // bind click/escape actions for reviewThreadReplyButton and textArea diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index 14db2828..bc62a581 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import * as languages from 'vs/editor/common/languages'; import { Emitter } from 'vs/base/common/event'; import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; @@ -30,7 +30,7 @@ export class CommentThreadBody extends D private _onDidResize = new Emitter(); onDidResize = this._onDidResize.event; - private _commentDisposable = new Map, IDisposable>(); + private _commentDisposable = new DisposableMap, DisposableStore>(); private _markdownRenderer: MarkdownRenderer; get length() { @@ -70,6 +70,12 @@ export class CommentThreadBody extends D this._commentsElement.focus(); } + ensureFocusIntoNewEditingComment() { + if (this._commentElements.length === 1 && this._commentElements[0].isEditing) { + this._commentElements[0].setFocus(true); + } + } + async display() { this._commentsElement = dom.append(this.container, dom.$('div.comments-container')); this._commentsElement.setAttribute('role', 'presentation'); @@ -90,6 +96,7 @@ export class CommentThreadBody extends D } })); + this._commentDisposable.clearAndDisposeAll(); this._commentElements = []; if (this._commentThread.comments) { for (const comment of this._commentThread.comments) { @@ -177,11 +184,10 @@ export class CommentThreadBody extends D // del removed elements for (let i = commentElementsToDel.length - 1; i >= 0; i--) { const commentToDelete = commentElementsToDel[i]; - this._commentDisposable.get(commentToDelete)?.dispose(); - this._commentDisposable.delete(commentToDelete); + this._commentDisposable.deleteAndDispose(commentToDelete); this._commentElements.splice(commentElementsToDelIndex[i], 1); - this._commentsElement.removeChild(commentToDelete.domNode); + commentToDelete.domNode.remove(); } @@ -267,10 +273,12 @@ export class CommentThreadBody extends D this._parentCommentThreadWidget, this._markdownRenderer) as unknown as CommentNode; - this._register(newCommentNode); - this._commentDisposable.set(newCommentNode, newCommentNode.onDidClick(clickedNode => + const disposables: DisposableStore = new DisposableStore(); + disposables.add(newCommentNode.onDidClick(clickedNode => this._setFocusedComment(this._commentElements.findIndex(commentNode => commentNode.comment.uniqueIdInThread === clickedNode.comment.uniqueIdInThread)) )); + disposables.add(newCommentNode); + this._commentDisposable.set(newCommentNode, disposables); return newCommentNode; } @@ -283,6 +291,6 @@ export class CommentThreadBody extends D this._resizeObserver = null; } - this._commentDisposable.forEach(v => v.dispose()); + this._commentDisposable.dispose(); } } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts index 2849c9af..d8bcd057 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, ActionRunner } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import * as languages from 'vs/editor/common/languages'; import { IRange } from 'vs/editor/common/core/range'; @@ -26,7 +26,11 @@ import { MarshalledCommentThread } from 'vs/workbench/common/comments'; const collapseIcon = registerIcon('review-comment-collapse', Codicon.chevronUp, nls.localize('collapseIcon', 'Icon to collapse a review comment.')); const COLLAPSE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(collapseIcon); +const DELETE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(Codicon.trashcan); +function threadHasComments(comments: ReadonlyArray | undefined): comments is ReadonlyArray { + return !!comments && comments.length > 0; +} export class CommentThreadHeader extends Disposable { private _headElement: HTMLElement; @@ -46,6 +50,7 @@ export class CommentThreadHeader extends Disposable { super(); this._headElement = dom.$('.head'); container.appendChild(this._headElement); + this._register(toDisposable(() => this._headElement.remove())); this._fillHead(); } @@ -62,7 +67,17 @@ export class CommentThreadHeader extends Disposable { this._register(this._actionbarWidget); - this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => this._delegate.collapse()); + const collapseClass = threadHasComments(this._commentThread.comments) ? COLLAPSE_ACTION_CLASS : DELETE_ACTION_CLASS; + this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), collapseClass, true, () => this._delegate.collapse()); + if (!threadHasComments(this._commentThread.comments)) { + const commentsChanged: MutableDisposable = this._register(new MutableDisposable()); + commentsChanged.value = this._commentThread.onDidChangeComments(() => { + if (threadHasComments(this._commentThread.comments)) { + this._collapseAction.class = COLLAPSE_ACTION_CLASS; + commentsChanged.clear(); + } + }); + } const menu = this._commentMenus.getCommentThreadTitleActions(this._contextKeyService); this._register(menu); diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index a919fe26..2838845b 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/review'; import * as dom from 'vs/base/browser/dom'; import { Emitter } from 'vs/base/common/event'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import * as languages from 'vs/editor/common/languages'; import { IMarkdownRendererOptions } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; @@ -23,7 +23,7 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { contrastBorder, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; -import { IRange } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import { commentThreadStateBackgroundColorVar, commentThreadStateColorVar } from 'vs/workbench/contrib/comments/browser/commentColors'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; @@ -36,10 +36,11 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { LayoutableEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; +import { DomEmitter } from 'vs/base/browser/event'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; - export class CommentThreadWidget extends Disposable implements ICommentThreadWidget { private _header!: CommentThreadHeader; private _body: CommentThreadBody; @@ -104,6 +105,7 @@ export class CommentThreadWidget extends const bodyElement = dom.$('.body'); container.appendChild(bodyElement); + this._register(toDisposable(() => bodyElement.remove())); const tracker = this._register(dom.trackFocus(bodyElement)); this._register(registerNavigableContainer({ @@ -155,6 +157,14 @@ export class CommentThreadWidget extends } this.currentThreadListeners(); + this._register(new DomEmitter(this.container, 'keydown').event(e => { + if (dom.isKeyboardEvent(e) && e.key === 'Escape') { + if (Range.isIRange(this.commentThread.range) && isCodeEditor(this._parentEditor)) { + this._parentEditor.setSelection(this.commentThread.range); + } + this.collapse(); + } + })); } private _setAriaLabel(): void { @@ -352,6 +362,10 @@ export class CommentThreadWidget extends } } + ensureFocusIntoNewEditingComment() { + this._body.ensureFocusIntoNewEditingComment(); + } + focusCommentEditor() { this._commentReply?.expandReplyAreaAndFocusCommentEditor(); } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index a79ec4c2..5fcfb5a4 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -139,9 +139,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget super(editor, { keepEditorSelection: true, isAccessible: true }); this._contextKeyService = contextKeyService.createScoped(this.domNode); - this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection( + this._scopedInstantiationService = this._globalToDispose.add(instantiationService.createChild(new ServiceCollection( [IContextKeyService, this._contextKeyService] - )); + ))); const controller = this.commentService.getCommentController(this._uniqueOwner); if (controller) { @@ -187,39 +187,58 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } public reveal(commentUniqueId?: number, focus: CommentWidgetFocus = CommentWidgetFocus.None) { + this.makeVisible(commentUniqueId, focus); + const comment = this._commentThread.comments?.find(comment => comment.uniqueIdInThread === commentUniqueId); + this.commentService.setActiveCommentAndThread(this.uniqueOwner, { thread: this._commentThread, comment }); + } + + private _expandAndShowZoneWidget() { if (!this._isExpanded) { this.show(this.arrowPosition(this._commentThread.range), 2); } + } - if (commentUniqueId !== undefined) { - const height = this.editor.getLayoutInfo().height; - const coords = this._commentThreadWidget.getCommentCoords(commentUniqueId); - if (coords) { - let scrollTop: number = 1; - if (this._commentThread.range) { - const commentThreadCoords = coords.thread; - const commentCoords = coords.comment; - scrollTop = this.editor.getTopForLineNumber(this._commentThread.range.startLineNumber) - height / 2 + commentCoords.top - commentThreadCoords.top; - } - this.editor.setScrollTop(scrollTop); - if (focus === CommentWidgetFocus.Widget) { - this._commentThreadWidget.focus(); - } else if (focus === CommentWidgetFocus.Editor) { - this._commentThreadWidget.focusCommentEditor(); - } - return; + private _setFocus(focus: CommentWidgetFocus) { + if (focus === CommentWidgetFocus.Widget) { + this._commentThreadWidget.focus(); + } else if (focus === CommentWidgetFocus.Editor) { + this._commentThreadWidget.focusCommentEditor(); + } + } + + private _goToComment(commentUniqueId: number, focus: CommentWidgetFocus) { + const height = this.editor.getLayoutInfo().height; + const coords = this._commentThreadWidget.getCommentCoords(commentUniqueId); + if (coords) { + let scrollTop: number = 1; + if (this._commentThread.range) { + const commentThreadCoords = coords.thread; + const commentCoords = coords.comment; + scrollTop = this.editor.getTopForLineNumber(this._commentThread.range.startLineNumber) - height / 2 + commentCoords.top - commentThreadCoords.top; } + this.editor.setScrollTop(scrollTop); + this._setFocus(focus); + } else { + this._goToThread(focus); } + } + + private _goToThread(focus: CommentWidgetFocus) { const rangeToReveal = this._commentThread.range ? new Range(this._commentThread.range.startLineNumber, this._commentThread.range.startColumn, this._commentThread.range.endLineNumber + 1, 1) : new Range(1, 1, 1, 1); this.editor.revealRangeInCenter(rangeToReveal); - if (focus === CommentWidgetFocus.Widget) { - this._commentThreadWidget.focus(); - } else if (focus === CommentWidgetFocus.Editor) { - this._commentThreadWidget.focusCommentEditor(); + this._setFocus(focus); + } + + public makeVisible(commentUniqueId?: number, focus: CommentWidgetFocus = CommentWidgetFocus.None) { + this._expandAndShowZoneWidget(); + + if (commentUniqueId !== undefined) { + this._goToComment(commentUniqueId, focus); } + this._goToThread(focus); } public getPendingComments(): { newComment: string | undefined; edits: { [key: number]: string } } { @@ -300,8 +319,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Collapsed; } - public expand() { + public expand(setActive?: boolean) { this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; + if (setActive) { + this.commentService.setActiveCommentAndThread(this.uniqueOwner, { thread: this._commentThread }); + } } public getGlyphPosition(): number { @@ -311,14 +333,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget return 0; } - toggleExpand() { - if (this._isExpanded) { - this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Collapsed; - } else { - this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; - } - } - async update(commentThread: languages.CommentThread) { if (this._commentThread !== commentThread) { this._commentThreadDisposables.forEach(disposable => disposable.dispose()); @@ -371,7 +385,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget // If this is a new comment thread awaiting user input then we need to reveal it. if (shouldReveal) { - this.reveal(); + this.makeVisible(); } this.bindCommentThreadListeners(); @@ -401,6 +415,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThreadDisposables.push(this._commentThread.onDidChangeCollapsibleState(state => { if (state === languages.CommentThreadCollapsibleState.Expanded && !this._isExpanded) { this.show(this.arrowPosition(this._commentThread.range), 2); + this._commentThreadWidget.ensureFocusIntoNewEditingComment(); return; } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts index 1c247dec..e9ec6e6a 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts @@ -13,21 +13,23 @@ import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCo import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; import { IAccessibleViewContentProvider, AccessibleViewProviderId, IAccessibleViewOptions, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { Disposable } from 'vs/base/common/lifecycle'; + export namespace CommentAccessibilityHelpNLS { export const intro = nls.localize('intro', "The editor contains commentable range(s). Some useful commands include:"); - export const tabFocus = nls.localize('introWidget', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled with the command Toggle Tab Key Moves Focus{0}", ``); + export const tabFocus = nls.localize('introWidget', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled with the command Toggle Tab Key Moves Focus{0}.", ``); export const commentCommands = nls.localize('commentCommands', "Some useful comment commands include:"); export const escape = nls.localize('escape', "- Dismiss Comment (Escape)"); - export const nextRange = nls.localize('next', "- Go to Next Commenting Range{0}", ``); - export const previousRange = nls.localize('previous', "- Go to Previous Commenting Range{0}", ``); - export const nextCommentThread = nls.localize('nextCommentThreadKb', "- Go to Next Comment Thread{0}", ``); - export const previousCommentThread = nls.localize('previousCommentThreadKb', "- Go to Previous Comment Thread{0}", ``); - export const addComment = nls.localize('addCommentNoKb', "- Add Comment on Current Selection{0}", ``); - export const submitComment = nls.localize('submitComment', "- Submit Comment{0}", ``); + export const nextRange = nls.localize('next', "- Go to Next Commenting Range{0}.", ``); + export const previousRange = nls.localize('previous', "- Go to Previous Commenting Range{0}.", ``); + export const nextCommentThread = nls.localize('nextCommentThreadKb', "- Go to Next Comment Thread{0}.", ``); + export const previousCommentThread = nls.localize('previousCommentThreadKb', "- Go to Previous Comment Thread{0}.", ``); + export const addComment = nls.localize('addCommentNoKb', "- Add Comment on Current Selection{0}.", ``); + export const submitComment = nls.localize('submitComment', "- Submit Comment{0}.", ``); } -export class CommentsAccessibilityHelpProvider implements IAccessibleViewContentProvider { +export class CommentsAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider { id = AccessibleViewProviderId.Comments; verbositySettingKey: AccessibilityVerbositySettingId = AccessibilityVerbositySettingId.Comments; options: IAccessibleViewOptions = { type: AccessibleViewType.Help }; @@ -48,5 +50,4 @@ export class CommentsAccessibilityHelp implements IAccessibleViewImplentation { getProvider(accessor: ServicesAccessor) { return accessor.get(IInstantiationService).createInstance(CommentsAccessibilityHelpProvider); } - dispose() { } } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts index 2fdac524..907cd886 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts @@ -6,82 +6,81 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { COMMENTS_VIEW_ID, CommentsMenus } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; -import { CommentsPanel, CONTEXT_KEY_HAS_COMMENTS } from 'vs/workbench/contrib/comments/browser/commentsView'; +import { CommentsPanel, CONTEXT_KEY_COMMENT_FOCUSED } from 'vs/workbench/contrib/comments/browser/commentsView'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; export class CommentsAccessibleView extends Disposable implements IAccessibleViewImplentation { readonly priority = 90; readonly name = 'comment'; - readonly when = CONTEXT_KEY_HAS_COMMENTS; + readonly when = CONTEXT_KEY_COMMENT_FOCUSED; readonly type = AccessibleViewType.View; getProvider(accessor: ServicesAccessor) { const contextKeyService = accessor.get(IContextKeyService); const viewsService = accessor.get(IViewsService); const menuService = accessor.get(IMenuService); const commentsView = viewsService.getActiveViewWithId(COMMENTS_VIEW_ID); - if (!commentsView) { + const focusedCommentNode = commentsView?.focusedCommentNode; + if (!commentsView || !focusedCommentNode) { return; } const menus = this._register(new CommentsMenus(menuService)); menus.setContextKeyService(contextKeyService); - function resolveProvider() { - if (!commentsView) { - return; - } + return new CommentsAccessibleContentProvider(commentsView, focusedCommentNode, menus); + } + constructor() { + super(); + } +} - const commentNode = commentsView.focusedCommentNode; - const content = commentsView.focusedCommentInfo?.toString(); - if (!commentNode || !content) { - return; +class CommentsAccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider { + constructor( + private readonly _commentsView: CommentsPanel, + private readonly _focusedCommentNode: any, + private readonly _menus: CommentsMenus, + ) { + super(); + } + readonly id = AccessibleViewProviderId.Comments; + readonly verbositySettingKey = AccessibilityVerbositySettingId.Comments; + readonly options = { type: AccessibleViewType.View }; + public actions = [...this._menus.getResourceContextActions(this._focusedCommentNode)].filter(i => i.enabled).map(action => { + return { + ...action, + run: () => { + this._commentsView.focus(); + action.run({ + thread: this._focusedCommentNode.thread, + $mid: MarshalledId.CommentThread, + commentControlHandle: this._focusedCommentNode.controllerHandle, + commentThreadHandle: this._focusedCommentNode.threadHandle, + }); } - const menuActions = [...menus.getResourceContextActions(commentNode)].filter(i => i.enabled); - const actions = menuActions.map(action => { - return { - ...action, - run: () => { - commentsView.focus(); - action.run({ - thread: commentNode.thread, - $mid: MarshalledId.CommentThread, - commentControlHandle: commentNode.controllerHandle, - commentThreadHandle: commentNode.threadHandle, - }); - } - }; - }); - return { - id: AccessibleViewProviderId.Notification, - provideContent: () => { - return content; - }, - onClose(): void { - commentsView.focus(); - }, - next(): void { - commentsView.focus(); - commentsView.focusNextNode(); - resolveProvider(); - }, - previous(): void { - commentsView.focus(); - commentsView.focusPreviousNode(); - resolveProvider(); - }, - verbositySettingKey: AccessibilityVerbositySettingId.Comments, - options: { type: AccessibleViewType.View }, - actions - }; + }; + }); + provideContent(): string { + const commentNode = this._commentsView.focusedCommentNode; + const content = this._commentsView.focusedCommentInfo?.toString(); + if (!commentNode || !content) { + throw new Error('Comment tree is focused but no comment is selected'); } - return resolveProvider(); + return content; } - constructor() { - super(); + onClose(): void { + this._commentsView.focus(); + } + provideNextContent(): string | undefined { + this._commentsView.focusNextNode(); + return this.provideContent(); + } + providePreviousContent(): string | undefined { + this._commentsView.focusPreviousNode(); + return this.provideContent(); } } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsController.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsController.ts index d679c223..34d258d0 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -46,6 +46,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { URI } from 'vs/base/common/uri'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { threadHasMeaningfulComments } from 'vs/workbench/contrib/comments/browser/commentsModel'; export const ID = 'editor.contrib.review'; @@ -430,6 +431,7 @@ export class CommentController implements IEditorContribution { private _commentingRangeSpaceReserved = false; private _commentingRangeAmountReserved = 0; private _computePromise: CancelablePromise> | null; + private _computeAndSetPromise: Promise | undefined; private _addInProgress!: boolean; private _emptyThreadsToAddQueue: [Range | undefined, IEditorMouseEvent | undefined][] = []; private _computeCommentingRangePromise!: CancelablePromise | null; @@ -645,10 +647,12 @@ export class CommentController implements IEditorContribution { return Promise.resolve([]); }); - return this._computePromise.then(async commentInfos => { + this._computeAndSetPromise = this._computePromise.then(async commentInfos => { await this.setComments(coalesce(commentInfos)); this._computePromise = null; }, error => console.log(error)); + this._computePromise.then(() => this._computeAndSetPromise = undefined); + return this._computeAndSetPromise; } private beginComputeCommentingRanges() { @@ -687,8 +691,8 @@ export class CommentController implements IEditorContribution { if (commentThreadWidget.length === 1) { commentThreadWidget[0].reveal(commentUniqueId, focus); } else if (fetchOnceIfNotExist) { - if (this._computePromise) { - this._computePromise.then(_ => { + if (this._computeAndSetPromise) { + this._computeAndSetPromise.then(_ => { this.revealCommentThread(threadId, commentUniqueId, false, focus); }); } else { @@ -728,7 +732,7 @@ export class CommentController implements IEditorContribution { return; } - const after = this.editor.getSelection().getEndPosition(); + const after = reverse ? this.editor.getSelection().getStartPosition() : this.editor.getSelection().getEndPosition(); const sortedWidgets = this._commentWidgets.sort((a, b) => { if (reverse) { const temp = a; @@ -1012,7 +1016,7 @@ export class CommentController implements IEditorContribution { } private async openCommentsView(thread: languages.CommentThread) { - if (thread.comments && (thread.comments.length > 0)) { + if (thread.comments && (thread.comments.length > 0) && threadHasMeaningfulComments(thread)) { const openViewState = this.configurationService.getValue(COMMENTS_SECTION).openView; if (openViewState === 'file') { return this.viewsService.openView(COMMENTS_VIEW_ID); @@ -1094,7 +1098,7 @@ export class CommentController implements IEditorContribution { const existingCommentsAtLine = this._commentWidgets.filter(widget => widget.getGlyphPosition() === (commentRange ? commentRange.endLineNumber : 0)); if (existingCommentsAtLine.length) { const allExpanded = existingCommentsAtLine.every(widget => widget.expanded); - existingCommentsAtLine.forEach(allExpanded ? widget => widget.collapse() : widget => widget.expand()); + existingCommentsAtLine.forEach(allExpanded ? widget => widget.collapse() : widget => widget.expand(true)); this.processNextThreadToAdd(); return; } else { diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsModel.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsModel.ts index d0701d5f..624db1d5 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsModel.ts @@ -9,6 +9,12 @@ import { CommentThread } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { Disposable } from 'vs/base/common/lifecycle'; +import { isMarkdownString } from 'vs/base/common/htmlContent'; + +export function threadHasMeaningfulComments(thread: CommentThread): boolean { + return !!thread.comments && !!thread.comments.length && thread.comments.some(comment => isMarkdownString(comment.body) ? comment.body.value.length > 0 : comment.body.length > 0); + +} export interface ICommentsModel { hasCommentThreads(): boolean; @@ -38,9 +44,6 @@ export class CommentsModel extends Disposable implements ICommentsModel { return resource; }).flat(); }).flat(); - this._resourceCommentThreads.sort((a, b) => { - return a.resource.toString() > b.resource.toString() ? 1 : -1; - }); } public setCommentThreads(uniqueOwner: string, owner: string, ownerLabel: string, commentThreads: CommentThread[]): void { @@ -116,7 +119,14 @@ export class CommentsModel extends Disposable implements ICommentsModel { } public hasCommentThreads(): boolean { - return !!this._resourceCommentThreads.length; + // There's a resource with at least one thread + return !!this._resourceCommentThreads.length && this._resourceCommentThreads.some(resource => { + // At least one of the threads in the the resource has comments + return (resource.commentThreads.length > 0) && resource.commentThreads.some(thread => { + // At least one of the comments in the thread is not empty + return threadHasMeaningfulComments(thread.thread); + }); + }); } public getMessage(): string { diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 6caf357f..92b28d1a 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -35,7 +35,7 @@ import { CommentsModel } from 'vs/workbench/contrib/comments/browser/commentsMod import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -76,7 +76,7 @@ interface ICommentThreadTemplateData { disposables: IDisposable[]; } -class CommentsModelVirualDelegate implements IListVirtualDelegate { +class CommentsModelVirtualDelegate implements IListVirtualDelegate { private static readonly RESOURCE_ID = 'resource-with-comments'; private static readonly COMMENT_ID = 'comment-node'; @@ -90,10 +90,10 @@ class CommentsModelVirualDelegate implements IListVirtualDelegate textDescription.textContent = image.alt ? nls.localize('imageWithLabel', "Image: {0}", image.alt) : nls.localize('image', "Image"); image.parentNode!.replaceChild(textDescription, image); } + while ((renderedComment.element.children.length > 1) && (renderedComment.element.firstElementChild?.tagName === 'HR')) { + renderedComment.element.removeChild(renderedComment.element.firstElementChild); + } return renderedComment; } @@ -303,7 +305,7 @@ export class CommentNodeRenderer implements IListRenderer const renderedComment = this.getRenderedComment(originalComment.comment.body, disposables); templateData.disposables.push(renderedComment); templateData.threadMetadata.commentPreview.appendChild(renderedComment.element.firstElementChild ?? renderedComment.element); - templateData.disposables.push(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), templateData.threadMetadata.commentPreview, renderedComment.element.textContent ?? '')); + templateData.disposables.push(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), templateData.threadMetadata.commentPreview, renderedComment.element.textContent ?? '')); } if (node.element.range) { @@ -451,7 +453,7 @@ export class CommentsList extends WorkbenchObjectTree('commentsView.hasComments', false); export const CONTEXT_KEY_SOME_COMMENTS_EXPANDED = new RawContextKey('commentsView.someCommentsExpanded', false); +export const CONTEXT_KEY_COMMENT_FOCUSED = new RawContextKey('commentsView.commentFocused', false); const VIEW_STORAGE_ID = 'commentsViewState'; -function createResourceCommentsIterator(model: ICommentsModel): Iterable> { - return Iterable.map(model.resourceCommentThreads, m => { - const CommentNodeIt = Iterable.from(m.commentThreads); - const children = Iterable.map(CommentNodeIt, r => ({ element: r })); +type CommentsTreeNode = CommentsModel | ResourceWithCommentThreads | CommentNode; - return { element: m, children }; - }); +function createResourceCommentsIterator(model: ICommentsModel): Iterable> { + const result: ITreeElement[] = []; + + for (const m of model.resourceCommentThreads) { + const children = []; + for (const r of m.commentThreads) { + if (threadHasMeaningfulComments(r.thread)) { + children.push({ element: r }); + } + } + if (children.length > 0) { + result.push({ element: m, children }); + } + } + return result; } export class CommentsPanel extends FilterViewPane implements ICommentsView { @@ -59,6 +70,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { private totalComments: number = 0; private readonly hasCommentsContextKey: IContextKey; private readonly someCommentsExpandedContextKey: IContextKey; + private readonly commentsFocusedContextKey: IContextKey; private readonly filter: Filter; readonly filters: CommentsFilters; @@ -136,7 +148,8 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @IPathService private readonly pathService: IPathService ) { const stateMemento = new Memento(VIEW_STORAGE_ID, storageService); const viewState = stateMemento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); @@ -152,12 +165,14 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); this.hasCommentsContextKey = CONTEXT_KEY_HAS_COMMENTS.bindTo(contextKeyService); this.someCommentsExpandedContextKey = CONTEXT_KEY_SOME_COMMENTS_EXPANDED.bindTo(contextKeyService); + this.commentsFocusedContextKey = CONTEXT_KEY_COMMENT_FOCUSED.bindTo(contextKeyService); this.stateMemento = stateMemento; this.viewState = viewState; this.filters = this._register(new CommentsFilters({ showResolved: this.viewState['showResolved'] !== false, showUnresolved: this.viewState['showUnresolved'] !== false, + sortBy: this.viewState['sortBy'] ?? CommentsSortOrder.ResourceAscending, }, this.contextKeyService)); this.filter = new Filter(new FilterOptions(this.filterWidget.getFilterText(), this.filters.showResolved, this.filters.showUnresolved)); @@ -165,6 +180,9 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { if (event.showResolved || event.showUnresolved) { this.updateFilter(); } + if (event.sortBy) { + this.refresh(); + } })); this._register(this.filterWidget.onDidChangeFilterText(() => this.updateFilter())); } @@ -174,6 +192,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { this.viewState['filterHistory'] = this.filterWidget.getHistory(); this.viewState['showResolved'] = this.filters.showResolved; this.viewState['showUnresolved'] = this.filters.showUnresolved; + this.viewState['sortBy'] = this.filters.sortBy; this.stateMemento.saveMemento(); super.saveState(); } @@ -267,10 +286,10 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { } } - private async renderComments(): Promise { + private renderComments(): void { this.treeContainer.classList.toggle('hidden', !this.commentService.commentsModel.hasCommentThreads()); this.renderMessage(); - await this.tree?.setChildren(null, createResourceCommentsIterator(this.commentService.commentsModel)); + this.tree?.setChildren(null, createResourceCommentsIterator(this.commentService.commentsModel)); } public collapseAll() { @@ -388,8 +407,30 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { overrideStyles: this.getLocationBasedColors().listOverrideStyles, selectionNavigation: true, filter: this.filter, + sorter: { + compare: (a: CommentsTreeNode, b: CommentsTreeNode) => { + if (a instanceof CommentsModel || b instanceof CommentsModel) { + return 0; + } + if (this.filters.sortBy === CommentsSortOrder.UpdatedAtDescending) { + return a.lastUpdatedAt > b.lastUpdatedAt ? -1 : 1; + } else if (this.filters.sortBy === CommentsSortOrder.ResourceAscending) { + if (a instanceof ResourceWithCommentThreads && b instanceof ResourceWithCommentThreads) { + const workspaceScheme = this.pathService.defaultUriScheme; + if ((a.resource.scheme !== b.resource.scheme) && (a.resource.scheme === workspaceScheme || b.resource.scheme === workspaceScheme)) { + // Workspace scheme should always come first + return b.resource.scheme === workspaceScheme ? 1 : -1; + } + return a.resource.toString() > b.resource.toString() ? 1 : -1; + } else if (a instanceof CommentNode && b instanceof CommentNode && a.thread.range && b.thread.range) { + return a.thread.range?.startLineNumber > b.thread.range?.startLineNumber ? 1 : -1; + } + } + return 0; + }, + }, keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: (item: CommentsModel | ResourceWithCommentThreads | CommentNode) => { + getKeyboardNavigationLabel: (item: CommentsTreeNode) => { return undefined; } }, @@ -423,6 +464,8 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { this._register(this.tree.onDidChangeCollapseState(() => { this.updateSomeCommentsExpanded(); })); + this._register(this.tree.onDidFocus(() => this.commentsFocusedContextKey.set(true))); + this._register(this.tree.onDidBlur(() => this.commentsFocusedContextKey.set(false))); } private openFile(element: any, pinned?: boolean, preserveFocus?: boolean, sideBySide?: boolean): void { @@ -444,11 +487,8 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { } if (this.isVisible()) { this.hasCommentsContextKey.set(this.commentService.commentsModel.hasCommentThreads()); - - this.treeContainer.classList.toggle('hidden', !this.commentService.commentsModel.hasCommentThreads()); this.cachedFilterStats = undefined; - this.renderMessage(); - this.tree?.setChildren(null, createResourceCommentsIterator(this.commentService.commentsModel)); + this.renderComments(); if (this.tree.getSelection().length === 0 && this.commentService.commentsModel.hasCommentThreads()) { const firstComment = this.commentService.commentsModel.resourceCommentThreads[0].commentThreads[0]; diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts index b850d68a..e2494534 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts @@ -7,27 +7,37 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { CommentsViewFilterFocusContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments'; -import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { viewFilterSubmenu } from 'vs/workbench/browser/parts/views/viewFilter'; +import { Codicon } from 'vs/base/common/codicons'; + +export const enum CommentsSortOrder { + ResourceAscending = 'resourceAscending', + UpdatedAtDescending = 'updatedAtDescending', +} + const CONTEXT_KEY_SHOW_RESOLVED = new RawContextKey('commentsView.showResolvedFilter', true); const CONTEXT_KEY_SHOW_UNRESOLVED = new RawContextKey('commentsView.showUnResolvedFilter', true); +const CONTEXT_KEY_SORT_BY = new RawContextKey('commentsView.sortBy', CommentsSortOrder.ResourceAscending); export interface CommentsFiltersChangeEvent { showResolved?: boolean; showUnresolved?: boolean; + sortBy?: CommentsSortOrder; } interface CommentsFiltersOptions { showResolved: boolean; showUnresolved: boolean; + sortBy: CommentsSortOrder; } export class CommentsFilters extends Disposable { @@ -39,6 +49,7 @@ export class CommentsFilters extends Disposable { super(); this._showResolved.set(options.showResolved); this._showUnresolved.set(options.showUnresolved); + this._sortBy.set(options.sortBy); } private readonly _showUnresolved = CONTEXT_KEY_SHOW_UNRESOLVED.bindTo(this.contextKeyService); @@ -63,6 +74,16 @@ export class CommentsFilters extends Disposable { } } + private _sortBy: IContextKey = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService); + get sortBy(): CommentsSortOrder { + return this._sortBy.get() ?? CommentsSortOrder.ResourceAscending; + } + set sortBy(sortBy: CommentsSortOrder) { + if (this._sortBy.get() !== sortBy) { + this._sortBy.set(sortBy); + this._onDidChange.fire({ sortBy }); + } + } } registerAction2(class extends ViewAction { @@ -168,3 +189,64 @@ registerAction2(class extends ViewAction { view.filters.showResolved = !view.filters.showResolved; } }); + +const commentSortSubmenu = new MenuId('submenu.filter.commentSort'); +MenuRegistry.appendMenuItem(viewFilterSubmenu, { + submenu: commentSortSubmenu, + title: localize('comment sorts', "Sort By"), + group: '2_sort', + icon: Codicon.history, + when: ContextKeyExpr.equals('view', COMMENTS_VIEW_ID), +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleSortByUpdatedAt`, + title: localize('toggle sorting by updated at', "Updated Time"), + category: localize('comments', "Comments"), + icon: Codicon.history, + viewId: COMMENTS_VIEW_ID, + toggled: { + condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.UpdatedAtDescending), + title: localize('sorting by updated at', "Updated Time"), + }, + menu: { + id: commentSortSubmenu, + group: 'navigation', + order: 1, + isHiddenByDefault: false, + }, + }); + } + + async runInView(serviceAccessor: ServicesAccessor, view: ICommentsView): Promise { + view.filters.sortBy = CommentsSortOrder.UpdatedAtDescending; + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleSortByResource`, + title: localize('toggle sorting by resource', "Position in File"), + category: localize('comments', "Comments"), + icon: Codicon.history, + viewId: COMMENTS_VIEW_ID, + toggled: { + condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.ResourceAscending), + title: localize('sorting by position in file', "Position in File"), + }, + menu: { + id: commentSortSubmenu, + group: 'navigation', + order: 0, + isHiddenByDefault: false, + }, + }); + } + + async runInView(serviceAccessor: ServicesAccessor, view: ICommentsView): Promise { + view.filters.sortBy = CommentsSortOrder.ResourceAscending; + } +}); diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/media/panel.css b/patched-vscode/src/vs/workbench/contrib/comments/browser/media/panel.css index 938c658f..8527341b 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/media/panel.css +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/media/panel.css @@ -90,11 +90,14 @@ .comments-panel .comments-panel-container .tree-container .comment-thread-container .text * { margin: 0; text-overflow: ellipsis; - max-width: 500px; overflow: hidden; padding-right: 5px; } +.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-metadata .text * { + max-width: 700px; +} + .comments-panel .comments-panel-container .tree-container .comment-thread-container .range { opacity: 0.8; } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/media/review.css b/patched-vscode/src/vs/workbench/contrib/comments/browser/media/review.css index 0ee64b83..f643c685 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/media/review.css @@ -244,11 +244,6 @@ margin-top: 0; } -.review-widget .body .comment-body code { - border-radius: 3px; - padding: 0 0.4em; -} - .review-widget .body .comment-body span { white-space: pre; } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index bad0ff2c..15975ce1 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -31,12 +31,13 @@ import { clamp } from 'vs/base/common/numbers'; import { CopyPasteController } from 'vs/editor/contrib/dropOrPasteInto/browser/copyPasteController'; import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; import { DropIntoEditorController } from 'vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; import { LinkDetector } from 'vs/editor/contrib/links/browser/links'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { MenuId } from 'vs/platform/actions/common/actions'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; +import { MarginHoverController } from 'vs/editor/contrib/hover/browser/marginHoverController'; export const ctxCommentEditorFocused = new RawContextKey('commentEditorFocused', false); export const MIN_EDITOR_HEIGHT = 5 * 18; @@ -78,7 +79,8 @@ export class SimpleCommentEditor extends CodeEditorWidget { DropIntoEditorController.ID, LinkDetector.ID, MessageController.ID, - HoverController.ID, + ContentHoverController.ID, + MarginHoverController.ID, SelectionClipboardContributionID, InlineCompletionsController.ID, CodeActionController.ID, @@ -137,6 +139,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { autoClosingBrackets: configurationService.getValue('editor.autoClosingBrackets'), quickSuggestions: false, accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), + fontFamily: configurationService.getValue('editor.fontFamily'), }; } } @@ -144,7 +147,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { export function calculateEditorHeight(parentEditor: LayoutableEditor, editor: ICodeEditor, currentHeight: number): number { const layoutInfo = editor.getLayoutInfo(); const lineHeight = editor.getOption(EditorOption.lineHeight); - const contentHeight = (editor._getViewModel()?.getLineCount()! * lineHeight) ?? editor.getContentHeight(); // Can't just call getContentHeight() because it returns an incorrect, large, value when the editor is first created. + const contentHeight = (editor._getViewModel()?.getLineCount()! * lineHeight); // Can't just call getContentHeight() because it returns an incorrect, large, value when the editor is first created. if ((contentHeight > layoutInfo.height) || (contentHeight < layoutInfo.height && currentHeight > MIN_EDITOR_HEIGHT)) { const linesToAdd = Math.ceil((contentHeight - layoutInfo.height) / lineHeight); diff --git a/patched-vscode/src/vs/workbench/contrib/comments/browser/timestamp.ts b/patched-vscode/src/vs/workbench/contrib/comments/browser/timestamp.ts index 47faa02f..583ccaa7 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/browser/timestamp.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/browser/timestamp.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { fromNow } from 'vs/base/common/date'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -18,7 +18,7 @@ export class TimestampWidget extends Disposable { private _timestamp: Date | undefined; private _useRelativeTime: boolean; - private hover: IUpdatableHover; + private hover: IManagedHover; constructor( private configurationService: IConfigurationService, @@ -30,7 +30,7 @@ export class TimestampWidget extends Disposable { this._date = dom.append(container, dom.$('span.timestamp')); this._date.style.display = 'none'; this._useRelativeTime = this.useRelativeTimeSetting; - this.hover = this._register(hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this._date, '')); + this.hover = this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this._date, '')); this.setTimestamp(timeStamp); } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/common/commentModel.ts b/patched-vscode/src/vs/workbench/contrib/comments/common/commentModel.ts index fbf25f6c..716985b3 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/common/commentModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -42,6 +42,23 @@ export class CommentNode { hasReply(): boolean { return this.replies && this.replies.length !== 0; } + + private _lastUpdatedAt: string | undefined; + + get lastUpdatedAt(): string { + if (this._lastUpdatedAt === undefined) { + let updatedAt = this.comment.timestamp || ''; + if (this.replies.length) { + const reply = this.replies[this.replies.length - 1]; + const replyUpdatedAt = reply.lastUpdatedAt; + if (replyUpdatedAt > updatedAt) { + updatedAt = replyUpdatedAt; + } + } + this._lastUpdatedAt = updatedAt; + } + return this._lastUpdatedAt; + } } export class ResourceWithCommentThreads { @@ -71,5 +88,25 @@ export class ResourceWithCommentThreads { return commentNodes[0]; } + + private _lastUpdatedAt: string | undefined; + + get lastUpdatedAt() { + if (this._lastUpdatedAt === undefined) { + let updatedAt = ''; + // Return result without cahcing as we expect data to arrive later + if (!this.commentThreads.length) { + return updatedAt; + } + for (const thread of this.commentThreads) { + const threadUpdatedAt = thread.lastUpdatedAt; + if (threadUpdatedAt && threadUpdatedAt > updatedAt) { + updatedAt = threadUpdatedAt; + } + } + this._lastUpdatedAt = updatedAt; + } + return this._lastUpdatedAt; + } } diff --git a/patched-vscode/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts b/patched-vscode/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts index 84e77afe..9c7d9e80 100644 --- a/patched-vscode/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/comments/test/browser/commentsView.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IRange, Range } from 'vs/editor/common/core/range'; import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; diff --git a/patched-vscode/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts b/patched-vscode/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts index e903b24d..3f607d92 100644 --- a/patched-vscode/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/contextmenu/browser/contextmenu.contribution.ts @@ -3,24 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -class ContextMenuContribution implements IWorkbenchContribution { - - private readonly disposables = new DisposableStore(); +class ContextMenuContribution extends Disposable implements IWorkbenchContribution { constructor( @ILayoutService layoutService: ILayoutService, @IContextMenuService contextMenuService: IContextMenuService ) { + super(); + const update = (visible: boolean) => layoutService.activeContainer.classList.toggle('context-menu-visible', visible); - contextMenuService.onDidShowContextMenu(() => update(true), null, this.disposables); - contextMenuService.onDidHideContextMenu(() => update(false), null, this.disposables); + this._register(contextMenuService.onDidShowContextMenu(() => update(true))); + this._register(contextMenuService.onDidHideContextMenu(() => update(false))); } } diff --git a/patched-vscode/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/patched-vscode/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 04b79c58..e2fa81ae 100644 --- a/patched-vscode/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/patched-vscode/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -132,7 +132,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ priority: contributedEditor.priority, }, { - singlePerResource: () => !this.getCustomEditorCapabilities(contributedEditor.id)?.supportsMultipleEditorsPerDocument ?? true + singlePerResource: () => !(this.getCustomEditorCapabilities(contributedEditor.id)?.supportsMultipleEditorsPerDocument ?? false) }, { createEditorInput: ({ resource }, group) => { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 35464240..8543d1ac 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -22,7 +22,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IHoverService } from 'vs/platform/hover/browser/hover'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { COPY_EVALUATE_PATH_ID, COPY_VALUE_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; -import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { DebugLinkHoverBehavior, DebugLinkHoverBehaviorTypeData, LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { IDebugService, IExpression, IExpressionValue } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, ExpressionContainer, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; @@ -37,8 +37,7 @@ export interface IRenderValueOptions { showChanged?: boolean; maxValueLength?: number; /** If set, a hover will be shown on the element. Requires a disposable store for usage. */ - hover?: DisposableStore | { - store: DisposableStore; + hover?: false | { commands: { id: string; args: unknown[] }[]; commandService: ICommandService; }; @@ -49,6 +48,7 @@ export interface IRenderValueOptions { export interface IVariableTemplateData { expression: HTMLElement; name: HTMLElement; + type: HTMLElement; value: HTMLElement; label: HighlightedLabel; lazyButton: HTMLElement; @@ -61,7 +61,7 @@ export function renderViewTree(container: HTMLElement): HTMLElement { return treeContainer; } -export function renderExpressionValue(expressionOrValue: IExpressionValue | string, container: HTMLElement, options: IRenderValueOptions, hoverService: IHoverService): void { +export function renderExpressionValue(store: DisposableStore, expressionOrValue: IExpressionValue | string, container: HTMLElement, options: IRenderValueOptions, hoverService: IHoverService): void { let value = typeof expressionOrValue === 'string' ? expressionOrValue : expressionOrValue.value; // remove stale classes @@ -99,17 +99,22 @@ export function renderExpressionValue(expressionOrValue: IExpressionValue | stri value = ''; } - if (options.linkDetector) { + const session = (expressionOrValue instanceof ExpressionContainer) ? expressionOrValue.getSession() : undefined; + // Only use hovers for links if thre's not going to be a hover for the value. + const hoverBehavior: DebugLinkHoverBehaviorTypeData = options.hover === false ? { type: DebugLinkHoverBehavior.Rich, store } : { type: DebugLinkHoverBehavior.None }; + if (expressionOrValue instanceof ExpressionContainer && expressionOrValue.valueLocationReference !== undefined && session && options.linkDetector) { container.textContent = ''; - const session = (expressionOrValue instanceof ExpressionContainer) ? expressionOrValue.getSession() : undefined; - container.appendChild(options.linkDetector.linkify(value, false, session ? session.root : undefined, true)); + container.appendChild(options.linkDetector.linkifyLocation(value, expressionOrValue.valueLocationReference, session, hoverBehavior)); + } else if (options.linkDetector) { + container.textContent = ''; + container.appendChild(options.linkDetector.linkify(value, false, session ? session.root : undefined, true, hoverBehavior)); } else { container.textContent = value; } - if (options.hover) { - const { store, commands, commandService } = options.hover instanceof DisposableStore ? { store: options.hover, commands: [], commandService: undefined } : options.hover; - store.add(hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), container, () => { + if (options.hover !== false) { + const { commands = [], commandService } = options.hover || {}; + store.add(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), container, () => { const container = dom.$('div'); const markdownHoverElement = dom.$('div.hover-row'); const hoverContentsElement = dom.append(markdownHoverElement, dom.$('div.hover-contents')); @@ -130,13 +135,20 @@ export function renderExpressionValue(expressionOrValue: IExpressionValue | stri } } -export function renderVariable(store: DisposableStore, commandService: ICommandService, hoverService: IHoverService, variable: Variable, data: IVariableTemplateData, showChanged: boolean, highlights: IHighlight[], linkDetector?: LinkDetector): void { +export function renderVariable(store: DisposableStore, commandService: ICommandService, hoverService: IHoverService, variable: Variable, data: IVariableTemplateData, showChanged: boolean, highlights: IHighlight[], linkDetector?: LinkDetector, displayType?: boolean): void { if (variable.available) { + data.type.textContent = ''; let text = variable.name; if (variable.value && typeof variable.name === 'string') { - text += ':'; + if (variable.type && displayType) { + text += ': '; + data.type.textContent = variable.type + ' ='; + } else { + text += ' ='; + } } - data.label.set(text, highlights, variable.type ? variable.type : variable.name); + + data.label.set(text, highlights, variable.type && !displayType ? variable.type : variable.name); data.name.classList.toggle('virtual', variable.presentationHint?.kind === 'virtual'); data.name.classList.toggle('internal', variable.presentationHint?.visibility === 'internal'); } else if (variable.value && typeof variable.name === 'string' && variable.name) { @@ -151,10 +163,10 @@ export function renderVariable(store: DisposableStore, commandService: ICommandS commands.push({ id: COPY_EVALUATE_PATH_ID, args: [{ variable }] }); } - renderExpressionValue(variable, data.value, { + renderExpressionValue(store, variable, data.value, { showChanged, maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET, - hover: { store, commands, commandService }, + hover: { commands, commandService }, colorize: true, linkDetector }, hoverService); @@ -171,6 +183,7 @@ export interface IInputBoxOptions { export interface IExpressionTemplateData { expression: HTMLElement; name: HTMLSpanElement; + type: HTMLSpanElement; value: HTMLSpanElement; inputBoxContainer: HTMLElement; actionBar?: ActionBar; @@ -228,7 +241,10 @@ export abstract class AbstractExpressionsRenderer implements IT const name = dom.append(expression, $('span.name')); const lazyButton = dom.append(expression, $('span.lazy-button')); lazyButton.classList.add(...ThemeIcon.asClassNameArray(Codicon.eye)); - templateDisposable.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), lazyButton, localize('debug.lazyButton.tooltip', "Click to expand"))); + + templateDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), lazyButton, localize('debug.lazyButton.tooltip', "Click to expand"))); + const type = dom.append(expression, $('span.type')); + const value = dom.append(expression, $('span.value')); const label = templateDisposable.add(new HighlightedLabel(name)); @@ -241,7 +257,7 @@ export abstract class AbstractExpressionsRenderer implements IT actionBar = templateDisposable.add(new ActionBar(expression)); } - const template: IExpressionTemplateData = { expression, name, value, label, inputBoxContainer, actionBar, elementDisposable: new DisposableStore(), templateDisposable, lazyButton, currentElement: undefined }; + const template: IExpressionTemplateData = { expression, name, type, value, label, inputBoxContainer, actionBar, elementDisposable: new DisposableStore(), templateDisposable, lazyButton, currentElement: undefined }; templateDisposable.add(dom.addDisposableListener(lazyButton, dom.EventType.CLICK, () => { if (template.currentElement) { @@ -255,7 +271,6 @@ export abstract class AbstractExpressionsRenderer implements IT public abstract renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void; protected renderExpressionElement(element: IExpression, node: ITreeNode, data: IExpressionTemplateData): void { - data.elementDisposable.clear(); data.currentElement = element; this.renderExpression(node.element, data, createMatches(node.filterData)); if (data.actionBar) { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 1b53d400..d10b36ff 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -504,7 +504,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi if (!clz) { continue; } - const hasSomeActionableCodicon = !(clz.includes('codicon-') || clz.startsWith('coverage-deco-')) || clz.includes('codicon-testing-') || clz.includes('codicon-merge-') || clz.includes('codicon-arrow-') || clz.includes('codicon-loading') || clz.includes('codicon-fold') || clz.includes('codicon-inline-chat'); + const hasSomeActionableCodicon = !(clz.includes('codicon-') || clz.startsWith('coverage-deco-')) || clz.includes('codicon-testing-') || clz.includes('codicon-merge-') || clz.includes('codicon-arrow-') || clz.includes('codicon-loading') || clz.includes('codicon-fold') || clz.includes('codicon-gutter-lightbulb') || clz.includes('codicon-lightbulb-sparkle'); if (hasSomeActionableCodicon) { return false; } @@ -811,66 +811,71 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { } registerThemingParticipant((theme, collector) => { + const scope = '.monaco-editor .glyph-margin-widgets, .monaco-workbench .debug-breakpoints, .monaco-workbench .disassembly-view, .monaco-editor .contentWidgets'; const debugIconBreakpointColor = theme.getColor(debugIconBreakpointForeground); if (debugIconBreakpointColor) { - collector.addRule(` - ${icons.allBreakpoints.map(b => `.monaco-workbench ${ThemeIcon.asCSSSelector(b.regular)}`).join(',\n ')}, - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointUnsupported)}, - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugBreakpointHint)}:not([class*='codicon-debug-breakpoint']):not([class*='codicon-debug-stackframe']), - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.breakpoint.regular)}${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)}::after, - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.breakpoint.regular)}${ThemeIcon.asCSSSelector(icons.debugStackframe)}::after { - color: ${debugIconBreakpointColor} !important; - } - `); + collector.addRule(`${scope} { + ${icons.allBreakpoints.map(b => `${ThemeIcon.asCSSSelector(b.regular)}`).join(',\n ')}, + ${ThemeIcon.asCSSSelector(icons.debugBreakpointUnsupported)}, + ${ThemeIcon.asCSSSelector(icons.debugBreakpointHint)}:not([class*='codicon-debug-breakpoint']):not([class*='codicon-debug-stackframe']), + ${ThemeIcon.asCSSSelector(icons.breakpoint.regular)}${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)}::after, + ${ThemeIcon.asCSSSelector(icons.breakpoint.regular)}${ThemeIcon.asCSSSelector(icons.debugStackframe)}::after { + color: ${debugIconBreakpointColor} !important; + } + }`); - collector.addRule(` - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.breakpoint.pending)} { - color: ${debugIconBreakpointColor} !important; - font-size: 12px !important; - } - `); + collector.addRule(`${scope} { + ${ThemeIcon.asCSSSelector(icons.breakpoint.pending)} { + color: ${debugIconBreakpointColor} !important; + font-size: 12px !important; + } + }`); } const debugIconBreakpointDisabledColor = theme.getColor(debugIconBreakpointDisabledForeground); if (debugIconBreakpointDisabledColor) { - collector.addRule(` - ${icons.allBreakpoints.map(b => `.monaco-workbench ${ThemeIcon.asCSSSelector(b.disabled)}`).join(',\n ')} { - color: ${debugIconBreakpointDisabledColor}; - } - `); + collector.addRule(`${scope} { + ${icons.allBreakpoints.map(b => ThemeIcon.asCSSSelector(b.disabled)).join(',\n ')} { + color: ${debugIconBreakpointDisabledColor}; + } + }`); } const debugIconBreakpointUnverifiedColor = theme.getColor(debugIconBreakpointUnverifiedForeground); if (debugIconBreakpointUnverifiedColor) { - collector.addRule(` - ${icons.allBreakpoints.map(b => `.monaco-workbench ${ThemeIcon.asCSSSelector(b.unverified)}`).join(',\n ')} { - color: ${debugIconBreakpointUnverifiedColor}; - } - `); + collector.addRule(`${scope} { + ${icons.allBreakpoints.map(b => ThemeIcon.asCSSSelector(b.unverified)).join(',\n ')} { + color: ${debugIconBreakpointUnverifiedColor}; + } + }`); } const debugIconBreakpointCurrentStackframeForegroundColor = theme.getColor(debugIconBreakpointCurrentStackframeForeground); if (debugIconBreakpointCurrentStackframeForegroundColor) { collector.addRule(` - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStackframe)}, .monaco-editor .debug-top-stack-frame-column { color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important; } + ${scope} { + ${ThemeIcon.asCSSSelector(icons.debugStackframe)} { + color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important; + } + } `); } const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeForeground); if (debugIconBreakpointStackframeFocusedColor) { - collector.addRule(` - .monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)} { - color: ${debugIconBreakpointStackframeFocusedColor} !important; - } - `); + collector.addRule(`${scope} { + ${ThemeIcon.asCSSSelector(icons.debugStackframeFocused)} { + color: ${debugIconBreakpointStackframeFocusedColor} !important; + } + }`); } }); -export const debugIconBreakpointForeground = registerColor('debugIcon.breakpointForeground', { dark: '#E51400', light: '#E51400', hcDark: '#E51400', hcLight: '#E51400' }, nls.localize('debugIcon.breakpointForeground', 'Icon color for breakpoints.')); -const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpointDisabledForeground', { dark: '#848484', light: '#848484', hcDark: '#848484', hcLight: '#848484' }, nls.localize('debugIcon.breakpointDisabledForeground', 'Icon color for disabled breakpoints.')); -const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', { dark: '#848484', light: '#848484', hcDark: '#848484', hcLight: '#848484' }, nls.localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.')); +export const debugIconBreakpointForeground = registerColor('debugIcon.breakpointForeground', '#E51400', nls.localize('debugIcon.breakpointForeground', 'Icon color for breakpoints.')); +const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpointDisabledForeground', '#848484', nls.localize('debugIcon.breakpointDisabledForeground', 'Icon color for disabled breakpoints.')); +const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', '#848484', nls.localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.')); const debugIconBreakpointCurrentStackframeForeground = registerColor('debugIcon.breakpointCurrentStackframeForeground', { dark: '#FFCC00', light: '#BE8700', hcDark: '#FFCC00', hcLight: '#BE8700' }, nls.localize('debugIcon.breakpointCurrentStackframeForeground', 'Icon color for the current breakpoint stack frame.')); -const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#89D185', light: '#89D185', hcDark: '#89D185', hcLight: '#89D185' }, nls.localize('debugIcon.breakpointStackframeForeground', 'Icon color for all breakpoint stack frames.')); +const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', '#89D185', nls.localize('debugIcon.breakpointStackframeForeground', 'Icon color for all breakpoint stack frames.')); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 59a3a4dd..c002c378 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -6,6 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Button } from 'vs/base/browser/ui/button/button'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -34,6 +35,7 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -109,6 +111,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi @IKeybindingService private readonly keybindingService: IKeybindingService, @ILabelService private readonly labelService: ILabelService, @ITextModelService private readonly textModelService: ITextModelService, + @IHoverService private readonly hoverService: IHoverService ) { super(editor, { showFrame: true, showArrow: false, frameWidth: 1, isAccessible: true }); @@ -204,12 +207,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi protected _fillContainer(container: HTMLElement): void { this.setCssClass('breakpoint-widget'); - const selectBox = new SelectBox([ + const selectBox = new SelectBox([ { text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, { text: nls.localize('triggeredBy', "Wait for Breakpoint") }, - ], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); + ] satisfies ISelectOptionItem[], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); this.selectContainer = $('.breakpoint-select-container'); selectBox.render(dom.append(container, this.selectContainer)); selectBox.onDidSelect(e => { @@ -221,6 +224,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.createModesInput(container); this.inputContainer = $('.inputContainer'); + this.toDispose.push(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.inputContainer, this.placeholder)); this.createBreakpointInput(dom.append(container, this.inputContainer)); this.input.getModel().setValue(this.getInputValue(this.breakpoint)); @@ -264,7 +268,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } private createTriggerBreakpointInput(container: HTMLElement) { - const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); + const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint && !bp.logMessage); const breakpointOptions: ISelectOptionItem[] = [ { text: nls.localize('noTriggerByBreakpoint', 'None'), isDisabled: true }, ...breakpoints.map(bp => ({ @@ -346,7 +350,10 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.toDispose.push(scopedContextKeyService); const scopedInstatiationService = this.instantiationService.createChild(new ServiceCollection( - [IContextKeyService, scopedContextKeyService], [IPrivateBreakpointWidgetService, this])); + [IContextKeyService, scopedContextKeyService], + [IPrivateBreakpointWidgetService, this] + )); + this.toDispose.push(scopedInstatiationService); const options = this.createEditorOptions(); const codeEditorWidgetOptions = getSimpleCodeEditorWidgetOptions(); @@ -433,12 +440,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (success) { // if there is already a breakpoint on this location - remove it. - let condition = this.breakpoint?.condition; - let hitCondition = this.breakpoint?.hitCondition; - let logMessage = this.breakpoint?.logMessage; - let triggeredBy = this.breakpoint?.triggeredBy; - let mode = this.breakpoint?.mode; - let modeLabel = this.breakpoint?.modeLabel; + let condition: string | undefined = undefined; + let hitCondition: string | undefined = undefined; + let logMessage: string | undefined = undefined; + let triggeredBy: string | undefined = undefined; + let mode: string | undefined = undefined; + let modeLabel: string | undefined = undefined; this.rememberInput(); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 8b989fb4..9019df75 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -20,7 +20,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as resources from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/base/common/themables'; import { Constants } from 'vs/base/common/uint'; @@ -55,6 +55,7 @@ import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, In import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; const $ = dom.$; @@ -553,7 +554,7 @@ class BreakpointsRenderer implements IListRenderer { const debugService = accessor.get(IDebugService); + const viewService = accessor.get(IViewsService); + await viewService.openView(BREAKPOINTS_VIEW_ID); debugService.addFunctionBreakpoint(); } }); @@ -1491,18 +1494,19 @@ abstract class MemoryBreakpointAction extends Action2 { private getRange(quickInput: IQuickInputService, defaultValue?: string) { return new Promise<{ address: string; bytes: number } | undefined>(resolve => { - const input = quickInput.createInputBox(); + const disposables = new DisposableStore(); + const input = disposables.add(quickInput.createInputBox()); input.prompt = localize('dataBreakpointMemoryRangePrompt', "Enter a memory range in which to break"); input.placeholder = localize('dataBreakpointMemoryRangePlaceholder', 'Absolute range (0x1234 - 0x1300) or range of bytes after an address (0x1234 + 0xff)'); if (defaultValue) { input.value = defaultValue; input.valueSelection = [0, defaultValue.length]; } - input.onDidChangeValue(e => { + disposables.add(input.onDidChangeValue(e => { const err = this.parseAddress(e, false); input.validationMessage = err?.error; - }); - input.onDidAccept(() => { + })); + disposables.add(input.onDidAccept(() => { const r = this.parseAddress(input.value, true); if ('error' in r) { input.validationMessage = r.error; @@ -1510,11 +1514,11 @@ abstract class MemoryBreakpointAction extends Action2 { resolve(r); } input.dispose(); - }); - input.onDidHide(() => { + })); + disposables.add(input.onDidHide(() => { resolve(undefined); - input.dispose(); - }); + disposables.dispose(); + })); input.ignoreFocusOut = true; input.show(); }); @@ -1643,7 +1647,7 @@ registerAction2(class extends Action2 { } else if (breakpoint instanceof DataBreakpoint) { await debugService.removeDataBreakpoints(breakpoint.getId()); } else if (breakpoint instanceof InstructionBreakpoint) { - await debugService.removeInstructionBreakpoints(breakpoint.instructionReference); + await debugService.removeInstructionBreakpoints(breakpoint.instructionReference, breakpoint.offset); } } }); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts index 6c3aeb1d..70240432 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts @@ -48,19 +48,28 @@ const FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = { color: themeColorFromId(focusedStackFrameColor) } }; -const TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = { +export const TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = { description: 'top-stack-frame-decoration', isWholeLine: true, className: 'debug-top-stack-frame-line', stickiness }; -const FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = { +export const FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = { description: 'focused-stack-frame-decoration', isWholeLine: true, className: 'debug-focused-stack-frame-line', stickiness }; +export const makeStackFrameColumnDecoration = (noCharactersBefore: boolean): IModelDecorationOptions => ({ + description: 'top-stack-frame-inline-decoration', + before: { + content: '\uEB8B', + inlineClassName: noCharactersBefore ? 'debug-top-stack-frame-column start-of-line' : 'debug-top-stack-frame-column', + inlineClassNameAffectsLetterSpacing: true + }, +}); + export function createDecorationsForStackFrame(stackFrame: IStackFrame, isFocusedSession: boolean, noCharactersBefore: boolean): IModelDeltaDecoration[] { // only show decorations for the currently focused thread. const result: IModelDeltaDecoration[] = []; @@ -85,14 +94,7 @@ export function createDecorationsForStackFrame(stackFrame: IStackFrame, isFocuse if (stackFrame.range.startColumn > 1) { result.push({ - options: { - description: 'top-stack-frame-inline-decoration', - before: { - content: '\uEB8B', - inlineClassName: noCharactersBefore ? 'debug-top-stack-frame-column start-of-line' : 'debug-top-stack-frame-column', - inlineClassNameAffectsLetterSpacing: true - }, - }, + options: makeStackFrameColumnDecoration(noCharactersBefore), range: columnUntilEOLRange }); } diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackView.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackView.ts index 80bdc5ff..6ee016a1 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -49,7 +49,7 @@ import { CALLSTACK_VIEW_ID, CONTEXT_CALLSTACK_ITEM_STOPPED, CONTEXT_CALLSTACK_IT import { StackFrame, Thread, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; const $ = dom.$; @@ -136,7 +136,7 @@ async function expandTo(session: IDebugSession, tree: WorkbenchCompressibleAsync export class CallStackView extends ViewPane { private stateMessage!: HTMLSpanElement; private stateMessageLabel!: HTMLSpanElement; - private stateMessageLabelHover!: IUpdatableHover; + private stateMessageLabelHover!: IManagedHover; private onCallStackChangeScheduler: RunOnceScheduler; private needsRefresh = false; private ignoreSelectionChangedEvent = false; @@ -221,7 +221,7 @@ export class CallStackView extends ViewPane { this.stateMessage = dom.append(container, $('span.call-stack-state-message')); this.stateMessage.hidden = true; this.stateMessageLabel = dom.append(this.stateMessage, $('span.label')); - this.stateMessageLabelHover = this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.stateMessage, '')); + this.stateMessageLabelHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.stateMessage, '')); } protected override renderBody(container: HTMLElement): void { @@ -465,9 +465,8 @@ export class CallStackView extends ViewPane { const secondary: IAction[] = []; const result = { primary, secondary }; const contextKeyService = this.contextKeyService.createOverlay(overlay); - const menu = this.menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); - createAndFillInContextMenuActions(menu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, result, 'inline'); - menu.dispose(); + const menu = this.menuService.getMenuActions(MenuId.DebugCallStackContext, contextKeyService, { arg: getContextForContributedActions(element), shouldForwardArgs: true }); + createAndFillInContextMenuActions(menu, result, 'inline'); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => result.secondary, @@ -582,7 +581,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer t.stopped); @@ -671,7 +670,7 @@ class ThreadsRenderer implements ICompressibleTreeRenderer, _index: number, data: IThreadTemplateData): void { const thread = element.element; - data.elementDisposable.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), data.thread, thread.name)); + data.elementDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.thread, thread.name)); data.label.set(thread.name, createMatches(element.filterData)); data.stateLabel.textContent = thread.stateLabel; data.stateLabel.classList.toggle('exception', thread.stoppedDetails?.reason === 'exception'); @@ -756,7 +755,7 @@ class StackFramesRenderer implements ICompressibleTreeRenderer, index: number, data: IErrorTemplateData): void { const error = element.element; data.label.textContent = error; - data.templateDisposable.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), data.label, error)); + data.templateDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.label, error)); } renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IErrorTemplateData, height: number | undefined): void { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackWidget.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackWidget.ts new file mode 100644 index 00000000..d02de4fb --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/callStackWidget.ts @@ -0,0 +1,738 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { assertNever } from 'vs/base/common/assert'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Codicon } from 'vs/base/common/codicons'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue, transaction } from 'vs/base/common/observable'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { Constants } from 'vs/base/common/uint'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import 'vs/css!./media/callStackWidget'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorContributionCtor, EditorContributionInstantiation, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; +import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; +import { Location } from 'vs/editor/common/languages'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ClickLinkGesture, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture'; +import { localize, localize2 } from 'vs/nls'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { makeStackFrameColumnDecoration, TOP_STACK_FRAME_DECORATION } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; + + +export class CallStackFrame { + constructor( + public readonly name: string, + public readonly source?: URI, + public readonly line = 1, + public readonly column = 1, + ) { } +} + +export class SkippedCallFrames { + constructor( + public readonly label: string, + public readonly load: (token: CancellationToken) => Promise, + ) { } +} + +export abstract class CustomStackFrame { + public readonly showHeader = observableValue('CustomStackFrame.showHeader', true); + public abstract readonly height: IObservable; + public abstract readonly label: string; + public icon?: ThemeIcon; + public abstract render(container: HTMLElement): IDisposable; + public renderActions?(container: HTMLElement): IDisposable; +} + +export type AnyStackFrame = SkippedCallFrames | CallStackFrame | CustomStackFrame; + +interface IFrameLikeItem { + readonly collapsed: ISettableObservable; + readonly height: IObservable; +} + +class WrappedCallStackFrame extends CallStackFrame implements IFrameLikeItem { + public readonly editorHeight = observableValue('WrappedCallStackFrame.height', this.source ? 100 : 0); + public readonly collapsed = observableValue('WrappedCallStackFrame.collapsed', false); + + public readonly height = derived(reader => { + return this.collapsed.read(reader) ? HEADER_HEIGHT : HEADER_HEIGHT + this.editorHeight.read(reader); + }); + + constructor(original: CallStackFrame) { + super(original.name, original.source, original.line, original.column); + } +} + +class WrappedCustomStackFrame implements IFrameLikeItem { + public readonly collapsed = observableValue('WrappedCallStackFrame.collapsed', false); + + public readonly height = derived(reader => { + const headerHeight = this.original.showHeader.read(reader) ? HEADER_HEIGHT : 0; + return this.collapsed.read(reader) ? headerHeight : headerHeight + this.original.height.read(reader); + }); + + constructor(public readonly original: CustomStackFrame) { } +} + +const isFrameLike = (item: unknown): item is IFrameLikeItem => + item instanceof WrappedCallStackFrame || item instanceof WrappedCustomStackFrame; + +type ListItem = WrappedCallStackFrame | SkippedCallFrames | WrappedCustomStackFrame; + +const WIDGET_CLASS_NAME = 'multiCallStackWidget'; + +/** + * A reusable widget that displays a call stack as a series of editors. Note + * that this both used in debug's exception widget as well as in the testing + * call stack view. + */ +export class CallStackWidget extends Disposable { + private readonly list: WorkbenchList; + private readonly layoutEmitter = this._register(new Emitter()); + private readonly currentFramesDs = this._register(new DisposableStore()); + private cts?: CancellationTokenSource; + + constructor( + container: HTMLElement, + containingEditor: ICodeEditor | undefined, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + + container.classList.add(WIDGET_CLASS_NAME); + this._register(toDisposable(() => container.classList.remove(WIDGET_CLASS_NAME))); + + this.list = this._register(instantiationService.createInstance( + WorkbenchList, + 'TestResultStackWidget', + container, + new StackDelegate(), + [ + instantiationService.createInstance(FrameCodeRenderer, containingEditor, this.layoutEmitter.event), + instantiationService.createInstance(MissingCodeRenderer), + instantiationService.createInstance(CustomRenderer), + instantiationService.createInstance(SkippedRenderer, (i) => this.loadFrame(i)), + ], + { + multipleSelectionSupport: false, + mouseSupport: false, + keyboardSupport: false, + setRowLineHeight: false, + accessibilityProvider: instantiationService.createInstance(StackAccessibilityProvider), + } + ) as WorkbenchList); + } + + /** Replaces the call frames display in the view. */ + public setFrames(frames: AnyStackFrame[]): void { + // cancel any existing load + this.currentFramesDs.clear(); + this.cts = new CancellationTokenSource(); + this._register(toDisposable(() => this.cts!.dispose(true))); + + this.list.splice(0, this.list.length, this.mapFrames(frames)); + } + + public layout(height?: number, width?: number): void { + this.list.layout(height, width); + this.layoutEmitter.fire(); + } + + public collapseAll() { + transaction(tx => { + for (let i = 0; i < this.list.length; i++) { + const frame = this.list.element(i); + if (isFrameLike(frame)) { + frame.collapsed.set(true, tx); + } + } + }); + } + + private async loadFrame(replacing: SkippedCallFrames): Promise { + if (!this.cts) { + return; + } + + const frames = await replacing.load(this.cts.token); + if (this.cts.token.isCancellationRequested) { + return; + } + + const index = this.list.indexOf(replacing); + this.list.splice(index, 1, this.mapFrames(frames)); + } + + private mapFrames(frames: AnyStackFrame[]): ListItem[] { + const result: ListItem[] = []; + for (const frame of frames) { + if (frame instanceof SkippedCallFrames) { + result.push(frame); + continue; + } + + const wrapped = frame instanceof CustomStackFrame + ? new WrappedCustomStackFrame(frame) : new WrappedCallStackFrame(frame); + result.push(wrapped); + + this.currentFramesDs.add(autorun(reader => { + const height = wrapped.height.read(reader); + const idx = this.list.indexOf(wrapped); + if (idx !== -1) { + this.list.updateElementHeight(idx, height); + } + })); + } + + return result; + } +} + +class StackAccessibilityProvider implements IListAccessibilityProvider { + constructor(@ILabelService private readonly labelService: ILabelService) { } + + getAriaLabel(e: ListItem): string | IObservable | null { + if (e instanceof SkippedCallFrames) { + return e.label; + } + + if (e instanceof WrappedCustomStackFrame) { + return e.original.label; + } + + if (e instanceof CallStackFrame) { + if (e.source && e.line) { + return localize({ + comment: ['{0} is an extension-defined label, then line number and filename'], + key: 'stackTraceLabel', + }, '{0}, line {1} in {2}', e.name, e.line, this.labelService.getUriLabel(e.source, { relative: true })); + } + + return e.name; + } + + assertNever(e); + } + getWidgetAriaLabel(): string { + return localize('stackTrace', 'Stack Trace'); + } +} + +class StackDelegate implements IListVirtualDelegate { + getHeight(element: ListItem): number { + if (element instanceof CallStackFrame || element instanceof WrappedCustomStackFrame) { + return element.height.get(); + } + if (element instanceof SkippedCallFrames) { + return HEADER_HEIGHT; + } + + assertNever(element); + } + + getTemplateId(element: ListItem): string { + if (element instanceof CallStackFrame) { + return element.source ? FrameCodeRenderer.templateId : MissingCodeRenderer.templateId; + } + if (element instanceof SkippedCallFrames) { + return SkippedRenderer.templateId; + } + if (element instanceof WrappedCustomStackFrame) { + return CustomRenderer.templateId; + } + + assertNever(element); + } +} + +interface IStackTemplateData extends IAbstractFrameRendererTemplateData { + editor: CodeEditorWidget; + toolbar: MenuWorkbenchToolBar; +} + +const editorOptions: IEditorOptions = { + scrollBeyondLastLine: false, + scrollbar: { + vertical: 'hidden', + horizontal: 'hidden', + handleMouseWheel: false, + useShadows: false, + }, + overviewRulerLanes: 0, + fixedOverflowWidgets: true, + overviewRulerBorder: false, + stickyScroll: { enabled: false }, + minimap: { enabled: false }, + readOnly: true, + automaticLayout: false, +}; + +const makeFrameElements = () => dom.h('div.multiCallStackFrame', [ + dom.h('div.header@header', [ + dom.h('div.collapse-button@collapseButton'), + dom.h('div.title.show-file-icons@title'), + dom.h('div.actions@actions'), + ]), + + dom.h('div.editorParent', [ + dom.h('div.editorContainer@editor'), + ]) +]); + +const HEADER_HEIGHT = 24; + +interface IAbstractFrameRendererTemplateData { + container: HTMLElement; + label: ResourceLabel; + elements: ReturnType; + decorations: string[]; + collapse: Button; + elementStore: DisposableStore; + templateStore: DisposableStore; +} + +abstract class AbstractFrameRenderer implements IListRenderer { + public abstract templateId: string; + + constructor( + @IInstantiationService protected readonly instantiationService: IInstantiationService, + ) { } + + renderTemplate(container: HTMLElement): T { + const elements = makeFrameElements(); + container.appendChild(elements.root); + + + const templateStore = new DisposableStore(); + container.classList.add('multiCallStackFrameContainer'); + templateStore.add(toDisposable(() => { + container.classList.remove('multiCallStackFrameContainer'); + elements.root.remove(); + })); + + const label = templateStore.add(this.instantiationService.createInstance(ResourceLabel, elements.title, {})); + + const collapse = templateStore.add(new Button(elements.collapseButton, {})); + + const contentId = generateUuid(); + elements.editor.id = contentId; + elements.editor.role = 'region'; + elements.collapseButton.setAttribute('aria-controls', contentId); + + return this.finishRenderTemplate({ + container, + decorations: [], + elements, + label, + collapse, + elementStore: templateStore.add(new DisposableStore()), + templateStore, + }); + } + + protected abstract finishRenderTemplate(data: IAbstractFrameRendererTemplateData): T; + + renderElement(element: ListItem, index: number, template: T, height: number | undefined): void { + const { elementStore } = template; + elementStore.clear(); + const item = element as IFrameLikeItem; + + this.setupCollapseButton(item, template); + } + + private setupCollapseButton(item: IFrameLikeItem, { elementStore, elements, collapse }: T) { + elementStore.add(autorun(reader => { + collapse.element.className = ''; + const collapsed = item.collapsed.read(reader); + collapse.icon = collapsed ? Codicon.chevronRight : Codicon.chevronDown; + collapse.element.ariaExpanded = String(!collapsed); + elements.root.classList.toggle('collapsed', collapsed); + })); + const toggleCollapse = () => item.collapsed.set(!item.collapsed.get(), undefined); + elementStore.add(collapse.onDidClick(toggleCollapse)); + elementStore.add(dom.addDisposableListener(elements.title, 'click', toggleCollapse)); + } + + disposeElement(element: ListItem, index: number, templateData: T, height: number | undefined): void { + templateData.elementStore.clear(); + } + + disposeTemplate(templateData: T): void { + templateData.templateStore.dispose(); + } +} + +const CONTEXT_LINES = 2; + +/** Renderer for a normal stack frame where code is available. */ +class FrameCodeRenderer extends AbstractFrameRenderer { + public static readonly templateId = 'f'; + + public readonly templateId = FrameCodeRenderer.templateId; + + constructor( + private readonly containingEditor: ICodeEditor | undefined, + private readonly onLayout: Event, + @ITextModelService private readonly modelService: ITextModelService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(instantiationService); + } + + protected override finishRenderTemplate(data: IAbstractFrameRendererTemplateData): IStackTemplateData { + // override default e.g. language contributions, only allow users to click + // on code in the call stack to go to its source location + const contributions: IEditorContributionDescription[] = [{ + id: ClickToLocationContribution.ID, + instantiation: EditorContributionInstantiation.BeforeFirstInteraction, + ctor: ClickToLocationContribution as EditorContributionCtor, + }]; + + const editor = this.containingEditor + ? this.instantiationService.createInstance( + EmbeddedCodeEditorWidget, + data.elements.editor, + editorOptions, + { isSimpleWidget: true, contributions }, + this.containingEditor, + ) + : this.instantiationService.createInstance( + CodeEditorWidget, + data.elements.editor, + editorOptions, + { isSimpleWidget: true, contributions }, + ); + + data.templateStore.add(editor); + + const toolbar = data.templateStore.add(this.instantiationService.createInstance(MenuWorkbenchToolBar, data.elements.actions, MenuId.DebugCallStackToolbar, { + menuOptions: { shouldForwardArgs: true }, + actionViewItemProvider: (action, options) => createActionViewItem(this.instantiationService, action, options), + })); + + return { ...data, editor, toolbar }; + } + + override renderElement(element: ListItem, index: number, template: IStackTemplateData, height: number | undefined): void { + super.renderElement(element, index, template, height); + + const { elementStore, editor } = template; + + const item = element as WrappedCallStackFrame; + const uri = item.source!; + + template.label.element.setFile(uri); + const cts = new CancellationTokenSource(); + elementStore.add(toDisposable(() => cts.dispose(true))); + this.modelService.createModelReference(uri).then(reference => { + if (cts.token.isCancellationRequested) { + return reference.dispose(); + } + + elementStore.add(reference); + editor.setModel(reference.object.textEditorModel); + this.setupEditorAfterModel(item, template); + this.setupEditorLayout(item, template); + }); + } + + private setupEditorLayout(item: WrappedCallStackFrame, { elementStore, container, editor }: IStackTemplateData) { + const layout = () => { + const prev = editor.getContentHeight(); + editor.layout({ width: container.clientWidth, height: prev }); + + const next = editor.getContentHeight(); + if (next !== prev) { + editor.layout({ width: container.clientWidth, height: next }); + } + + item.editorHeight.set(next, undefined); + }; + elementStore.add(editor.onDidChangeModelDecorations(layout)); + elementStore.add(editor.onDidChangeModelContent(layout)); + elementStore.add(editor.onDidChangeModelOptions(layout)); + elementStore.add(this.onLayout(layout)); + layout(); + } + + private setupEditorAfterModel(item: WrappedCallStackFrame, template: IStackTemplateData): void { + const range = Range.fromPositions({ + column: item.column ?? 1, + lineNumber: item.line ?? 1, + }); + + template.toolbar.context = { uri: item.source, range }; + + template.editor.setHiddenAreas([ + Range.fromPositions( + { column: 1, lineNumber: 1 }, + { column: 1, lineNumber: Math.max(1, item.line - CONTEXT_LINES - 1) }, + ), + Range.fromPositions( + { column: 1, lineNumber: item.line + CONTEXT_LINES + 1 }, + { column: 1, lineNumber: Constants.MAX_SAFE_SMALL_INTEGER }, + ), + ]); + + template.editor.changeDecorations(accessor => { + for (const d of template.decorations) { + accessor.removeDecoration(d); + } + template.decorations.length = 0; + + const beforeRange = range.setStartPosition(range.startLineNumber, 1); + const hasCharactersBefore = !!template.editor.getModel()?.getValueInRange(beforeRange).trim(); + const decoRange = range.setEndPosition(range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER); + + template.decorations.push(accessor.addDecoration( + decoRange, + makeStackFrameColumnDecoration(!hasCharactersBefore), + )); + template.decorations.push(accessor.addDecoration( + decoRange, + TOP_STACK_FRAME_DECORATION, + )); + }); + + item.editorHeight.set(template.editor.getContentHeight(), undefined); + } +} + +interface IMissingTemplateData { + elements: ReturnType; + label: ResourceLabel; +} + +/** Renderer for a call frame that's missing a URI */ +class MissingCodeRenderer implements IListRenderer { + public static readonly templateId = 'm'; + public readonly templateId = MissingCodeRenderer.templateId; + + constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { } + + renderTemplate(container: HTMLElement): IMissingTemplateData { + const elements = makeFrameElements(); + elements.root.classList.add('missing'); + container.appendChild(elements.root); + const label = this.instantiationService.createInstance(ResourceLabel, elements.title, {}); + return { elements, label }; + } + + renderElement(element: ListItem, _index: number, templateData: IMissingTemplateData): void { + const cast = element as CallStackFrame; + templateData.label.element.setResource({ + name: cast.name, + description: localize('stackFrameLocation', 'Line {0} column {1}', cast.line, cast.column), + range: { startLineNumber: cast.line, startColumn: cast.column, endColumn: cast.column, endLineNumber: cast.line }, + }, { + icon: Codicon.fileBinary, + }); + } + + disposeTemplate(templateData: IMissingTemplateData): void { + templateData.label.dispose(); + templateData.elements.root.remove(); + } +} + +/** Renderer for a call frame that's missing a URI */ +class CustomRenderer extends AbstractFrameRenderer { + public static readonly templateId = 'c'; + public readonly templateId = CustomRenderer.templateId; + + protected override finishRenderTemplate(data: IAbstractFrameRendererTemplateData): IAbstractFrameRendererTemplateData { + return data; + } + + override renderElement(element: ListItem, index: number, template: IAbstractFrameRendererTemplateData, height: number | undefined): void { + super.renderElement(element, index, template, height); + + const item = element as WrappedCustomStackFrame; + const { elementStore, container, label } = template; + + label.element.setResource({ name: item.original.label }, { icon: item.original.icon }); + + elementStore.add(autorun(reader => { + template.elements.header.style.display = item.original.showHeader.read(reader) ? '' : 'none'; + })); + + elementStore.add(autorunWithStore((reader, store) => { + if (!item.collapsed.read(reader)) { + store.add(item.original.render(container)); + } + })); + + const actions = item.original.renderActions?.(template.elements.actions); + if (actions) { + elementStore.add(actions); + } + } +} + +interface ISkippedTemplateData { + button: Button; + current?: SkippedCallFrames; + store: DisposableStore; +} + +/** Renderer for a button to load more call frames */ +class SkippedRenderer implements IListRenderer { + public static readonly templateId = 's'; + public readonly templateId = SkippedRenderer.templateId; + + constructor( + private readonly loadFrames: (fromItem: SkippedCallFrames) => Promise, + @INotificationService private readonly notificationService: INotificationService, + ) { } + + renderTemplate(container: HTMLElement): ISkippedTemplateData { + const store = new DisposableStore(); + const button = new Button(container, { title: '', ...defaultButtonStyles }); + const data: ISkippedTemplateData = { button, store }; + + store.add(button); + store.add(button.onDidClick(() => { + if (!data.current || !button.enabled) { + return; + } + + button.enabled = false; + this.loadFrames(data.current).catch(e => { + this.notificationService.error(localize('failedToLoadFrames', 'Failed to load stack frames: {0}', e.message)); + }); + })); + + return data; + } + + renderElement(element: ListItem, index: number, templateData: ISkippedTemplateData, height: number | undefined): void { + const cast = element as SkippedCallFrames; + templateData.button.enabled = true; + templateData.button.label = cast.label; + templateData.current = cast; + } + + disposeTemplate(templateData: ISkippedTemplateData): void { + templateData.store.dispose(); + } +} + +/** A simple contribution that makes all data in the editor clickable to go to the location */ +class ClickToLocationContribution extends Disposable implements IEditorContribution { + public static readonly ID = 'clickToLocation'; + private readonly linkDecorations: IEditorDecorationsCollection; + private current: { line: number; word: IWordAtPosition } | undefined; + + constructor( + private readonly editor: ICodeEditor, + @IEditorService editorService: IEditorService, + ) { + super(); + this.linkDecorations = editor.createDecorationsCollection(); + this._register(toDisposable(() => this.linkDecorations.clear())); + + const clickLinkGesture = this._register(new ClickLinkGesture(editor)); + + this._register(clickLinkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => { + this.onMove(mouseEvent); + })); + this._register(clickLinkGesture.onExecute((e) => { + const model = this.editor.getModel(); + if (!this.current || !model) { + return; + } + + editorService.openEditor({ + resource: model.uri, + options: { + selection: Range.fromPositions(new Position(this.current.line, this.current.word.startColumn)), + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, + }, + }, e.hasSideBySideModifier ? SIDE_GROUP : undefined); + })); + } + + private onMove(mouseEvent: ClickLinkMouseEvent) { + if (!mouseEvent.hasTriggerModifier) { + return this.clear(); + } + + const position = mouseEvent.target.position; + const word = position && this.editor.getModel()?.getWordAtPosition(position); + if (!word) { + return this.clear(); + } + + const prev = this.current?.word; + if (prev && prev.startColumn === word.startColumn && prev.endColumn === word.endColumn && prev.word === word.word) { + return; + } + + this.current = { word, line: position.lineNumber }; + this.linkDecorations.set([{ + range: new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), + options: { + description: 'call-stack-go-to-file-link', + inlineClassName: 'call-stack-go-to-file-link', + }, + }]); + } + + private clear() { + this.linkDecorations.clear(); + this.current = undefined; + } +} + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'callStackWidget.goToFile', + title: localize2('goToFile', 'Open File'), + icon: Codicon.goToFile, + menu: { + id: MenuId.DebugCallStackToolbar, + order: 22, + group: 'navigation', + }, + }); + } + + async run(accessor: ServicesAccessor, { uri, range }: Location): Promise { + const editorService = accessor.get(IEditorService); + await editorService.openEditor({ + resource: uri, + options: { + selection: range, + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, + }, + }); + } +}); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 36da9c86..c909d645 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -11,6 +11,7 @@ import 'vs/css!./media/debug.contribution'; import 'vs/css!./media/debugHover'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import * as nls from 'vs/nls'; +import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -21,13 +22,14 @@ import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/pl import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, registerWorkbenchContribution2, Extensions as WorkbenchExtensions, WorkbenchPhase } from 'vs/workbench/common/contributions'; import { EditorExtensions } from 'vs/workbench/common/editor'; import { IViewContainersRegistry, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView'; +import { ReplAccessibleView } from 'vs/workbench/contrib/debug/browser/replAccessibleView'; import { registerColors } from 'vs/workbench/contrib/debug/browser/debugColors'; import { ADD_CONFIGURATION_ID, ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTINUE_ID, CONTINUE_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_STACK_TRACE_ID, COPY_VALUE_ID, COPY_VALUE_LABEL, DEBUG_COMMAND_CATEGORY, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, EDIT_EXPRESSION_COMMAND_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, PAUSE_ID, PAUSE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, REMOVE_EXPRESSION_COMMAND_ID, RESTART_FRAME_ID, RESTART_LABEL, RESTART_SESSION_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL, SET_EXPRESSION_COMMAND_ID, SHOW_LOADED_SCRIPTS_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL, TERMINATE_THREAD_ID, TOGGLE_INLINE_BREAKPOINT_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { DebugConsoleQuickAccess } from 'vs/workbench/contrib/debug/browser/debugConsoleQuickAccess'; @@ -56,6 +58,11 @@ import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassem import { COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import './debugSettingMigration'; +import { ReplAccessibilityHelp } from 'vs/workbench/contrib/debug/browser/replAccessibilityHelp'; +import { ReplAccessibilityAnnouncer } from 'vs/workbench/contrib/debug/common/replAccessibilityAnnouncer'; +import { RunAndDebugAccessibilityHelp } from 'vs/workbench/contrib/debug/browser/runAndDebugAccessibilityHelp'; +import { DebugWatchAccessibilityAnnouncer } from 'vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer'; const debugCategory = nls.localize('debugCategory', "Debug"); registerColors(); @@ -440,6 +447,11 @@ configurationRegistry.registerConfiguration({ title: nls.localize('debugConfigurationTitle', "Debug"), type: 'object', properties: { + 'debug.showVariableTypes': { + type: 'boolean', + description: nls.localize({ comment: ['This is the description for a setting'], key: 'showVariableTypes' }, "Show variable type in variable pane during debug session"), + default: false + }, 'debug.allowBreakpointsEverywhere': { type: 'boolean', description: nls.localize({ comment: ['This is the description for a setting'], key: 'allowBreakpointsEverywhere' }, "Allow setting breakpoints in any file."), @@ -480,7 +492,7 @@ configurationRegistry.registerConfiguration({ }, 'debug.toolBarLocation': { enum: ['floating', 'docked', 'commandCenter', 'hidden'], - markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'toolBarLocation' }, "Controls the location of the debug toolbar. Either `floating` in all views, `docked` in the debug view, `commandCenter` (requires `{0}`), or `hidden`.", '#window.commandCenter#'), + markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'toolBarLocation' }, "Controls the location of the debug toolbar. Either `floating` in all views, `docked` in the debug view, `commandCenter` (requires {0}), or `hidden`.", '`#window.commandCenter#`'), default: 'floating', markdownEnumDescriptions: [ nls.localize('debugToolBar.floating', "Show debug toolbar in all views."), @@ -555,7 +567,8 @@ configurationRegistry.registerConfiguration({ type: 'object', description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces."), default: { configurations: [], compounds: [] }, - $ref: launchSchemaId + $ref: launchSchemaId, + disallowConfigurationDefault: true }, 'debug.focusWindowOnBreak': { type: 'boolean', @@ -610,9 +623,15 @@ configurationRegistry.registerConfiguration({ description: nls.localize('debug.disassemblyView.showSourceCode', "Show Source Code in Disassembly View.") }, 'debug.autoExpandLazyVariables': { - type: 'boolean', - default: false, - description: nls.localize('debug.autoExpandLazyVariables', "Automatically show values for variables that are lazily resolved by the debugger, such as getters.") + type: 'string', + enum: ['auto', 'on', 'off'], + default: 'auto', + enumDescriptions: [ + nls.localize('debug.autoExpandLazyVariables.auto', "When in screen reader optimized mode, automatically expand lazy variables."), + nls.localize('debug.autoExpandLazyVariables.on', "Always automatically expand lazy variables."), + nls.localize('debug.autoExpandLazyVariables.off', "Never automatically expand lazy variables.") + ], + description: nls.localize('debug.autoExpandLazyVariables', "Controls whether variables that are lazily resolved, such as getters, are automatically resolved and expanded by the debugger.") }, 'debug.enableStatusBarColor': { type: 'boolean', @@ -621,8 +640,14 @@ configurationRegistry.registerConfiguration({ }, 'debug.hideLauncherWhileDebugging': { type: 'boolean', - markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'debug.hideLauncherWhileDebugging' }, "Hide 'Start Debugging' control in title bar of 'Run and Debug' view while debugging is active. Only relevant when `{0}` is not `docked`.", '#debug.toolBarLocation#'), + markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'debug.hideLauncherWhileDebugging' }, "Hide 'Start Debugging' control in title bar of 'Run and Debug' view while debugging is active. Only relevant when {0} is not `docked`.", '`#debug.toolBarLocation#`'), default: false } } }); + +AccessibleViewRegistry.register(new ReplAccessibleView()); +AccessibleViewRegistry.register(new ReplAccessibilityHelp()); +AccessibleViewRegistry.register(new RunAndDebugAccessibilityHelp()); +registerWorkbenchContribution2(ReplAccessibilityAnnouncer.ID, ReplAccessibilityAnnouncer, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(DebugWatchAccessibilityAnnouncer.ID, DebugWatchAccessibilityAnnouncer, WorkbenchPhase.AfterRestored); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index a4d5c4e7..d92bd1ec 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -24,6 +24,9 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const $ = dom.$; @@ -49,7 +52,8 @@ export class StartDebugActionViewItem extends BaseActionViewItem { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IContextViewService contextViewService: IContextViewService, @IKeybindingService private readonly keybindingService: IKeybindingService, - @IHoverService private readonly hoverService: IHoverService + @IHoverService private readonly hoverService: IHoverService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(context, action, options); this.toDispose = []; @@ -78,9 +82,9 @@ export class StartDebugActionViewItem extends BaseActionViewItem { const keybinding = this.keybindingService.lookupKeybinding(this.action.id)?.getLabel(); const keybindingLabel = keybinding ? ` (${keybinding})` : ''; const title = this.action.label + keybindingLabel; - this.toDispose.push(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.start, title)); + this.toDispose.push(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.start, title)); this.start.setAttribute('role', 'button'); - this.start.ariaLabel = title; + this._setAriaLabel(title); this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.CLICK, () => { this.start.blur(); @@ -261,6 +265,21 @@ export class StartDebugActionViewItem extends BaseActionViewItem { this.selectBox.setOptions(this.debugOptions.map((data, index): ISelectOptionItem => ({ text: data.label, isDisabled: disabledIdxs.indexOf(index) !== -1 })), this.selected); } + + private _setAriaLabel(title: string): void { + let ariaLabel = title; + let keybinding: string | undefined; + const verbose = this.configurationService.getValue(AccessibilityVerbositySettingId.Debug); + if (verbose) { + keybinding = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp, this.contextKeyService)?.getLabel() ?? undefined; + } + if (keybinding) { + ariaLabel = nls.localize('commentLabelWithKeybinding', "{0}, use ({1}) for accessibility help", ariaLabel, keybinding); + } else { + ariaLabel = nls.localize('commentLabelWithKeybindingNoKeybinding', "{0}, run the command Open Accessibility Help which is currently not triggerable via keybinding.", ariaLabel); + } + this.start.ariaLabel = ariaLabel; + } } export class FocusSessionActionViewItem extends SelectActionViewItem { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 42331620..8e1fc260 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -13,13 +14,14 @@ import { IEditorModel } from 'vs/editor/common/editorCommon'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import * as nls from 'vs/nls'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Breakpoints } from 'vs/workbench/contrib/debug/common/breakpoints'; @@ -27,6 +29,7 @@ import { CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE, IAdapte import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { breakpointsExtPoint, debuggersExtPoint, launchSchema, presentationSchema } from 'vs/workbench/contrib/debug/common/debugSchemas'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; +import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -49,6 +52,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { private readonly _onDidDebuggersExtPointRead = new Emitter(); private breakpointContributions: Breakpoints[] = []; private debuggerWhenKeys = new Set(); + private taskLabels: string[] = []; /** Extensions that were already active before any debugger activation events */ private earlyActivatedExtensions: Set | undefined; @@ -66,7 +70,9 @@ export class AdapterManager extends Disposable implements IAdapterManager { @IContextKeyService private readonly contextKeyService: IContextKeyService, @ILanguageService private readonly languageService: ILanguageService, @IDialogService private readonly dialogService: IDialogService, - @ILifecycleService private readonly lifecycleService: ILifecycleService + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ITaskService private readonly tasksService: ITaskService, + @IMenuService private readonly menuService: IMenuService, ) { super(); this.adapterDescriptorFactories = []; @@ -85,12 +91,22 @@ export class AdapterManager extends Disposable implements IAdapterManager { this._register(this.onDidDebuggersExtPointRead(() => { this.debugExtensionsAvailable.set(this.debuggers.length > 0); })); + + // generous debounce since this will end up calling `resolveTask` internally + const updateTaskScheduler = this._register(new RunOnceScheduler(() => this.updateTaskLabels(), 5000)); + + this._register(Event.any(tasksService.onDidChangeTaskConfig, tasksService.onDidChangeTaskProviders)(() => { + updateTaskScheduler.cancel(); + updateTaskScheduler.schedule(); + })); this.lifecycleService.when(LifecyclePhase.Eventually) .then(() => this.debugExtensionsAvailable.set(this.debuggers.length > 0)); // If no extensions with a debugger contribution are loaded this._register(delegate.onDidNewSession(s => { this.usedDebugTypes.add(s.configuration.type); })); + + updateTaskScheduler.schedule(); } private registerListeners(): void { @@ -137,7 +153,14 @@ export class AdapterManager extends Disposable implements IAdapterManager { }); } - private updateDebugAdapterSchema(): void { + private updateTaskLabels() { + this.tasksService.getKnownTasks().then(tasks => { + this.taskLabels = tasks.map(task => task._label); + this.updateDebugAdapterSchema(); + }); + } + + private updateDebugAdapterSchema() { // update the schema to include all attributes, snippets and types from extensions. const items = (launchSchema.properties!['configurations'].items); const taskSchema = TaskDefinitionRegistry.getJsonSchema(); @@ -160,7 +183,8 @@ export class AdapterManager extends Disposable implements IAdapterManager { }], default: '', defaultSnippets: [{ body: { task: '', type: '' } }], - description: nls.localize('debugPrelaunchTask', "Task to run before debug session starts.") + description: nls.localize('debugPrelaunchTask', "Task to run before debug session starts."), + examples: this.taskLabels, }, 'postDebugTask': { anyOf: [taskSchema, { @@ -168,7 +192,8 @@ export class AdapterManager extends Disposable implements IAdapterManager { }], default: '', defaultSnippets: [{ body: { task: '', type: '' } }], - description: nls.localize('debugPostDebugTask', "Task to run after debug session ends.") + description: nls.localize('debugPostDebugTask', "Task to run after debug session ends."), + examples: this.taskLabels, }, 'presentation': presentationSchema, 'internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, @@ -382,7 +407,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { } }); - const picks: { label: string; debugger?: Debugger; type?: string }[] = []; + const picks: ({ label: string; debugger?: Debugger; type?: string } | MenuItemAction)[] = []; if (suggestedCandidates.length > 0) { picks.push( { type: 'separator', label: nls.localize('suggestedDebuggers', "Suggested") }, @@ -401,11 +426,20 @@ export class AdapterManager extends Disposable implements IAdapterManager { { type: 'separator', label: '' }, { label: languageLabel ? nls.localize('installLanguage', "Install an extension for {0}...", languageLabel) : nls.localize('installExt', "Install extension...") }); + const contributed = this.menuService.getMenuActions(MenuId.DebugCreateConfiguration, this.contextKeyService); + for (const [, action] of contributed) { + for (const item of action) { + picks.push(item); + } + } const placeHolder = nls.localize('selectDebug', "Select debugger"); - return this.quickInputService.pick<{ label: string; debugger?: Debugger }>(picks, { activeItem: picks[0], placeHolder }) - .then(picked => { - if (picked && picked.debugger) { + return this.quickInputService.pick<{ label: string; debugger?: Debugger } | IQuickPickItem>(picks, { activeItem: picks[0], placeHolder }) + .then(async picked => { + if (picked && 'debugger' in picked && picked.debugger) { return picked.debugger; + } else if (picked instanceof MenuItemAction) { + picked.run(); + return; } if (picked) { this.commandService.executeCommand('debug.installAdditionalDebuggers', languageLabel); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugColors.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugColors.ts index f7b75866..19b47280 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugColors.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugColors.ts @@ -18,12 +18,7 @@ export const debugToolBarBackground = registerColor('debugToolBar.background', { hcLight: '#FFFFFF' }, localize('debugToolBarBackground', "Debug toolbar background color.")); -export const debugToolBarBorder = registerColor('debugToolBar.border', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, localize('debugToolBarBorder', "Debug toolbar border color.")); +export const debugToolBarBorder = registerColor('debugToolBar.border', null, localize('debugToolBarBorder', "Debug toolbar border color.")); export const debugIconStartForeground = registerColor('debugIcon.startForeground', { dark: '#89D185', @@ -35,6 +30,7 @@ export const debugIconStartForeground = registerColor('debugIcon.startForeground export function registerColors() { const debugTokenExpressionName = registerColor('debugTokenExpression.name', { dark: '#c586c0', light: '#9b46b0', hcDark: foreground, hcLight: foreground }, 'Foreground color for the token names shown in the debug views (ie. the Variables or Watch view).'); + const debugTokenExpressionType = registerColor('debugTokenExpression.type', { dark: '#4A90E2', light: '#4A90E2', hcDark: foreground, hcLight: foreground }, 'Foreground color for the token types shown in the debug views (ie. the Variables or Watch view).'); const debugTokenExpressionValue = registerColor('debugTokenExpression.value', { dark: '#cccccc99', light: '#6c6c6ccc', hcDark: foreground, hcLight: foreground }, 'Foreground color for the token values shown in the debug views (ie. the Variables or Watch view).'); const debugTokenExpressionString = registerColor('debugTokenExpression.string', { dark: '#ce9178', light: '#a31515', hcDark: '#f48771', hcLight: '#a31515' }, 'Foreground color for strings in the debug views (ie. the Variables or Watch view).'); const debugTokenExpressionBoolean = registerColor('debugTokenExpression.boolean', { dark: '#4e94ce', light: '#0000ff', hcDark: '#75bdfe', hcLight: '#0000ff' }, 'Foreground color for booleans in the debug views (ie. the Variables or Watch view).'); @@ -43,15 +39,15 @@ export function registerColors() { const debugViewExceptionLabelForeground = registerColor('debugView.exceptionLabelForeground', { dark: foreground, light: '#FFF', hcDark: foreground, hcLight: foreground }, 'Foreground color for a label shown in the CALL STACK view when the debugger breaks on an exception.'); const debugViewExceptionLabelBackground = registerColor('debugView.exceptionLabelBackground', { dark: '#6C2022', light: '#A31515', hcDark: '#6C2022', hcLight: '#A31515' }, 'Background color for a label shown in the CALL STACK view when the debugger breaks on an exception.'); - const debugViewStateLabelForeground = registerColor('debugView.stateLabelForeground', { dark: foreground, light: foreground, hcDark: foreground, hcLight: foreground }, 'Foreground color for a label in the CALL STACK view showing the current session\'s or thread\'s state.'); - const debugViewStateLabelBackground = registerColor('debugView.stateLabelBackground', { dark: '#88888844', light: '#88888844', hcDark: '#88888844', hcLight: '#88888844' }, 'Background color for a label in the CALL STACK view showing the current session\'s or thread\'s state.'); - const debugViewValueChangedHighlight = registerColor('debugView.valueChangedHighlight', { dark: '#569CD6', light: '#569CD6', hcDark: '#569CD6', hcLight: '#569CD6' }, 'Color used to highlight value changes in the debug views (ie. in the Variables view).'); + const debugViewStateLabelForeground = registerColor('debugView.stateLabelForeground', foreground, 'Foreground color for a label in the CALL STACK view showing the current session\'s or thread\'s state.'); + const debugViewStateLabelBackground = registerColor('debugView.stateLabelBackground', '#88888844', 'Background color for a label in the CALL STACK view showing the current session\'s or thread\'s state.'); + const debugViewValueChangedHighlight = registerColor('debugView.valueChangedHighlight', '#569CD6', 'Color used to highlight value changes in the debug views (ie. in the Variables view).'); const debugConsoleInfoForeground = registerColor('debugConsole.infoForeground', { dark: editorInfoForeground, light: editorInfoForeground, hcDark: foreground, hcLight: foreground }, 'Foreground color for info messages in debug REPL console.'); const debugConsoleWarningForeground = registerColor('debugConsole.warningForeground', { dark: editorWarningForeground, light: editorWarningForeground, hcDark: '#008000', hcLight: editorWarningForeground }, 'Foreground color for warning messages in debug REPL console.'); - const debugConsoleErrorForeground = registerColor('debugConsole.errorForeground', { dark: errorForeground, light: errorForeground, hcDark: errorForeground, hcLight: errorForeground }, 'Foreground color for error messages in debug REPL console.'); - const debugConsoleSourceForeground = registerColor('debugConsole.sourceForeground', { dark: foreground, light: foreground, hcDark: foreground, hcLight: foreground }, 'Foreground color for source filenames in debug REPL console.'); - const debugConsoleInputIconForeground = registerColor('debugConsoleInputIcon.foreground', { dark: foreground, light: foreground, hcDark: foreground, hcLight: foreground }, 'Foreground color for debug console input marker icon.'); + const debugConsoleErrorForeground = registerColor('debugConsole.errorForeground', errorForeground, 'Foreground color for error messages in debug REPL console.'); + const debugConsoleSourceForeground = registerColor('debugConsole.sourceForeground', foreground, 'Foreground color for source filenames in debug REPL console.'); + const debugConsoleInputIconForeground = registerColor('debugConsoleInputIcon.foreground', foreground, 'Foreground color for debug console input marker icon.'); const debugIconPauseForeground = registerColor('debugIcon.pauseForeground', { dark: '#75BEFF', @@ -210,6 +206,7 @@ export function registerColors() { } const tokenNameColor = theme.getColor(debugTokenExpressionName)!; + const tokenTypeColor = theme.getColor(debugTokenExpressionType)!; const tokenValueColor = theme.getColor(debugTokenExpressionValue)!; const tokenStringColor = theme.getColor(debugTokenExpressionString)!; const tokenBooleanColor = theme.getColor(debugTokenExpressionBoolean)!; @@ -221,6 +218,10 @@ export function registerColors() { color: ${tokenNameColor}; } + .monaco-workbench .monaco-list-row .expression .type { + color: ${tokenTypeColor}; + } + .monaco-workbench .monaco-list-row .expression .value, .monaco-workbench .debug-hover-widget .value { color: ${tokenValueColor}; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 7db66075..b657df87 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -36,6 +36,8 @@ import { showLoadedScriptMenu } from 'vs/workbench/contrib/debug/common/loadedSc import { showDebugSessionMenu } from 'vs/workbench/contrib/debug/browser/debugSessionPicker'; import { TEXT_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint'; @@ -568,11 +570,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ target: DebugProtocol.StepInTarget; } - const qp = quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const qp = disposables.add(quickInputService.createQuickPick()); qp.busy = true; qp.show(); - qp.onDidChangeActive(([item]) => { + disposables.add(qp.onDidChangeActive(([item]) => { if (codeEditor && item && item.target.line !== undefined) { codeEditor.revealLineInCenterIfOutsideViewport(item.target.line); codeEditor.setSelection({ @@ -582,15 +585,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ endColumn: item.target.endColumn || item.target.column || 1, }); } - }); + })); - qp.onDidAccept(() => { + disposables.add(qp.onDidAccept(() => { if (qp.activeItems.length) { session.stepIn(frame.thread.threadId, qp.activeItems[0].target.id); } - }); + })); - qp.onDidHide(() => qp.dispose()); + disposables.add(qp.onDidHide(() => disposables.dispose())); session.stepInTargets(frame.frameId).then(targets => { qp.busy = false; @@ -1009,7 +1012,11 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, { title: nls.localize('addInlineBreakpoint', "Add Inline Breakpoint"), category: DEBUG_COMMAND_CATEGORY }, - when: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), EditorContextKeys.editorTextFocus), + when: ContextKeyExpr.and( + CONTEXT_IN_DEBUG_MODE, + PanelFocusContext.toNegated(), + EditorContextKeys.editorTextFocus, + CONTEXT_IN_CHAT_SESSION.toNegated()), group: 'debug', order: 1 }); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 7151a0ff..ed6d6ece 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -20,6 +20,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { ILogService } from 'vs/platform/log/common/log'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -72,7 +73,8 @@ export class ConfigurationManager implements IConfigurationManager { @IExtensionService private readonly extensionService: IExtensionService, @IHistoryService private readonly historyService: IHistoryService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @ILogService private readonly logService: ILogService, ) { this.configProviders = []; this.toDispose = [this._onDidChangeConfigurationProviders]; @@ -257,7 +259,18 @@ export class ConfigurationManager implements IConfigurationManager { disposables.add(input.onDidHide(() => resolve(undefined))); }); - const nestedPicks = await Promise.all(picks); + let nestedPicks: IDynamicPickItem[][]; + try { + // This await invokes the extension providers, which might fail due to several reasons, + // therefore we gate this logic under a try/catch to prevent leaving the Debug Tab + // selector in a borked state. + nestedPicks = await Promise.all(picks); + } catch (err) { + this.logService.error(err); + disposables.dispose(); + return; + } + const items = nestedPicks.flat(); input.items = items; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 7810995b..63792065 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -14,6 +14,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import * as nls from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -22,14 +23,15 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { PanelFocusContext } from 'vs/workbench/common/contextkeys'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView'; -import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_EXCEPTION_WIDGET_VISIBLE, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_IN_DEBUG_MODE, CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugConfiguration, IDebugEditorContribution, IDebugService, REPL_VIEW_ID, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { Repl } from 'vs/workbench/contrib/debug/browser/repl'; +import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_EXCEPTION_WIDGET_VISIBLE, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_IN_DEBUG_MODE, CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugConfiguration, IDebugEditorContribution, IDebugService, REPL_VIEW_ID, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { getEvaluatableExpressionAtPosition } from 'vs/workbench/contrib/debug/common/debugUtils'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ILocalizedString } from 'vs/platform/action/common/action'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; class ToggleBreakpointAction extends Action2 { constructor() { @@ -39,6 +41,7 @@ class ToggleBreakpointAction extends Action2 { ...nls.localize2('toggleBreakpointAction', "Debug: Toggle Breakpoint"), mnemonicTitle: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint"), }, + f1: true, precondition: CONTEXT_DEBUGGERS_AVAILABLE, keybinding: { when: ContextKeyExpr.or(EditorContextKeys.editorTextFocus, CONTEXT_DISASSEMBLY_VIEW_FOCUS), @@ -302,7 +305,12 @@ export class RunToCursorAction extends EditorAction { id: RunToCursorAction.ID, label: RunToCursorAction.LABEL.value, alias: 'Debug: Run to Cursor', - precondition: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, PanelFocusContext.toNegated(), ContextKeyExpr.or(EditorContextKeys.editorTextFocus, CONTEXT_DISASSEMBLY_VIEW_FOCUS)), + precondition: ContextKeyExpr.and( + CONTEXT_DEBUGGERS_AVAILABLE, + PanelFocusContext.toNegated(), + ContextKeyExpr.or(EditorContextKeys.editorTextFocus, CONTEXT_DISASSEMBLY_VIEW_FOCUS), + CONTEXT_IN_CHAT_SESSION.negate() + ), contextMenuOpts: { group: 'debug', order: 2, @@ -343,7 +351,10 @@ export class SelectionToReplAction extends EditorAction { id: SelectionToReplAction.ID, label: SelectionToReplAction.LABEL.value, alias: 'Debug: Evaluate in Console', - precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), + precondition: ContextKeyExpr.and( + CONTEXT_IN_DEBUG_MODE, + EditorContextKeys.editorTextFocus, + CONTEXT_IN_CHAT_SESSION.negate()), contextMenuOpts: { group: 'debug', order: 0 @@ -368,8 +379,8 @@ export class SelectionToReplAction extends EditorAction { text = editor.getModel().getValueInRange(selection); } - await session.addReplExpression(viewModel.focusedStackFrame, text); - await viewsService.openView(REPL_VIEW_ID, false); + const replView = await viewsService.openView(REPL_VIEW_ID, false) as Repl | undefined; + replView?.sendReplInput(text); } } @@ -383,7 +394,10 @@ export class SelectionToWatchExpressionsAction extends EditorAction { id: SelectionToWatchExpressionsAction.ID, label: SelectionToWatchExpressionsAction.LABEL.value, alias: 'Debug: Add to Watch', - precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), + precondition: ContextKeyExpr.and( + CONTEXT_IN_DEBUG_MODE, + EditorContextKeys.editorTextFocus, + CONTEXT_IN_CHAT_SESSION.negate()), contextMenuOpts: { group: 'debug', order: 1 diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 4ab2ba9d..96c0952b 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -6,7 +6,7 @@ import { addDisposableListener, isKeyboardEvent } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { distinct } from 'vs/base/common/arrays'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; @@ -37,7 +37,7 @@ import { IModelDeltaDecoration, ITextModel, InjectedTextCursorStops } from 'vs/e import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IModelService } from 'vs/editor/common/services/model'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import * as nls from 'vs/nls'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; @@ -66,12 +66,7 @@ export const debugInlineForeground = registerColor('editor.inlineValuesForegroun hcLight: '#00000080' }, nls.localize('editor.inlineValuesForeground', "Color for the debug inline value text.")); -export const debugInlineBackground = registerColor('editor.inlineValuesBackground', { - dark: '#ffc80033', - light: '#ffc80033', - hcDark: '#ffc80033', - hcLight: '#ffc80033' -}, nls.localize('editor.inlineValuesBackground', "Color for the debug inline value background.")); +export const debugInlineBackground = registerColor('editor.inlineValuesBackground', '#ffc80033', nls.localize('editor.inlineValuesBackground', "Color for the debug inline value background.")); class InlineSegment { constructor(public column: number, public text: string) { @@ -126,7 +121,7 @@ function replaceWsWithNoBreakWs(str: string): string { return str.replace(/[ \t]/g, strings.noBreakWhitespace); } -function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray, ranges: Range[], model: ITextModel, wordToLineNumbersMap: Map): IModelDeltaDecoration[] { +function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray, ranges: Range[], model: ITextModel, wordToLineNumbersMap: Map) { const nameValueMap = new Map(); for (const expr of expressions) { nameValueMap.set(expr.name, expr.value); @@ -156,17 +151,14 @@ function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray { - const contentText = names.sort((first, second) => { + return [...lineToNamesMap].map(([line, names]) => ({ + line, + variables: names.sort((first, second) => { const content = model.getLineContent(line); return content.indexOf(first) - content.indexOf(second); - }).map(name => `${name} = ${nameValueMap.get(name)}`).join(', '); - decorations.push(...createInlineValueDecoration(line, contentText)); - }); - - return decorations; + }).map(name => ({ name, value: nameValueMap.get(name)! })) + })); } function getWordToLineNumbersMap(model: ITextModel, lineNumber: number, result: Map) { @@ -208,7 +200,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private toDispose: IDisposable[]; private hoverWidget: DebugHoverWidget; - private hoverPosition: Position | null = null; + private hoverPosition?: { position: Position; event: IMouseEvent }; private mouseDown = false; private exceptionWidgetVisible: IContextKey; private gutterIsHovered = false; @@ -341,7 +333,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (debugHoverWasVisible && this.hoverPosition) { // If the debug hover was visible immediately show the editor hover for the alt transition to be smooth - this.showEditorHover(this.hoverPosition, false); + this.showEditorHover(this.hoverPosition.position, false); } const onKeyUp = new DomEmitter(ownerDocument, 'keyup'); @@ -361,14 +353,14 @@ export class DebugEditorContribution implements IDebugEditorContribution { }); } - async showHover(position: Position, focus: boolean): Promise { + async showHover(position: Position, focus: boolean, mouseEvent?: IMouseEvent): Promise { // normally will already be set in `showHoverScheduler`, but public callers may hit this directly: this.preventDefaultEditorHover(); const sf = this.debugService.getViewModel().focusedStackFrame; const model = this.editor.getModel(); if (sf && model && this.uriIdentityService.extUri.isEqual(sf.source.uri, model.uri)) { - const result = await this.hoverWidget.showAt(position, focus); + const result = await this.hoverWidget.showAt(position, focus, mouseEvent); if (result === ShowDebugHoverResult.NOT_AVAILABLE) { // When no expression available fallback to editor hover this.showEditorHover(position, focus); @@ -383,7 +375,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { return; } - const hoverController = this.editor.getContribution(HoverController.ID); + const hoverController = this.editor.getContribution(ContentHoverController.ID); hoverController?.hideContentHover(); this.editor.updateOptions({ hover: { enabled: false } }); @@ -397,7 +389,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } private showEditorHover(position: Position, focus: boolean) { - const hoverController = this.editor.getContribution(HoverController.ID); + const hoverController = this.editor.getContribution(ContentHoverController.ID); const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); // enable the editor hover, otherwise the content controller will see it // as disabled and hide it on the first mouse move (#193149) @@ -438,7 +430,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private get showHoverScheduler() { const scheduler = new RunOnceScheduler(() => { if (this.hoverPosition && !this.altPressed) { - this.showHover(this.hoverPosition, false); + this.showHover(this.hoverPosition.position, false, this.hoverPosition.event); } }, this.hoverDelay); this.toDispose.push(scheduler); @@ -493,8 +485,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { } if (target.type === MouseTargetType.CONTENT_TEXT) { - if (target.position && !Position.equals(target.position, this.hoverPosition)) { - this.hoverPosition = target.position; + if (target.position && !Position.equals(target.position, this.hoverPosition?.position || null) && !this.hoverWidget.isInSafeTriangle(mouseEvent.event.posx, mouseEvent.event.posy)) { + this.hoverPosition = { position: target.position, event: mouseEvent.event }; // Disable the editor hover during the request to avoid flickering this.preventDefaultEditorHover(); this.showHoverScheduler.schedule(this.hoverDelay); @@ -782,10 +774,15 @@ export class DebugEditorContribution implements IDebugEditorContribution { // old "one-size-fits-all" strategy const scopes = await stackFrame.getMostSpecificScopes(stackFrame.range); - // Get all top level variables in the scope chain - const decorationsPerScope = await Promise.all(scopes.map(async scope => { - const variables = await scope.getChildren(); + const scopesWithVariables = await Promise.all(scopes.map(async scope => + ({ scope, variables: await scope.getChildren() }))); + + // Map of inline values per line that's populated in scope order, from + // narrowest to widest. This is done to avoid duplicating values if + // they appear in multiple scopes or are shadowed (#129770, #217326) + const valuesPerLine = new Map>(); + for (const { scope, variables } of scopesWithVariables) { let scopeRange = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn); if (scope.range) { scopeRange = scopeRange.setStartPosition(scope.range.startLineNumber, scope.range.startColumn); @@ -797,12 +794,25 @@ export class DebugEditorContribution implements IDebugEditorContribution { this._wordToLineNumbersMap.ensureRangePopulated(range); } - return createInlineValueDecorationsInsideRange(variables, ownRanges, model, this._wordToLineNumbersMap.value); - })); + const mapped = createInlineValueDecorationsInsideRange(variables, ownRanges, model, this._wordToLineNumbersMap.value); + for (const { line, variables } of mapped) { + let values = valuesPerLine.get(line); + if (!values) { + values = new Map(); + valuesPerLine.set(line, values); + } + + for (const { name, value } of variables) { + if (!values.has(name)) { + values.set(name, value); + } + } + } + } - allDecorations = distinct(decorationsPerScope.flat(), - // Deduplicate decorations since same variable can appear in multiple scopes, leading to duplicated decorations #129770 - decoration => `${decoration.range.startLineNumber}:${decoration?.options.after?.content}`); + allDecorations = [...valuesPerLine.entries()].flatMap(([line, values]) => + createInlineValueDecoration(line, [...values].map(([n, v]) => `${n} = ${v}`).join(', ')) + ); } if (cts.token.isCancellationRequested) { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugHover.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugHover.ts index b534c3fb..514c2291 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -5,6 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -82,7 +83,12 @@ export class DebugHoverWidget implements IContentWidget { // editor.IContentWidget.allowEditorOverflow readonly allowEditorOverflow = true; - private _isVisible: boolean; + // todo@connor4312: move more properties that are only valid while a hover + // is happening into `_isVisible` + private _isVisible?: { + store: lifecycle.DisposableStore; + }; + private safeTriangle?: dom.SafeTriangle; private showCancellationSource?: CancellationTokenSource; private domNode!: HTMLElement; private tree!: AsyncDataTree; @@ -115,7 +121,6 @@ export class DebugHoverWidget implements IContentWidget { ) { this.toDispose = []; - this._isVisible = false; this.showAtPosition = null; this.positionPreference = [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]; this.debugHoverComputer = this.instantiationService.createInstance(DebugHoverComputer, this.editor); @@ -213,7 +218,7 @@ export class DebugHoverWidget implements IContentWidget { } isVisible(): boolean { - return this._isVisible; + return !!this._isVisible; } willBeVisible(): boolean { @@ -228,8 +233,16 @@ export class DebugHoverWidget implements IContentWidget { return this.domNode; } - async showAt(position: Position, focus: boolean): Promise { - this.showCancellationSource?.cancel(); + /** + * Gets whether the given coordinates are in the safe triangle formed from + * the position at which the hover was initiated. + */ + isInSafeTriangle(x: number, y: number) { + return this._isVisible && !!this.safeTriangle?.contains(x, y); + } + + async showAt(position: Position, focus: boolean, mouseEvent?: IMouseEvent): Promise { + this.showCancellationSource?.dispose(true); const cancellationSource = this.showCancellationSource = new CancellationTokenSource(); const session = this.debugService.getViewModel().focusedSession; @@ -269,7 +282,7 @@ export class DebugHoverWidget implements IContentWidget { options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS }]); - return this.doShow(result.range.getStartPosition(), expression, focus); + return this.doShow(result.range.getStartPosition(), expression, focus, mouseEvent); } private static readonly _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({ @@ -277,20 +290,22 @@ export class DebugHoverWidget implements IContentWidget { className: 'hoverHighlight' }); - private async doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): Promise { + private async doShow(position: Position, expression: IExpression, focus: boolean, mouseEvent: IMouseEvent | undefined): Promise { if (!this.domNode) { this.create(); } this.showAtPosition = position; - this._isVisible = true; + const store = new lifecycle.DisposableStore(); + this._isVisible = { store }; - if (!expression.hasChildren || forceValueHover) { + if (!expression.hasChildren) { this.complexValueContainer.hidden = true; this.valueContainer.hidden = false; - renderExpressionValue(expression, this.valueContainer, { + renderExpressionValue(store, expression, this.valueContainer, { showChanged: false, - colorize: true + colorize: true, + hover: false, }, this.hoverService); this.valueContainer.title = ''; this.editor.layoutContentWidget(this); @@ -312,6 +327,7 @@ export class DebugHoverWidget implements IContentWidget { this.tree.scrollTop = 0; this.tree.scrollLeft = 0; this.complexValueContainer.hidden = false; + this.safeTriangle = mouseEvent && new dom.SafeTriangle(mouseEvent.posx, mouseEvent.posy, this.domNode); if (focus) { this.editor.render(); @@ -370,7 +386,7 @@ export class DebugHoverWidget implements IContentWidget { hide(): void { if (this.showCancellationSource) { - this.showCancellationSource.cancel(); + this.showCancellationSource.dispose(true); this.showCancellationSource = undefined; } @@ -381,7 +397,9 @@ export class DebugHoverWidget implements IContentWidget { if (dom.isAncestorOfActiveElement(this.domNode)) { this.editor.focus(); } - this._isVisible = false; + this._isVisible.store.dispose(); + this._isVisible = undefined; + this.highlightDecorations.clear(); this.editor.layoutContentWidget(this); this.positionPreference = [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]; @@ -440,8 +458,10 @@ interface IDebugHoverComputeResult { } class DebugHoverComputer { - private _currentRange: Range | undefined; - private _currentExpression: string | undefined; + private _current?: { + range: Range; + expression: string; + }; constructor( private editor: ICodeEditor, @@ -463,30 +483,35 @@ class DebugHoverComputer { } const { range, matchingExpression } = result; - const rangeChanged = this._currentRange ? - !this._currentRange.equalsRange(range) : - true; - this._currentExpression = matchingExpression; - this._currentRange = Range.lift(range); - return { rangeChanged, range: this._currentRange }; + const rangeChanged = !this._current?.range.equalsRange(range); + this._current = { expression: matchingExpression, range: Range.lift(range) }; + return { rangeChanged, range: this._current.range }; } async evaluate(session: IDebugSession): Promise { - if (!this._currentExpression) { + if (!this._current) { this.logService.error('No expression to evaluate'); return; } + const textModel = this.editor.getModel(); + const debugSource = textModel && session.getSourceForUri(textModel?.uri); + if (session.capabilities.supportsEvaluateForHovers) { - const expression = new Expression(this._currentExpression); - await expression.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover'); + const expression = new Expression(this._current.expression); + await expression.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover', undefined, debugSource ? { + line: this._current.range.startLineNumber, + column: this._current.range.startColumn, + source: debugSource.raw, + } : undefined); return expression; } else { const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; if (focusedStackFrame) { return await findExpressionInStackFrame( focusedStackFrame, - coalesce(this._currentExpression.split('.').map(word => word.trim()))); + coalesce(this._current.expression.split('.').map(word => word.trim())) + ); } } diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugService.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugService.ts index d09eada1..8c52537a 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -51,6 +51,7 @@ import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; +import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -112,6 +113,7 @@ export class DebugService implements IDebugService { @IQuickInputService private readonly quickInputService: IQuickInputService, @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ITestService private readonly testService: ITestService, ) { this.breakpointsToSendOnResourceSaved = new Set(); @@ -202,8 +204,8 @@ export class DebugService implements IDebugService { this.disposables.add(extensionService.onWillStop(evt => { evt.veto( - this.stopSession(undefined).then(() => false), - nls.localize('stoppingDebug', 'Stopping debug sessions...'), + this.model.getSessions().length > 0, + nls.localize('active debug session', 'A debug session is still running.'), ); })); @@ -839,6 +841,21 @@ export class DebugService implements IDebugService { } }; + // For debug sessions spawned by test runs, cancel the test run and stop + // the session, then start the test run again; tests have no notion of restarts. + if (session.correlatedTestRun) { + if (!session.correlatedTestRun.completedAt) { + this.testService.cancelTestRun(session.correlatedTestRun.id); + await Event.toPromise(session.correlatedTestRun.onComplete); + // todo@connor4312 is there any reason to wait for the debug session to + // terminate? I don't think so, test extension should already handle any + // state conflicts... + } + + this.testService.runResolvedTests(session.correlatedTestRun.request); + return; + } + if (session.capabilities.supportsRestartRequest) { const taskResult = await runTasks(); if (taskResult === TaskRunResult.Success) { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSession.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSession.ts index e2d54e9a..453bcd86 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -29,7 +29,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; -import { AdapterEndEvent, IBreakpoint, IConfig, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugConfiguration, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, LoadedSourceEvent, State, VIEWLET_ID, isFrameDeemphasized } from 'vs/workbench/contrib/debug/common/debug'; +import { AdapterEndEvent, IBreakpoint, IConfig, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugConfiguration, IDebugLocationReferenced, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, LoadedSourceEvent, State, VIEWLET_ID, isFrameDeemphasized } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { DebugModel, ExpressionContainer, MemoryRegion, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; @@ -42,6 +42,10 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { getActiveWindow } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; import { isDefined } from 'vs/base/common/types'; +import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; +import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; +import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; const TRIGGERED_BREAKPOINT_MAX_DELAY = 1500; @@ -66,6 +70,11 @@ export class DebugSession implements IDebugSession, IDisposable { private stoppedDetails: IRawStoppedDetails[] = []; private readonly statusQueue = this.rawListeners.add(new ThreadStatusScheduler()); + /** Test run this debug session was spawned by */ + public readonly correlatedTestRun?: LiveTestResult; + /** Whether we terminated the correlated run yet. Used so a 2nd terminate request goes through to the underlying session. */ + private didTerminateTestRun?: boolean; + private readonly _onDidChangeState = new Emitter(); private readonly _onDidEndAdapter = new Emitter(); @@ -76,7 +85,7 @@ export class DebugSession implements IDebugSession, IDisposable { private readonly _onDidProgressEnd = new Emitter(); private readonly _onDidInvalidMemory = new Emitter(); - private readonly _onDidChangeREPLElements = new Emitter(); + private readonly _onDidChangeREPLElements = new Emitter(); private _name: string | undefined; private readonly _onDidChangeName = new Emitter(); @@ -106,7 +115,10 @@ export class DebugSession implements IDebugSession, IDisposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @ITestService private readonly testService: ITestService, + @ITestResultService testResultService: ITestResultService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, ) { this._options = options || {}; this.parentSession = this._options.parentSession; @@ -118,7 +130,7 @@ export class DebugSession implements IDebugSession, IDisposable { const toDispose = this.globalDisposables; const replListener = toDispose.add(new MutableDisposable()); - replListener.value = this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire()); + replListener.value = this.repl.onDidChangeElements((e) => this._onDidChangeREPLElements.fire(e)); if (lifecycleService) { toDispose.add(lifecycleService.onWillShutdown(() => { this.shutdown(); @@ -126,6 +138,16 @@ export class DebugSession implements IDebugSession, IDisposable { })); } + // Cast here, it's not possible to reference a hydrated result in this code path. + this.correlatedTestRun = options?.testRun + ? (testResultService.getResult(options.testRun.runId) as LiveTestResult) + : this.parentSession?.correlatedTestRun; + + if (this.correlatedTestRun) { + // Listen to the test completing because the user might have taken the cancel action rather than stopping the session. + toDispose.add(this.correlatedTestRun.onComplete(() => this.terminate())); + } + const compoundRoot = this._options.compoundRoot; if (compoundRoot) { toDispose.add(compoundRoot.onDidSessionStop(() => this.terminate())); @@ -156,7 +178,7 @@ export class DebugSession implements IDebugSession, IDisposable { // remove its parent, if it's still running if (!this.hasSeparateRepl() && this.raw?.isInShutdown === false) { this.repl = this.repl.clone(); - replListener.value = this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire()); + replListener.value = this.repl.onDidChangeElements((e) => this._onDidChangeREPLElements.fire(e)); this.parentSession = undefined; } })); @@ -218,7 +240,9 @@ export class DebugSession implements IDebugSession, IDisposable { get autoExpandLazyVariables(): boolean { // This tiny helper avoids converting the entire debug model to use service injection - return this.configurationService.getValue('debug').autoExpandLazyVariables; + const screenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); + const value = this.configurationService.getValue('debug').autoExpandLazyVariables; + return value === 'auto' && screenReaderOptimized || value === 'on'; } setConfiguration(configuration: { resolved: IConfig; unresolved: IConfig | undefined }) { @@ -271,7 +295,7 @@ export class DebugSession implements IDebugSession, IDisposable { return this._onDidEndAdapter.event; } - get onDidChangeReplElements(): Event { + get onDidChangeReplElements(): Event { return this._onDidChangeREPLElements.event; } @@ -387,6 +411,9 @@ export class DebugSession implements IDebugSession, IDisposable { this.cancelAllRequests(); if (this._options.lifecycleManagedByParent && this.parentSession) { await this.parentSession.terminate(restart); + } else if (this.correlatedTestRun && !this.correlatedTestRun.completedAt && !this.didTerminateTestRun) { + this.didTerminateTestRun = true; + this.testService.cancelTestRun(this.correlatedTestRun.id); } else if (this.raw) { if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') { await this.raw.terminate(restart); @@ -662,12 +689,12 @@ export class DebugSession implements IDebugSession, IDisposable { return this.raw.variables({ variablesReference, filter, start, count }, token); } - evaluate(expression: string, frameId: number, context?: string): Promise { + evaluate(expression: string, frameId: number, context?: string, location?: { line: number; column: number; source: DebugProtocol.Source }): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'evaluate')); } - return this.raw.evaluate({ expression, frameId, context }); + return this.raw.evaluate({ expression, frameId, context, line: location?.line, column: location?.column, source: location?.source }); } async restartFrame(frameId: number, threadId: number): Promise { @@ -879,6 +906,20 @@ export class DebugSession implements IDebugSession, IDisposable { return this.raw.writeMemory({ memoryReference, offset, allowPartial, data }); } + async resolveLocationReference(locationReference: number): Promise { + if (!this.raw) { + throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'locations')); + } + + const location = await this.raw.locations({ locationReference }); + if (!location?.body) { + throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'locations')); + } + + const source = this.getSource(location.body.source); + return { column: 1, ...location.body, source }; + } + //---- threads getThread(threadId: number): Thread | undefined { @@ -1347,7 +1388,7 @@ export class DebugSession implements IDebugSession, IDisposable { } const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; - if (!focusedStackFrame || !isFrameDeemphasized(focusedStackFrame)) { + if (!focusedStackFrame || isFrameDeemphasized(focusedStackFrame)) { // The top stack frame can be deemphesized so try to focus again #68616 focus(); } @@ -1498,8 +1539,8 @@ export class DebugSession implements IDebugSession, IDisposable { this.repl.removeReplExpressions(); } - async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { - await this.repl.addReplExpression(this, stackFrame, name); + async addReplExpression(stackFrame: IStackFrame | undefined, expression: string): Promise { + await this.repl.addReplExpression(this, stackFrame, expression); // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. this.debugService.getViewModel().updateViews(); } diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts index 74ca56b6..bc2417b2 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts @@ -21,7 +21,7 @@ export async function showDebugSessionMenu(accessor: ServicesAccessor, selectAnd const commandService = accessor.get(ICommandService); const localDisposableStore = new DisposableStore(); - const quickPick = quickInputService.createQuickPick(); + const quickPick = quickInputService.createQuickPick({ useSeparators: true }); localDisposableStore.add(quickPick); quickPick.matchOnLabel = quickPick.matchOnDescription = quickPick.matchOnDetail = quickPick.sortByLabel = false; quickPick.placeholder = nls.localize('moveFocusedView.selectView', 'Search debug sessions by name'); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSettingMigration.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSettingMigration.ts new file mode 100644 index 00000000..cee1d887 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugSettingMigration.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; + +Registry.as(Extensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'debug.autoExpandLazyVariables', + migrateFn: (value: boolean) => { + let newValue: string | undefined; + if (value === true) { + newValue = 'on'; + } else if (value === false) { + newValue = 'off'; + } + return [ + ['debug.autoExpandLazyVariables', { value: newValue }], + ]; + } + }]); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index 15de8bc4..d9c1fdc9 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -3,35 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { disposableTimeout } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { createErrorWithActions } from 'vs/base/common/errorMessage'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import severity from 'vs/base/common/severity'; -import { Event } from 'vs/base/common/event'; -import { Markers } from 'vs/workbench/contrib/markers/common/markers'; -import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; +import * as nls from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { ITaskEvent, TaskEventKind, ITaskIdentifier, Task } from 'vs/workbench/contrib/tasks/common/tasks'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { createErrorWithActions } from 'vs/base/common/errorMessage'; -import { Action } from 'vs/base/common/actions'; +import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; +import { Markers } from 'vs/workbench/contrib/markers/common/markers'; +import { ConfiguringTask, CustomTask, ITaskEvent, ITaskIdentifier, Task, TaskEventKind } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -function once(match: (e: ITaskEvent) => boolean, event: Event): Event { - return (listener, thisArgs = null, disposables?) => { - const result = event(e => { - if (match(e)) { - result.dispose(); - return listener.call(thisArgs, e); - } - }, null, disposables); - return result; - }; -} +const onceFilter = (event: Event, filter: (e: ITaskEvent) => boolean) => Event.once(Event.filter(event, filter)); export const enum TaskRunResult { Failure, @@ -39,10 +33,17 @@ export const enum TaskRunResult { } const DEBUG_TASK_ERROR_CHOICE_KEY = 'debug.taskerrorchoice'; +const ABORT_LABEL = nls.localize('abort', "Abort"); +const DEBUG_ANYWAY_LABEL = nls.localize({ key: 'debugAnyway', comment: ['&& denotes a mnemonic'] }, "&&Debug Anyway"); +const DEBUG_ANYWAY_LABEL_NO_MEMO = nls.localize('debugAnywayNoMemo', "Debug Anyway"); -export class DebugTaskRunner { +interface IRunnerTaskSummary extends ITaskSummary { + cancelled?: boolean; +} + +export class DebugTaskRunner implements IDisposable { - private canceled = false; + private globalCancellation = new CancellationTokenSource(); constructor( @ITaskService private readonly taskService: ITaskService, @@ -51,18 +52,26 @@ export class DebugTaskRunner { @IViewsService private readonly viewsService: IViewsService, @IDialogService private readonly dialogService: IDialogService, @IStorageService private readonly storageService: IStorageService, - @ICommandService private readonly commandService: ICommandService + @ICommandService private readonly commandService: ICommandService, + @IProgressService private readonly progressService: IProgressService, ) { } cancel(): void { - this.canceled = true; + this.globalCancellation.dispose(true); + this.globalCancellation = new CancellationTokenSource(); } - async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | ITaskIdentifier | undefined): Promise { + public dispose(): void { + this.globalCancellation.dispose(true); + } + + async runTaskAndCheckErrors( + root: IWorkspaceFolder | IWorkspace | undefined, + taskId: string | ITaskIdentifier | undefined, + ): Promise { try { - this.canceled = false; - const taskSummary = await this.runTask(root, taskId); - if (this.canceled || (taskSummary && taskSummary.exitCode === undefined)) { + const taskSummary = await this.runTask(root, taskId, this.globalCancellation.token); + if (taskSummary && (taskSummary.exitCode === undefined || taskSummary.cancelled)) { // User canceled, either debugging, or the prelaunch task return TaskRunResult.Failure; } @@ -101,7 +110,7 @@ export class DebugTaskRunner { message, buttons: [ { - label: nls.localize({ key: 'debugAnyway', comment: ['&& denotes a mnemonic'] }, "&&Debug Anyway"), + label: DEBUG_ANYWAY_LABEL, run: () => DebugChoice.DebugAnyway }, { @@ -110,7 +119,7 @@ export class DebugTaskRunner { } ], cancelButton: { - label: nls.localize('abort', "Abort"), + label: ABORT_LABEL, run: () => DebugChoice.Cancel }, checkbox: { @@ -182,7 +191,7 @@ export class DebugTaskRunner { } } - async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | ITaskIdentifier | undefined): Promise { + async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | ITaskIdentifier | undefined, token = this.globalCancellation.token): Promise { if (!taskId) { return Promise.resolve(null); } @@ -200,23 +209,42 @@ export class DebugTaskRunner { // If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340 let taskStarted = false; + const store = new DisposableStore(); const getTaskKey = (t: Task) => t.getKey() ?? t.getMapKey(); const taskKey = getTaskKey(task); - const inactivePromise: Promise = new Promise((c) => once(e => { - // When a task isBackground it will go inactive when it is safe to launch. - // But when a background task is terminated by the user, it will also fire an inactive event. - // This means that we will not get to see the real exit code from running the task (undefined when terminated by the user). - // Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this. - return (e.kind === TaskEventKind.Inactive - || (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined)) - && getTaskKey(e.__task) === taskKey; - }, this.taskService.onDidStateChange)(e => { - taskStarted = true; - c(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null); - })); - - const promise: Promise = this.taskService.getActiveTasks().then(async (tasks): Promise => { + const inactivePromise: Promise = new Promise((resolve) => store.add( + onceFilter(this.taskService.onDidStateChange, e => { + // When a task isBackground it will go inactive when it is safe to launch. + // But when a background task is terminated by the user, it will also fire an inactive event. + // This means that we will not get to see the real exit code from running the task (undefined when terminated by the user). + // Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this. + return (e.kind === TaskEventKind.Inactive + || (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined)) + && getTaskKey(e.__task) === taskKey; + })(e => { + taskStarted = true; + resolve(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null); + }), + )); + + store.add( + onceFilter(this.taskService.onDidStateChange, e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && getTaskKey(e.__task) === taskKey + )(() => { + // Task is active, so everything seems to be fine, no need to prompt after 10 seconds + // Use case being a slow running task should not be prompted even though it takes more than 10 seconds + taskStarted = true; + }) + ); + + const didAcquireInput = store.add(new Emitter()); + store.add(onceFilter( + this.taskService.onDidStateChange, + e => (e.kind === TaskEventKind.AcquiredInput) && getTaskKey(e.__task) === taskKey + )(() => didAcquireInput.fire())); + + const taskDonePromise: Promise = this.taskService.getActiveTasks().then(async (tasks): Promise => { if (tasks.find(t => getTaskKey(t) === taskKey)) { + didAcquireInput.fire(); // Check that the task isn't busy and if it is, wait for it const busyTasks = await this.taskService.getBusyTasks(); if (busyTasks.find(t => getTaskKey(t) === taskKey)) { @@ -226,11 +254,7 @@ export class DebugTaskRunner { // task is already running and isn't busy - nothing to do. return Promise.resolve(null); } - once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && getTaskKey(e.__task) === taskKey, this.taskService.onDidStateChange)(() => { - // Task is active, so everything seems to be fine, no need to prompt after 10 seconds - // Use case being a slow running task should not be prompted even though it takes more than 10 seconds - taskStarted = true; - }); + const taskPromise = this.taskService.run(task); if (task.configurationProperties.isBackground) { return inactivePromise; @@ -239,28 +263,59 @@ export class DebugTaskRunner { return taskPromise.then(x => x ?? null); }); - return new Promise((c, e) => { - const waitForInput = new Promise(resolve => once(e => (e.kind === TaskEventKind.AcquiredInput) && getTaskKey(e.__task) === taskKey, this.taskService.onDidStateChange)(() => { - resolve(); - })); - - promise.then(result => { + const result = new Promise((resolve, reject) => { + taskDonePromise.then(result => { taskStarted = true; - c(result); - }, error => e(error)); + resolve(result); + }, error => reject(error)); + + store.add(token.onCancellationRequested(() => { + resolve({ exitCode: undefined, cancelled: true }); + this.taskService.terminate(task).catch(() => { }); + })); - waitForInput.then(() => { + // Start the timeouts once a terminal has been acquired + store.add(didAcquireInput.event(() => { const waitTime = task.configurationProperties.isBackground ? 5000 : 10000; - setTimeout(() => { + // Error shown if there's a background task with no problem matcher that doesn't exit quickly + store.add(disposableTimeout(() => { if (!taskStarted) { - const errorMessage = typeof taskId === 'string' - ? nls.localize('taskNotTrackedWithTaskId', "The task '{0}' cannot be tracked. Make sure to have a problem matcher defined.", taskId) - : nls.localize('taskNotTracked', "The task '{0}' cannot be tracked. Make sure to have a problem matcher defined.", JSON.stringify(taskId)); - e({ severity: severity.Error, message: errorMessage }); + const errorMessage = nls.localize('taskNotTracked', "The task '{0}' has not exited and doesn't have a 'problemMatcher' defined. Make sure to define a problem matcher for watch tasks.", typeof taskId === 'string' ? taskId : JSON.stringify(taskId)); + reject({ severity: severity.Error, message: errorMessage }); } - }, waitTime); - }); + }, waitTime)); + + // Notification shown on any task taking a while to resolve + store.add(disposableTimeout(() => { + const message = nls.localize('runningTask', "Waiting for preLaunchTask '{0}'...", task.configurationProperties.name); + const buttons = [DEBUG_ANYWAY_LABEL_NO_MEMO, ABORT_LABEL]; + const canConfigure = task instanceof CustomTask || task instanceof ConfiguringTask; + if (canConfigure) { + buttons.splice(1, 0, nls.localize('configureTask', "Configure Task")); + } + + this.progressService.withProgress( + { location: ProgressLocation.Notification, title: message, buttons }, + () => result.catch(() => { }), + (choice) => { + if (choice === undefined) { + // no-op, keep waiting + } else if (choice === 0) { // debug anyway + resolve({ exitCode: 0 }); + } else { // abort or configure + resolve({ exitCode: undefined, cancelled: true }); + this.taskService.terminate(task).catch(() => { }); + if (canConfigure && choice === 1) { // configure + this.taskService.openConfig(task as CustomTask); + } + } + } + ); + }, 10_000)); + })); }); + + return result.finally(() => store.dispose()); } } diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index add88e75..7711f4cc 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -10,7 +10,7 @@ import { Action, IAction, IRunEvent, WorkbenchActionExecutedClassification, Work import * as arrays from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as errors from 'vs/base/common/errors'; -import { DisposableStore, dispose, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose, IDisposable, markAsSingleton, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/debugToolBar'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -351,9 +351,9 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl const instantiationService = accessor.get(IInstantiationService); const contextMenuService = accessor.get(IContextMenuService); - const menu = menuService.createMenu(MenuId.DebugToolBarStop, contextKeyService); + const menu = menuService.getMenuActions(MenuId.DebugToolBarStop, contextKeyService, { shouldForwardArgs: true }); const secondary: IAction[] = []; - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, secondary); + createAndFillInActionBarActions(menu, secondary); if (!secondary.length) { return undefined; @@ -401,7 +401,7 @@ const registerDebugToolBarItem = (id: string, title: string | ICommandActionTitl })); }; -MenuRegistry.onDidChangeMenu(e => { +markAsSingleton(MenuRegistry.onDidChangeMenu(e => { // In case the debug toolbar is docked we need to make sure that the docked toolbar has the up to date commands registered #115945 if (e.has(MenuId.DebugToolBar)) { dispose(debugViewTitleItems); @@ -413,7 +413,7 @@ MenuRegistry.onDidChangeMenu(e => { })); } } -}); +})); const CONTEXT_TOOLBAR_COMMAND_CENTER = ContextKeyExpr.equals('config.debug.toolBarLocation', 'commandCenter'); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index 92f139a3..62923aed 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -247,6 +247,8 @@ export class DisassemblyView extends EditorPane { } )) as WorkbenchTable; + this._disassembledInstructions.domNode.classList.add('disassembly-view'); + if (this.focusedInstructionReference) { this.reloadDisassembly(this.focusedInstructionReference, 0); } @@ -661,8 +663,7 @@ class BreakpointRenderer implements ITableRenderer * and added as a child of the returned . + * If a `hoverBehavior` is passed, hovers may be added using the workbench hover service. + * This should be preferred for new code where hovers are desirable. */ - linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder, includeFulltext?: boolean): HTMLElement { + linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder, includeFulltext?: boolean, hoverBehavior?: DebugLinkHoverBehaviorTypeData): HTMLElement { if (splitLines) { const lines = text.split('\n'); for (let i = 0; i < lines.length - 1; i++) { @@ -88,13 +111,13 @@ export class LinkDetector { container.appendChild(document.createTextNode(part.value)); break; case 'web': - container.appendChild(this.createWebLink(includeFulltext ? text : undefined, part.value)); + container.appendChild(this.createWebLink(includeFulltext ? text : undefined, part.value, hoverBehavior)); break; case 'path': { const path = part.captures[0]; const lineNumber = part.captures[1] ? Number(part.captures[1]) : 0; const columnNumber = part.captures[2] ? Number(part.captures[2]) : 0; - container.appendChild(this.createPathLink(includeFulltext ? text : undefined, part.value, path, lineNumber, columnNumber, workspaceFolder)); + container.appendChild(this.createPathLink(includeFulltext ? text : undefined, part.value, path, lineNumber, columnNumber, workspaceFolder, hoverBehavior)); break; } } @@ -105,7 +128,25 @@ export class LinkDetector { return container; } - private createWebLink(fulltext: string | undefined, url: string): Node { + /** + * Linkifies a location reference. + */ + linkifyLocation(text: string, locationReference: number, session: IDebugSession, hoverBehavior?: DebugLinkHoverBehaviorTypeData) { + const link = this.createLink(text); + this.decorateLink(link, undefined, text, hoverBehavior, async (preserveFocus: boolean) => { + const location = await session.resolveLocationReference(locationReference); + await location.source.openInEditor(this.editorService, { + startLineNumber: location.line, + startColumn: location.column, + endLineNumber: location.endLine ?? location.line, + endColumn: location.endColumn ?? location.column, + }, preserveFocus); + }); + + return link; + } + + private createWebLink(fulltext: string | undefined, url: string, hoverBehavior?: DebugLinkHoverBehaviorTypeData): Node { const link = this.createLink(url); let uri = URI.parse(url); @@ -119,7 +160,7 @@ export class LinkDetector { }); } - this.decorateLink(link, uri, fulltext, async () => { + this.decorateLink(link, uri, fulltext, hoverBehavior, async () => { if (uri.scheme === Schemas.file) { // Just using fsPath here is unsafe: https://github.com/microsoft/vscode/issues/109076 @@ -149,7 +190,7 @@ export class LinkDetector { return link; } - private createPathLink(fulltext: string | undefined, text: string, path: string, lineNumber: number, columnNumber: number, workspaceFolder: IWorkspaceFolder | undefined): Node { + private createPathLink(fulltext: string | undefined, text: string, path: string, lineNumber: number, columnNumber: number, workspaceFolder: IWorkspaceFolder | undefined, hoverBehavior?: DebugLinkHoverBehaviorTypeData): Node { if (path[0] === '/' && path[1] === '/') { // Most likely a url part which did not match, for example ftp://path. return document.createTextNode(text); @@ -162,7 +203,7 @@ export class LinkDetector { } const uri = workspaceFolder.toResource(path); const link = this.createLink(text); - this.decorateLink(link, uri, fulltext, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } })); + this.decorateLink(link, uri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } })); return link; } @@ -180,7 +221,7 @@ export class LinkDetector { if (stat.isDirectory) { return; } - this.decorateLink(link, uri, fulltext, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } })); + this.decorateLink(link, uri, fulltext, hoverBehavior, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } })); }).catch(() => { // If the uri can not be resolved we should not spam the console with error, remain quite #86587 }); @@ -193,12 +234,19 @@ export class LinkDetector { return link; } - private decorateLink(link: HTMLElement, uri: URI, fulltext: string | undefined, onClick: (preserveFocus: boolean) => void) { + private decorateLink(link: HTMLElement, uri: URI | undefined, fulltext: string | undefined, hoverBehavior: DebugLinkHoverBehaviorTypeData | undefined, onClick: (preserveFocus: boolean) => void) { link.classList.add('link'); - const followLink = this.tunnelService.canTunnel(uri) ? localize('followForwardedLink', "follow link using forwarded port") : localize('followLink', "follow link"); - link.title = fulltext + const followLink = uri && this.tunnelService.canTunnel(uri) ? localize('followForwardedLink', "follow link using forwarded port") : localize('followLink', "follow link"); + const title = link.ariaLabel = fulltext ? (platform.isMacintosh ? localize('fileLinkWithPathMac', "Cmd + click to {0}\n{1}", followLink, fulltext) : localize('fileLinkWithPath', "Ctrl + click to {0}\n{1}", followLink, fulltext)) : (platform.isMacintosh ? localize('fileLinkMac', "Cmd + click to {0}", followLink) : localize('fileLink', "Ctrl + click to {0}", followLink)); + + if (hoverBehavior?.type === DebugLinkHoverBehavior.Rich) { + hoverBehavior.store.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), link, title)); + } else if (hoverBehavior?.type !== DebugLinkHoverBehavior.None) { + link.title = title; + } + link.onmousemove = (event) => { link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey); }; link.onmouseleave = () => link.classList.remove('pointer'); link.onclick = (event) => { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css b/patched-vscode/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css new file mode 100644 index 00000000..e5e0d56e --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.multiCallStackFrame { + .header { + display: flex; + + align-items: center; + height: 24px; + background: var(--vscode-multiDiffEditor-headerBackground); + border-top: 1px solid var(--vscode-multiDiffEditor-border); + color: var(--vscode-foreground); + padding: 0 5px; + } + + .title { + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &[role="link"] { + cursor: pointer; + } + + .monaco-icon-label::before { + height: auto; + } + } + + &.collapsed { + .header { + border-bottom: 1px solid var(--vscode-multiDiffEditor-border); + } + + .editorParent { + display: none; + } + } + + .collapse-button { + width: 16px; + min-height: 1px; /* show even if empty */ + line-height: 0; + + a { + cursor: pointer; + } + } + + .actions { + display: flex; + align-items: center; + gap: 8px; + margin-right: 12px; + } +} + +.multiCallStackWidget { + .multiCallStackFrameContainer { + background: none !important; + } +} + +.monaco-editor .call-stack-go-to-file-link { + text-decoration: underline; + cursor: pointer; + color: var(--vscode-editorLink-activeForeground) !important; +} diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/patched-vscode/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index fbe36b38..fc948f97 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -5,7 +5,7 @@ .monaco-workbench .debug-toolbar { position: absolute; - z-index: 3000; + z-index: 2520; /* Below quick input at 2550, above custom titlebar toolbar at 2500 */ height: 26px; display: flex; padding-left: 7px; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/media/repl.css b/patched-vscode/src/vs/workbench/contrib/debug/browser/media/repl.css index 5baf2c48..59696ad5 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -52,7 +52,7 @@ display: flex; } -.monaco-workbench .repl .repl-tree .output.expression.value-and-source .value { +.monaco-workbench .repl .repl-tree .output.expression.value-and-source .label { margin-right: 4px; } @@ -71,7 +71,8 @@ left: 2px; } -.monaco-workbench .repl .repl-tree .output.expression.value-and-source .source { +.monaco-workbench .repl .repl-tree .output.expression.value-and-source .source, +.monaco-workbench .repl .repl-tree .group .source { margin-left: auto; margin-right: 8px; cursor: pointer; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index de7be37f..894bde68 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -484,6 +484,10 @@ export class RawDebugSession implements IDisposable { return this.send('source', args); } + locations(args: DebugProtocol.LocationsArguments): Promise { + return this.send('locations', args); + } + loadedSources(args: DebugProtocol.LoadedSourcesArguments): Promise { if (this.capabilities.supportsLoadedSourcesRequest) { return this.send('loadedSources', args); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/repl.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/repl.ts index 4f183f37..b0bed607 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/repl.ts @@ -72,6 +72,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; +import { Codicon } from 'vs/base/common/codicons'; const $ = dom.$; @@ -116,6 +119,8 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { private filter: ReplFilter; private multiSessionRepl: IContextKey; private menu: IMenu; + private replDataSource: IAsyncDataSource | undefined; + private findIsOpen: boolean = false; constructor( options: IViewPaneOptions, @@ -128,10 +133,10 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { @ICodeEditorService codeEditorService: ICodeEditorService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextMenuService contextMenuService: IContextMenuService, - @IConfigurationService configurationService: IConfigurationService, + @IConfigurationService protected override readonly configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, @IEditorService private readonly editorService: IEditorService, - @IKeybindingService keybindingService: IKeybindingService, + @IKeybindingService protected override readonly keybindingService: IKeybindingService, @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @IHoverService hoverService: IHoverService, @@ -151,7 +156,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.menu = menuService.createMenu(MenuId.DebugConsoleContext, contextKeyService); this._register(this.menu); - this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); + this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 100); this.filter = new ReplFilter(); this.filter.filterQuery = filterText; this.multiSessionRepl = CONTEXT_MULTI_SESSION_REPL.bindTo(contextKeyService); @@ -345,6 +350,10 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.filterWidget.focus(); } + openFind(): void { + this.tree?.openFind(); + } + private setMode(): void { if (!this.isVisible()) { return; @@ -472,6 +481,15 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { } } + sendReplInput(input: string): void { + const session = this.tree?.getInput(); + if (session && !this.isReadonly) { + session.addReplExpression(this.debugService.getViewModel().focusedStackFrame, input); + revealLastElement(this.tree!); + this.history.add(input); + } + } + getVisibleContent(): string { let text = ''; if (this.model && this.tree) { @@ -513,10 +531,26 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this.tree?.collapseAll(); } + getDebugSession(): IDebugSession | undefined { + return this.tree?.getInput(); + } + getReplInput(): CodeEditorWidget { return this.replInput; } + getReplDataSource(): IAsyncDataSource | undefined { + return this.replDataSource; + } + + getFocusedElement(): IReplElement | undefined { + return this.tree?.getFocus()?.[0]; + } + + focusTree(): void { + this.tree?.domFocus(); + } + override focus(): void { super.focus(); setTimeout(() => this.replInput.focus(), 0); @@ -615,6 +649,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { const wordWrap = this.configurationService.getValue('debug').console.wordWrap; this.treeContainer.classList.toggle('word-wrap', wordWrap); const linkDetector = this.instantiationService.createInstance(LinkDetector); + this.replDataSource = new ReplDataSource(); const tree = this.tree = >this.instantiationService.createInstance( WorkbenchAsyncDataTree, @@ -629,14 +664,13 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { new ReplEvaluationResultsRenderer(linkDetector, this.hoverService), new ReplRawObjectsRenderer(linkDetector, this.hoverService), ], - // https://github.com/microsoft/TypeScript/issues/32526 - new ReplDataSource() satisfies IAsyncDataSource, + this.replDataSource, { filter: this.filter, accessibilityProvider: new ReplAccessibilityProvider(), identityProvider, mouseSupport: false, - findWidgetEnabled: false, + findWidgetEnabled: true, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e.toString(true) }, horizontalScrolling: !wordWrap, setRowLineHeight: false, @@ -661,11 +695,16 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { })); this._register(tree.onContextMenu(e => this.onContextMenu(e))); + this._register(tree.onDidChangeFindOpenState((open) => this.findIsOpen = open)); + let lastSelectedString: string; this._register(tree.onMouseClick(() => { + if (this.findIsOpen) { + return; + } const selection = dom.getWindow(this.treeContainer).getSelection(); if (!selection || selection.type !== 'Range' || lastSelectedString === selection.toString()) { - // only focus the input if the user is not currently selecting. + // only focus the input if the user is not currently selecting and find isn't open. this.replInput.focus(); } lastSelectedString = selection ? selection.toString() : ''; @@ -687,13 +726,13 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { }; CONTEXT_IN_DEBUG_REPL.bindTo(this.scopedContextKeyService).set(true); - this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); + this.scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); const options = getSimpleEditorOptions(this.configurationService); options.readOnly = true; options.suggest = { showStatusBar: true }; const config = this.configurationService.getValue('debug'); options.acceptSuggestionOnEnter = config.console.acceptSuggestionOnEnter === 'on' ? 'on' : 'off'; - options.ariaLabel = localize('debugConsole', "Debug Console"); + options.ariaLabel = this.getAriaLabel(); this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); @@ -716,6 +755,21 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => this.replInputContainer.classList.remove('synthetic-focus'))); } + private getAriaLabel(): string { + let ariaLabel = localize('debugConsole', "Debug Console"); + if (!this.configurationService.getValue(AccessibilityVerbositySettingId.Debug)) { + return ariaLabel; + } + const keybinding = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getAriaLabel(); + if (keybinding) { + ariaLabel = localize('commentLabelWithKeybinding', "{0}, use ({1}) for accessibility help", ariaLabel, keybinding); + } else { + ariaLabel = localize('commentLabelWithKeybindingNoKeybinding', "{0}, run the command Open Accessibility Help which is currently not triggerable via keybinding.", ariaLabel); + } + + return ariaLabel; + } + private onContextMenu(e: ITreeContextMenuEvent): void { const actions: IAction[] = []; createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, actions); @@ -852,8 +906,8 @@ class AcceptReplInputAction extends EditorAction { constructor() { super({ id: 'repl.action.acceptInput', - label: localize({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "REPL Accept Input"), - alias: 'REPL Accept Input', + label: localize({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "Debug Console: Accept Input"), + alias: 'Debug Console: Accept Input', precondition: CONTEXT_IN_DEBUG_REPL, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, @@ -870,25 +924,57 @@ class AcceptReplInputAction extends EditorAction { } } -class FilterReplAction extends EditorAction { +class FilterReplAction extends ViewAction { constructor() { super({ + viewId: REPL_VIEW_ID, id: 'repl.action.filter', - label: localize('repl.action.filter', "REPL Focus Content to Filter"), - alias: 'REPL Filter', + title: localize('repl.action.filter', "Debug Console: Focus Filter"), precondition: CONTEXT_IN_DEBUG_REPL, - kbOpts: { - kbExpr: EditorContextKeys.textInputFocus, + keybinding: [{ + when: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KeyF, weight: KeybindingWeight.EditorContrib - } + }] }); } - run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { - const repl = getReplView(accessor.get(IViewsService)); - repl?.focusFilter(); + runInView(accessor: ServicesAccessor, repl: Repl): void | Promise { + repl.focusFilter(); + } +} + + +class FindReplAction extends ViewAction { + + constructor() { + super({ + viewId: REPL_VIEW_ID, + id: 'repl.action.find', + title: localize('repl.action.find', "Debug Console: Focus Find"), + precondition: CONTEXT_IN_DEBUG_REPL, + keybinding: [{ + when: ContextKeyExpr.or(CONTEXT_IN_DEBUG_REPL, ContextKeyExpr.equals('focusedView', 'workbench.panel.repl.view')), + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF, + weight: KeybindingWeight.EditorContrib + }], + icon: Codicon.search, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.equals('view', REPL_VIEW_ID), + order: 15 + }, { + id: MenuId.DebugConsoleContext, + group: 'z_commands', + order: 25 + }], + }); + } + + runInView(accessor: ServicesAccessor, view: Repl): void | Promise { + view.openFind(); } } @@ -914,7 +1000,8 @@ class ReplCopyAllAction extends EditorAction { registerEditorAction(AcceptReplInputAction); registerEditorAction(ReplCopyAllAction); -registerEditorAction(FilterReplAction); +registerAction2(FilterReplAction); +registerAction2(FindReplAction); class SelectReplActionViewItem extends FocusSessionActionViewItem { @@ -930,7 +1017,7 @@ class SelectReplActionViewItem extends FocusSessionActionViewItem { } } -function getReplView(viewsService: IViewsService): Repl | undefined { +export function getReplView(viewsService: IViewsService): Repl | undefined { return viewsService.getActiveViewWithId(REPL_VIEW_ID) as Repl ?? undefined; } @@ -989,7 +1076,15 @@ registerAction2(class extends ViewAction { id: MenuId.DebugConsoleContext, group: 'z_commands', order: 20 - }] + }], + keybinding: [{ + primary: 0, + mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyK }, + // Weight is higher than work workbench contributions so the keybinding remains + // highest priority when chords are registered afterwards + weight: KeybindingWeight.WorkbenchContrib + 1, + when: ContextKeyExpr.equals('focusedView', 'workbench.panel.repl.view') + }], }); } diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/replAccessibilityHelp.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/replAccessibilityHelp.ts new file mode 100644 index 00000000..4cf2efbc --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/replAccessibilityHelp.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; +import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { getReplView, Repl } from 'vs/workbench/contrib/debug/browser/repl'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { localize } from 'vs/nls'; + +export class ReplAccessibilityHelp implements IAccessibleViewImplentation { + priority = 120; + name = 'replHelp'; + when = ContextKeyExpr.equals('focusedView', 'workbench.panel.repl.view'); + type: AccessibleViewType = AccessibleViewType.Help; + getProvider(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const replView = getReplView(viewsService); + if (!replView) { + return undefined; + } + return new ReplAccessibilityHelpProvider(replView); + } +} + +class ReplAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider { + public readonly id = AccessibleViewProviderId.ReplHelp; + public readonly verbositySettingKey = AccessibilityVerbositySettingId.Debug; + public readonly options = { type: AccessibleViewType.Help }; + private _treeHadFocus = false; + constructor(private readonly _replView: Repl) { + super(); + this._treeHadFocus = !!_replView.getFocusedElement(); + } + + public onClose(): void { + if (this._treeHadFocus) { + return this._replView.focusTree(); + } + this._replView.getReplInput().focus(); + } + + public provideContent(): string { + return [ + localize('repl.help', "The debug console is a Read-Eval-Print-Loop that allows you to evaluate expressions and run commands and can be focused with{0}.", ''), + localize('repl.output', "The debug console output can be navigated to from the input field with the Focus Previous Widget command{0}.", ''), + localize('repl.input', "The debug console input can be navigated to from the output with the Focus Next Widget command{0}.", ''), + localize('repl.history', "The debug console output history can be navigated with the up and down arrow keys."), + localize('repl.accessibleView', "The Open Accessible View command{0} will allow character by character navigation of the console output.", ''), + localize('repl.showRunAndDebug', "The Show Run and Debug view command{0} will open the Run and Debug view and provides more information about debugging.", ''), + localize('repl.clear', "The Debug: Clear Console command{0} will clear the console output.", ''), + localize('repl.lazyVariables', "The setting `debug.expandLazyVariables` controls whether variables are evaluated automatically. This is enabled by default when using a screen reader."), + ].join('\n'); + } +} + diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/replAccessibleView.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/replAccessibleView.ts new file mode 100644 index 00000000..891598ed --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/replAccessibleView.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentProvider, IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IReplElement } from 'vs/workbench/contrib/debug/common/debug'; +import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { getReplView, Repl } from 'vs/workbench/contrib/debug/browser/repl'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Position } from 'vs/editor/common/core/position'; + +export class ReplAccessibleView implements IAccessibleViewImplentation { + priority = 70; + name = 'debugConsole'; + when = ContextKeyExpr.equals('focusedView', 'workbench.panel.repl.view'); + type: AccessibleViewType = AccessibleViewType.View; + getProvider(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const accessibleViewService = accessor.get(IAccessibleViewService); + const replView = getReplView(viewsService); + if (!replView) { + return undefined; + } + + const focusedElement = replView.getFocusedElement(); + return new ReplOutputAccessibleViewProvider(replView, focusedElement, accessibleViewService); + } +} + +class ReplOutputAccessibleViewProvider extends Disposable implements IAccessibleViewContentProvider { + public readonly id = AccessibleViewProviderId.Repl; + private _content: string | undefined; + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + public readonly onDidChangeContent: Event = this._onDidChangeContent.event; + private readonly _onDidResolveChildren: Emitter = this._register(new Emitter()); + public readonly onDidResolveChildren: Event = this._onDidResolveChildren.event; + + public readonly verbositySettingKey = AccessibilityVerbositySettingId.Debug; + public readonly options = { + type: AccessibleViewType.View + }; + + private _elementPositionMap: Map = new Map(); + private _treeHadFocus = false; + + constructor( + private readonly _replView: Repl, + private readonly _focusedElement: IReplElement | undefined, + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService) { + super(); + this._treeHadFocus = !!_focusedElement; + } + public provideContent(): string { + const debugSession = this._replView.getDebugSession(); + if (!debugSession) { + return 'No debug session available.'; + } + const elements = debugSession.getReplElements(); + if (!elements.length) { + return 'No output in the debug console.'; + } + if (!this._content) { + this._updateContent(elements); + } + // Content is loaded asynchronously, so we need to check if it's available or fallback to the elements that are already available. + return this._content ?? elements.map(e => e.toString(true)).join('\n'); + } + + public onClose(): void { + this._content = undefined; + this._elementPositionMap.clear(); + if (this._treeHadFocus) { + return this._replView.focusTree(); + } + this._replView.getReplInput().focus(); + } + + public onOpen(): void { + // Children are resolved async, so we need to update the content when they are resolved. + this._register(this.onDidResolveChildren(() => { + this._onDidChangeContent.fire(); + queueMicrotask(() => { + if (this._focusedElement) { + const position = this._elementPositionMap.get(this._focusedElement.getId()); + if (position) { + this._accessibleViewService.setPosition(position, true); + } + } + }); + })); + } + + private async _updateContent(elements: IReplElement[]) { + const dataSource = this._replView.getReplDataSource(); + if (!dataSource) { + return; + } + let line = 1; + const content: string[] = []; + for (const e of elements) { + content.push(e.toString().replace(/\n/g, '')); + this._elementPositionMap.set(e.getId(), new Position(line, 1)); + line++; + if (dataSource.hasChildren(e)) { + const childContent: string[] = []; + const children = await dataSource.getChildren(e); + for (const child of children) { + const id = child.getId(); + if (!this._elementPositionMap.has(id)) { + // don't overwrite parent position + this._elementPositionMap.set(id, new Position(line, 1)); + } + childContent.push(' ' + child.toString()); + line++; + } + content.push(childContent.join('\n')); + } + } + + this._content = content.join('\n'); + this._onDidResolveChildren.fire(); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/replViewer.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/replViewer.ts index bae5c316..54f4efae 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -6,20 +6,25 @@ import * as dom from 'vs/base/browser/dom'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { IManagedHover } from 'vs/base/browser/ui/hover/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/path'; import severity from 'vs/base/common/severity'; +import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ThemeIcon } from 'vs/base/common/themables'; import { AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderExpressionValue, renderVariable } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; import { debugConsoleEvaluationInput } from 'vs/workbench/contrib/debug/browser/debugIcons'; @@ -28,9 +33,6 @@ import { IDebugConfiguration, IDebugService, IDebugSession, IExpression, IExpres import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup, ReplOutputElement, ReplVariableElement } from 'vs/workbench/contrib/debug/common/replModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; const $ = dom.$; @@ -40,10 +42,12 @@ interface IReplEvaluationInputTemplateData { interface IReplGroupTemplateData { label: HTMLElement; + source: SourceWidget; } interface IReplEvaluationResultTemplateData { value: HTMLElement; + elementStore: DisposableStore; } interface IOutputReplElementTemplateData { @@ -51,9 +55,8 @@ interface IOutputReplElementTemplateData { count: CountBadge; countContainer: HTMLElement; value: HTMLElement; - source: HTMLElement; + source: SourceWidget; getReplElementSource(): IReplElementSource | undefined; - toDispose: IDisposable[]; elementListener: IDisposable; } @@ -63,6 +66,7 @@ interface IRawObjectReplTemplateData { name: HTMLElement; value: HTMLElement; label: HighlightedLabel; + elementStore: DisposableStore; } export class ReplEvaluationInputsRenderer implements ITreeRenderer { @@ -94,7 +98,8 @@ export class ReplGroupRenderer implements ITreeRenderer, _index: number, templateData: IReplGroupTemplateData): void { @@ -111,10 +119,11 @@ export class ReplGroupRenderer implements ITreeRenderer, index: number, templateData: IReplEvaluationResultTemplateData): void { + templateData.elementStore.clear(); const expression = element.element; - renderExpressionValue(expression, templateData.value, { + renderExpressionValue(templateData.elementStore, expression, templateData.value, { colorize: true, - linkDetector: this.linkDetector + hover: false, + linkDetector: this.linkDetector, }, this.hoverService); } disposeTemplate(templateData: IReplEvaluationResultTemplateData): void { - // noop + templateData.elementStore.dispose(); } } @@ -155,10 +166,8 @@ export class ReplOutputElementRenderer implements ITreeRenderer { - e.preventDefault(); - e.stopPropagation(); - const source = data.getReplElementSource(); - if (source) { - source.source.openInEditor(this.editorService, { - startLineNumber: source.lineNumber, - startColumn: source.column, - endLineNumber: source.lineNumber, - endColumn: source.column - }); - } - })); + data.value = dom.append(expression, $('span.value.label')); + data.source = this.instaService.createInstance(SourceWidget, expression); return data; } @@ -204,8 +199,7 @@ export class ReplOutputElementRenderer implements ITreeRenderer element.sourceData; } @@ -219,7 +213,7 @@ export class ReplOutputElementRenderer implements ITreeRenderer, _index: number, templateData: IOutputReplElementTemplateData): void { @@ -247,6 +241,7 @@ export class ReplVariablesRenderer extends AbstractExpressionsRenderer, _index: number, data: IExpressionTemplateData): void { const element = node.element; + data.elementDisposable.clear(); super.renderExpressionElement(element instanceof ReplVariableElement ? element.expression : element, node, data); } @@ -254,7 +249,7 @@ export class ReplVariablesRenderer extends AbstractExpressionsRenderer, index: number, templateData: IRawObjectReplTemplateData): void { + templateData.elementStore.clear(); + // key const element = node.element; templateData.label.set(element.name ? `${element.name}:` : '', createMatches(node.filterData)); @@ -301,12 +298,14 @@ export class ReplRawObjectsRenderer implements ITreeRenderer { + e.preventDefault(); + e.stopPropagation(); + if (this.source) { + this.source.source.openInEditor(editorService, { + startLineNumber: this.source.lineNumber, + startColumn: this.source.column, + endLineNumber: this.source.lineNumber, + endColumn: this.source.column + }); + } + })); + + } + + public setSource(source?: IReplElementSource) { + this.source = source; + this.el.textContent = source ? `${basename(source.source.name)}:${source.lineNumber}` : ''; + + this.hover ??= this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.el, '')); + this.hover.update(source ? `${this.labelService.getUriLabel(source.source.uri)}:${source.lineNumber}` : ''); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/runAndDebugAccessibilityHelp.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/runAndDebugAccessibilityHelp.ts new file mode 100644 index 00000000..fd4e3713 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/runAndDebugAccessibilityHelp.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; +import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; +import { FocusedViewContext, SidebarFocusContext } from 'vs/workbench/common/contextkeys'; +import { BREAKPOINTS_VIEW_ID, CALLSTACK_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, VARIABLES_VIEW_ID, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; + +export class RunAndDebugAccessibilityHelp implements IAccessibleViewImplentation { + priority = 120; + name = 'runAndDebugHelp'; + when = ContextKeyExpr.or( + ContextKeyExpr.and(ContextKeyExpr.equals('activeViewlet', 'workbench.view.debug'), SidebarFocusContext), + ContextKeyExpr.equals(FocusedViewContext.key, VARIABLES_VIEW_ID), + ContextKeyExpr.equals(FocusedViewContext.key, WATCH_VIEW_ID), + ContextKeyExpr.equals(FocusedViewContext.key, CALLSTACK_VIEW_ID), + ContextKeyExpr.equals(FocusedViewContext.key, LOADED_SCRIPTS_VIEW_ID), + ContextKeyExpr.equals(FocusedViewContext.key, BREAKPOINTS_VIEW_ID) + ); + type: AccessibleViewType = AccessibleViewType.Help; + getProvider(accessor: ServicesAccessor) { + return new RunAndDebugAccessibilityHelpProvider(accessor.get(ICommandService), accessor.get(IViewsService)); + } +} + +class RunAndDebugAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider { + public readonly id = AccessibleViewProviderId.RunAndDebug; + public readonly verbositySettingKey = AccessibilityVerbositySettingId.Debug; + public readonly options = { type: AccessibleViewType.Help }; + private _focusedView: string | undefined; + constructor( + @ICommandService private readonly _commandService: ICommandService, + @IViewsService private readonly _viewsService: IViewsService + ) { + super(); + this._focusedView = this._viewsService.getFocusedViewName(); + } + + public onClose(): void { + switch (this._focusedView) { + case 'Watch': + this._commandService.executeCommand('workbench.debug.action.focusWatchView'); + break; + case 'Variables': + this._commandService.executeCommand('workbench.debug.action.focusVariablesView'); + break; + case 'Call Stack': + this._commandService.executeCommand('workbench.debug.action.focusCallStackView'); + break; + case 'Breakpoints': + this._commandService.executeCommand('workbench.debug.action.focusBreakpointsView'); + break; + default: + this._commandService.executeCommand('workbench.view.debug'); + } + } + + public provideContent(): string { + return [ + localize('debug.showRunAndDebug', "The Show Run and Debug view command{0} will open the current view.", ''), + localize('debug.startDebugging', "The Debug: Start Debugging command{0} will start a debug session.", ''), + localize('debug.help', "Access debug output and evaluate expressions in the debug console, which can be focused with{0}.", ''), + AccessibilityHelpNLS.setBreakpoint, + AccessibilityHelpNLS.addToWatch, + localize('onceDebugging', "Once debugging, the following commands will be available:"), + localize('debug.restartDebugging', "- Debug: Restart Debugging command{0} will restart the current debug session.", ''), + localize('debug.stopDebugging', "- Debug: Stop Debugging command{0} will stop the current debugging session.", ''), + localize('debug.continue', "- Debug: Continue command{0} will continue execution until the next breakpoint.", ''), + localize('debug.stepInto', "- Debug: Step Into command{0} will step into the next function call.", ''), + localize('debug.stepOver', "- Debug: Step Over command{0} will step over the current function call.", ''), + localize('debug.stepOut', "- Debug: Step Out command{0} will step out of the current function call.", ''), + localize('debug.views', 'The debug viewlet is comprised of several views that can be focused with the following commands or navigated to via tab then arrow keys:'), + localize('debug.focusBreakpoints', "- Debug: Focus Breakpoints View command{0} will focus the breakpoints view.", ''), + localize('debug.focusCallStack', "- Debug: Focus Call Stack View command{0} will focus the call stack view.", ''), + localize('debug.focusVariables', "- Debug: Focus Variables View command{0} will focus the variables view.", ''), + localize('debug.focusWatch', "- Debug: Focus Watch View command{0} will focus the watch view.", ''), + localize('debug.watchSetting', "The setting {0} controls whether watch variable changes are announced.", 'accessibility.debugWatchVariableAnnouncements'), + ].join('\n'); + } +} + diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index 4231608a..54c881df 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { ColorTransformType, asCssVariable, asCssVariableName, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { asCssVariable, asCssVariableName, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService, State, IDebugSession, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -31,21 +31,11 @@ export const STATUS_BAR_DEBUGGING_FOREGROUND = registerColor('statusBar.debuggin hcLight: '#FFFFFF' }, localize('statusBarDebuggingForeground', "Status bar foreground color when a program is being debugged. The status bar is shown in the bottom of the window")); -export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBorder', { - dark: STATUS_BAR_BORDER, - light: STATUS_BAR_BORDER, - hcDark: STATUS_BAR_BORDER, - hcLight: STATUS_BAR_BORDER -}, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window")); +export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBorder', STATUS_BAR_BORDER, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window")); export const COMMAND_CENTER_DEBUGGING_BACKGROUND = registerColor( 'commandCenter.debuggingBackground', - { - dark: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, - hcDark: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, - light: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, - hcLight: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 } - }, + transparent(STATUS_BAR_DEBUGGING_BACKGROUND, 0.258), localize('commandCenter-activeBackground', "Command center background color when a program is being debugged"), true ); @@ -86,7 +76,7 @@ export class StatusBarColorProvider implements IWorkbenchContribution { if (e.affectsConfiguration('debug.enableStatusBarColor') || e.affectsConfiguration('debug.toolBarLocation')) { this.update(); } - }, this.disposables); + }, undefined, this.disposables); this.update(); } diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/variablesView.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/variablesView.ts index 5dc2a66b..5511716c 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -16,7 +16,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -41,7 +41,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_VALUE_ID, COPY_VALUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DataBreakpointSetType, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DataBreakpointSetType, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugConfiguration, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; import { ErrorScope, Expression, Scope, StackFrame, Variable, VisualizedExpression, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; import { DebugVisualizer, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; @@ -246,22 +246,16 @@ export async function openContextMenuForVariableTreeElement(parentContextKeyServ return; } - const toDispose = new DisposableStore(); + const contextKeyService = await getContextForVariableMenuWithDataAccess(parentContextKeyService, variable); + const context: IVariablesContext = getVariablesContext(variable); + const menu = menuService.getMenuActions(menuId, contextKeyService, { arg: context, shouldForwardArgs: false }); - try { - const contextKeyService = await getContextForVariableMenuWithDataAccess(parentContextKeyService, variable); - const menu = toDispose.add(menuService.createMenu(menuId, contextKeyService)); - - const context: IVariablesContext = getVariablesContext(variable); - const secondary: IAction[] = []; - createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary: [], secondary }, 'inline'); - contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => secondary - }); - } finally { - toDispose.dispose(); - } + const secondary: IAction[] = []; + createAndFillInContextMenuActions(menu, { primary: [], secondary }, 'inline'); + contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => secondary + }); } const getVariablesContext = (variable: Variable): IVariablesContext => ({ @@ -455,6 +449,7 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { } public override renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { + data.elementDisposable.clear(); super.renderExpressionElement(node.element, node, data); } @@ -466,10 +461,9 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { text += ':'; } data.label.set(text, highlights, viz.name); - renderExpressionValue(viz, data.value, { + renderExpressionValue(data.elementDisposable, viz, data.value, { showChanged: false, maxValueLength: 1024, - hover: data.elementDisposable, colorize: true, linkDetector: this.linkDetector }, this.hoverService); @@ -499,11 +493,11 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { protected override renderActionBar(actionBar: ActionBar, expression: IExpression, _data: IExpressionTemplateData) { const viz = expression as VisualizedExpression; const contextKeyService = viz.original ? getContextForVariableMenuBase(this.contextKeyService, viz.original) : this.contextKeyService; - const menu = this.menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService); + const context = viz.original ? getVariablesContext(viz.original) : undefined; + const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false }); const primary: IAction[] = []; - const context = viz.original ? getVariablesContext(viz.original) : undefined; - createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary, secondary: [] }, 'inline'); + createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); if (viz.original) { const action = new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.eye), true, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined)); @@ -531,6 +525,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { @IDebugService debugService: IDebugService, @IContextViewService contextViewService: IContextViewService, @IHoverService hoverService: IHoverService, + @IConfigurationService private configurationService: IConfigurationService, ) { super(debugService, contextViewService, hoverService); } @@ -540,10 +535,17 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { } protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { - renderVariable(data.elementDisposable, this.commandService, this.hoverService, expression as Variable, data, true, highlights, this.linkDetector); + const showType = this.configurationService.getValue('debug').showVariableTypes; + renderVariable(data.elementDisposable, this.commandService, this.hoverService, expression as Variable, data, true, highlights, this.linkDetector, showType); } public override renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { + data.elementDisposable.clear(); + data.elementDisposable.add(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('debug.showVariableTypes')) { + super.renderExpressionElement(node.element, node, data); + } + })); super.renderExpressionElement(node.element, node, data); } @@ -574,11 +576,11 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { protected override renderActionBar(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData) { const variable = expression as Variable; const contextKeyService = getContextForVariableMenuBase(this.contextKeyService, variable); - const menu = this.menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService); const primary: IAction[] = []; const context = getVariablesContext(variable); - createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary, secondary: [] }, 'inline'); + const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false }); + createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); actionBar.clear(); actionBar.context = context; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/patched-vscode/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 7369a367..fcdebfce 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -34,7 +34,7 @@ import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionT import { watchExpressionsAdd, watchExpressionsRemoveAll } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { VariablesRenderer, VisualizedVariableRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; -import { CONTEXT_CAN_VIEW_MEMORY, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_WATCH_ITEM_TYPE, IDebugService, IExpression, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_CAN_VIEW_MEMORY, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_WATCH_ITEM_TYPE, IDebugConfiguration, IDebugService, IExpression, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, Variable, VisualizedExpression } from 'vs/workbench/contrib/debug/common/debugModel'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; @@ -87,8 +87,8 @@ export class WatchExpressionsView extends ViewPane { container.classList.add('debug-watch'); const treeContainer = renderViewTree(container); - const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer); const linkDetector = this.instantiationService.createInstance(LinkDetector); + const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer, linkDetector); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [ expressionsRenderer, @@ -157,7 +157,7 @@ export class WatchExpressionsView extends ViewPane { let horizontalScrolling: boolean | undefined; this._register(this.debugService.getViewModel().onDidSelectExpression(e => { const expression = e?.expression; - if (expression && this.tree.hasElement(expression)) { + if (expression && this.tree.hasNode(expression)) { horizontalScrolling = this.tree.options.horizontalScrolling; if (horizontalScrolling) { this.tree.updateOptions({ horizontalScrolling: false }); @@ -274,16 +274,18 @@ class WatchExpressionsDataSource extends AbstractExpressionDataSource, index: number, data: IExpressionTemplateData): void { + data.elementDisposable.clear(); + data.elementDisposable.add(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('debug.showVariableTypes')) { + super.renderExpressionElement(node.element, node, data); + } + })); super.renderExpressionElement(node.element, node, data); } protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { - const text = typeof expression.value === 'string' ? `${expression.name}:` : expression.name; + let text: string; + data.type.textContent = ''; + const showType = this.configurationService.getValue('debug').showVariableTypes; + if (showType && expression.type) { + text = typeof expression.value === 'string' ? `${expression.name}: ` : expression.name; + //render type + data.type.textContent = expression.type + ' ='; + } else { + text = typeof expression.value === 'string' ? `${expression.name} =` : expression.name; + } + let title: string; if (expression.type) { - title = expression.type === expression.value ? - expression.type : - `${expression.type}: ${expression.value}`; + if (showType) { + title = `${expression.name}`; + } else { + title = expression.type === expression.value ? + expression.type : + `${expression.type}`; + } } else { title = expression.value; } data.label.set(text, highlights, title); - renderExpressionValue(expression, data.value, { + renderExpressionValue(data.elementDisposable, expression, data.value, { showChanged: true, maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET, - hover: data.elementDisposable, + linkDetector: this.linkDetector, colorize: true }, this.hoverService); } @@ -352,11 +374,11 @@ class WatchExpressionsRenderer extends AbstractExpressionsRenderer { protected override renderActionBar(actionBar: ActionBar, expression: IExpression) { const contextKeyService = getContextForWatchExpressionMenu(this.contextKeyService, expression); - const menu = this.menuService.createMenu(MenuId.DebugWatchContext, contextKeyService); + const context = expression; + const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: context, shouldForwardArgs: false }); const primary: IAction[] = []; - const context = expression; - createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary, secondary: [] }, 'inline'); + createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); actionBar.clear(); actionBar.context = context; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/debug.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/debug.ts index e48607b9..c59765f1 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/common/debug.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/debug.ts @@ -27,6 +27,7 @@ import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompou import { IDataBreakpointOptions, IFunctionBreakpointOptions, IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { ITaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; +import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export const VIEWLET_ID = 'workbench.view.debug'; @@ -50,9 +51,9 @@ export const CONTEXT_IN_DEBUG_REPL = new RawContextKey('inDebugRepl', f export const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = new RawContextKey('breakpointWidgetVisible', false, { type: 'boolean', description: nls.localize('breakpointWidgetVisibile', "True when breakpoint editor zone widget is visible, false otherwise.") }); export const CONTEXT_IN_BREAKPOINT_WIDGET = new RawContextKey('inBreakpointWidget', false, { type: 'boolean', description: nls.localize('inBreakpointWidget', "True when focus is in the breakpoint editor zone widget, false otherwise.") }); export const CONTEXT_BREAKPOINTS_FOCUSED = new RawContextKey('breakpointsFocused', true, { type: 'boolean', description: nls.localize('breakpointsFocused', "True when the BREAKPOINTS view is focused, false otherwise.") }); -export const CONTEXT_WATCH_EXPRESSIONS_FOCUSED = new RawContextKey('watchExpressionsFocused', true, { type: 'boolean', description: nls.localize('watchExpressionsFocused', "True when the WATCH view is focused, false otherwsie.") }); +export const CONTEXT_WATCH_EXPRESSIONS_FOCUSED = new RawContextKey('watchExpressionsFocused', true, { type: 'boolean', description: nls.localize('watchExpressionsFocused', "True when the WATCH view is focused, false otherwise.") }); export const CONTEXT_WATCH_EXPRESSIONS_EXIST = new RawContextKey('watchExpressionsExist', false, { type: 'boolean', description: nls.localize('watchExpressionsExist', "True when at least one watch expression exists, false otherwise.") }); -export const CONTEXT_VARIABLES_FOCUSED = new RawContextKey('variablesFocused', true, { type: 'boolean', description: nls.localize('variablesFocused', "True when the VARIABLES views is focused, false otherwsie") }); +export const CONTEXT_VARIABLES_FOCUSED = new RawContextKey('variablesFocused', true, { type: 'boolean', description: nls.localize('variablesFocused', "True when the VARIABLES views is focused, false otherwise") }); export const CONTEXT_EXPRESSION_SELECTED = new RawContextKey('expressionSelected', false, { type: 'boolean', description: nls.localize('expressionSelected', "True when an expression input box is open in either the WATCH or the VARIABLES view, false otherwise.") }); export const CONTEXT_BREAKPOINT_INPUT_FOCUSED = new RawContextKey('breakpointInputFocused', false, { type: 'boolean', description: nls.localize('breakpointInputFocused', "True when the input box has focus in the BREAKPOINTS view.") }); export const CONTEXT_CALLSTACK_ITEM_TYPE = new RawContextKey('callStackItemType', undefined, { type: 'string', description: nls.localize('callStackItemType', "Represents the item type of the focused element in the CALL STACK view. For example: 'session', 'thread', 'stackFrame'") }); @@ -71,7 +72,7 @@ export const CONTEXT_FOCUSED_SESSION_IS_ATTACH = new RawContextKey('foc export const CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG = new RawContextKey('focusedSessionIsNoDebug', false, { type: 'boolean', description: nls.localize('focusedSessionIsNoDebug', "True when the focused session is run without debugging.") }); export const CONTEXT_STEP_BACK_SUPPORTED = new RawContextKey('stepBackSupported', false, { type: 'boolean', description: nls.localize('stepBackSupported', "True when the focused session supports 'stepBack' requests.") }); export const CONTEXT_RESTART_FRAME_SUPPORTED = new RawContextKey('restartFrameSupported', false, { type: 'boolean', description: nls.localize('restartFrameSupported', "True when the focused session supports 'restartFrame' requests.") }); -export const CONTEXT_STACK_FRAME_SUPPORTS_RESTART = new RawContextKey('stackFrameSupportsRestart', false, { type: 'boolean', description: nls.localize('stackFrameSupportsRestart', "True when the focused stack frame suppots 'restartFrame'.") }); +export const CONTEXT_STACK_FRAME_SUPPORTS_RESTART = new RawContextKey('stackFrameSupportsRestart', false, { type: 'boolean', description: nls.localize('stackFrameSupportsRestart', "True when the focused stack frame supports 'restartFrame'.") }); export const CONTEXT_JUMP_TO_CURSOR_SUPPORTED = new RawContextKey('jumpToCursorSupported', false, { type: 'boolean', description: nls.localize('jumpToCursorSupported', "True when the focused session supports 'jumpToCursor' request.") }); export const CONTEXT_STEP_INTO_TARGETS_SUPPORTED = new RawContextKey('stepIntoTargetsSupported', false, { type: 'boolean', description: nls.localize('stepIntoTargetsSupported', "True when the focused session supports 'stepIntoTargets' request.") }); export const CONTEXT_BREAKPOINTS_EXIST = new RawContextKey('breakpointsExist', false, { type: 'boolean', description: nls.localize('breakpointsExist', "True when at least one breakpoint exists.") }); @@ -219,6 +220,11 @@ export interface LoadedSourceEvent { export type IDebugSessionReplMode = 'separate' | 'mergeWithParent'; +export interface IDebugTestRunReference { + runId: string; + taskId: string; +} + export interface IDebugSessionOptions { noDebug?: boolean; parentSession?: IDebugSession; @@ -231,6 +237,11 @@ export interface IDebugSessionOptions { suppressDebugToolbar?: boolean; suppressDebugStatusbar?: boolean; suppressDebugView?: boolean; + /** + * Set if the debug session is correlated with a test run. Stopping/restarting + * the session will instead stop/restart the test run. + */ + testRun?: IDebugTestRunReference; } export interface IDataBreakpointInfoResponse { @@ -335,6 +346,19 @@ export interface INewReplElementData { source?: IReplElementSource; } +export interface IDebugEvaluatePosition { + line: number; + column: number; + source: DebugProtocol.Source; +} + +export interface IDebugLocationReferenced { + line: number; + column: number; + endLine?: number; + endColumn?: number; + source: Source; +} export interface IDebugSession extends ITreeElement { @@ -353,6 +377,8 @@ export interface IDebugSession extends ITreeElement { readonly suppressDebugStatusbar: boolean; readonly suppressDebugView: boolean; readonly lifecycleManagedByParent: boolean; + /** Test run this debug session was spawned by */ + readonly correlatedTestRun?: LiveTestResult; setSubId(subId: string | undefined): void; @@ -382,7 +408,7 @@ export interface IDebugSession extends ITreeElement { // session events readonly onDidEndAdapter: Event; readonly onDidChangeState: Event; - readonly onDidChangeReplElements: Event; + readonly onDidChangeReplElements: Event; // DA capabilities readonly capabilities: DebugProtocol.Capabilities; @@ -413,12 +439,13 @@ export interface IDebugSession extends ITreeElement { sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise; breakpointsLocations(uri: uri, lineNumber: number): Promise; getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined; + resolveLocationReference(locationReference: number): Promise; stackTrace(threadId: number, startFrame: number, levels: number, token: CancellationToken): Promise; exceptionInfo(threadId: number): Promise; scopes(frameId: number, threadId: number): Promise; variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise; - evaluate(expression: string, frameId?: number, context?: string): Promise; + evaluate(expression: string, frameId?: number, context?: string, location?: IDebugEvaluatePosition): Promise; customRequest(request: string, args: any): Promise; cancel(progressId: string): Promise; disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise; @@ -534,7 +561,8 @@ export interface IStackFrame extends ITreeElement { } export function isFrameDeemphasized(frame: IStackFrame): boolean { - return frame.source.presentationHint === 'deemphasize' || frame.presentationHint === 'deemphasize' || frame.presentationHint === 'subtle'; + const hint = frame.presentationHint ?? frame.source.presentationHint; + return hint === 'deemphasize' || hint === 'subtle'; } export interface IEnablement extends ITreeElement { @@ -723,7 +751,14 @@ export interface IDebugModel extends ITreeElement { getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[]; onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; + /** + * The expression has been added, removed, or repositioned. + */ onDidChangeWatchExpressions: Event; + /** + * The expression's value has changed. + */ + onDidChangeWatchExpressionValue: Event; fetchCallstack(thread: IThread, levels?: number): Promise; } @@ -772,8 +807,9 @@ export interface IDebugConfiguration { disassemblyView: { showSourceCode: boolean; }; - autoExpandLazyVariables: boolean; + autoExpandLazyVariables: 'auto' | 'off' | 'on'; enableStatusBarColor: boolean; + showVariableTypes: boolean; } export interface IGlobalConfig { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer.ts new file mode 100644 index 00000000..4c8bfe34 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Expression } from 'vs/workbench/contrib/debug/common/debugModel'; + +export class DebugWatchAccessibilityAnnouncer extends Disposable implements IWorkbenchContribution { + static ID = 'workbench.contrib.debugWatchAccessibilityAnnouncer'; + private readonly _listener: MutableDisposable = this._register(new MutableDisposable()); + constructor( + @IDebugService private readonly _debugService: IDebugService, + @ILogService private readonly _logService: ILogService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { + super(); + this._setListener(); + this._register(_configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('accessibility.debugWatchVariableAnnouncements')) { + this._setListener(); + } + })); + } + + private _setListener(): void { + const value = this._configurationService.getValue('accessibility.debugWatchVariableAnnouncements'); + if (value && !this._listener.value) { + this._listener.value = this._debugService.getModel().onDidChangeWatchExpressionValue((e) => { + if (!e || e.value === Expression.DEFAULT_VALUE) { + return; + } + + // TODO: get user feedback, perhaps setting to configure verbosity + whether value, name, neither, or both are announced + this._accessibilityService.alert(`${e.name} = ${e.value}`); + this._logService.trace(`debugAccessibilityAnnouncerValueChanged ${e.name} ${e.value}`); + }); + } else { + this._listener.clear(); + } + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/debugLifecycle.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/debugLifecycle.ts index 838140a9..68420965 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/common/debugLifecycle.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/debugLifecycle.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 * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -11,13 +12,15 @@ import { IDebugConfiguration, IDebugService } from 'vs/workbench/contrib/debug/c import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; export class DebugLifecycle implements IWorkbenchContribution { + private disposable: IDisposable; + constructor( @ILifecycleService lifecycleService: ILifecycleService, @IDebugService private readonly debugService: IDebugService, @IConfigurationService private readonly configurationService: IConfigurationService, @IDialogService private readonly dialogService: IDialogService, ) { - lifecycleService.onBeforeShutdown(async e => e.veto(this.shouldVetoShutdown(e.reason), 'veto.debug')); + this.disposable = lifecycleService.onBeforeShutdown(async e => e.veto(this.shouldVetoShutdown(e.reason), 'veto.debug')); } private shouldVetoShutdown(_reason: ShutdownReason): boolean | Promise { @@ -34,6 +37,10 @@ export class DebugLifecycle implements IWorkbenchContribution { return this.showWindowCloseConfirmation(rootSessions.length); } + public dispose() { + return this.disposable.dispose(); + } + private async showWindowCloseConfirmation(numSessions: number): Promise { let message: string; if (numSessions === 1) { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/debugModel.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/debugModel.ts index 7a89fef9..2d596c14 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -10,7 +10,7 @@ import { VSBuffer, decodeBase64, encodeBase64 } from 'vs/base/common/buffer'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { stringHash } from 'vs/base/common/hash'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { mixin } from 'vs/base/common/objects'; import { autorun } from 'vs/base/common/observable'; import * as resources from 'vs/base/common/resources'; @@ -22,7 +22,7 @@ import * as nls from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { DEBUG_MEMORY_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State, isFrameDeemphasized } from 'vs/workbench/contrib/debug/common/debug'; +import { DEBUG_MEMORY_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugEvaluatePosition, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State, isFrameDeemphasized } from 'vs/workbench/contrib/debug/common/debug'; import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; @@ -54,7 +54,8 @@ export class ExpressionContainer implements IExpressionContainer { public indexedVariables: number | undefined = 0, public memoryReference: string | undefined = undefined, private startOfVariables: number | undefined = 0, - public presentationHint: DebugProtocol.VariablePresentationHint | undefined = undefined + public presentationHint: DebugProtocol.VariablePresentationHint | undefined = undefined, + public valueLocationReference: number | undefined = undefined, ) { } get reference(): number | undefined { @@ -83,6 +84,7 @@ export class ExpressionContainer implements IExpressionContainer { this.indexedVariables = dummyVar.indexedVariables; this.memoryReference = dummyVar.memoryReference; this.presentationHint = dummyVar.presentationHint; + this.valueLocationReference = dummyVar.valueLocationReference; // Also call overridden method to adopt subclass props this.adoptLazyResponse(dummyVar); } @@ -162,7 +164,7 @@ export class ExpressionContainer implements IExpressionContainer { const count = nameCount.get(v.name) || 0; const idDuplicationIndex = count > 0 ? count.toString() : ''; nameCount.set(v.name, count + 1); - return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.memoryReference, v.presentationHint, v.type, v.__vscodeVariableMenuContext, true, 0, idDuplicationIndex); + return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.memoryReference, v.presentationHint, v.type, v.__vscodeVariableMenuContext, true, 0, idDuplicationIndex, v.declarationLocationReference, v.valueLocationReference); } return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false); }); @@ -198,7 +200,9 @@ export class ExpressionContainer implements IExpressionContainer { session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, - keepLazyVars = false): Promise { + keepLazyVars = false, + location?: IDebugEvaluatePosition, + ): Promise { if (!session || (!stackFrame && context !== 'repl')) { this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions") : Expression.DEFAULT_VALUE; @@ -208,7 +212,7 @@ export class ExpressionContainer implements IExpressionContainer { this.session = session; try { - const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context); + const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context, location); if (response && response.body) { this.value = response.body.result || ''; @@ -218,6 +222,7 @@ export class ExpressionContainer implements IExpressionContainer { this.memoryReference = response.body.memoryReference; this.type = response.body.type || this.type; this.presentationHint = response.body.presentationHint; + this.valueLocationReference = response.body.valueLocationReference; if (!keepLazyVars && response.body.presentationHint?.lazy) { await this.evaluateLazy(); @@ -296,6 +301,9 @@ export class Expression extends ExpressionContainer implements IExpression { public available: boolean; + private readonly _onDidChangeValue = new Emitter(); + public readonly onDidChangeValue: Event = this._onDidChangeValue.event; + constructor(public name: string, id = generateUuid()) { super(undefined, undefined, 0, id); this.available = false; @@ -306,8 +314,12 @@ export class Expression extends ExpressionContainer implements IExpression { } } - async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, keepLazyVars?: boolean): Promise { - this.available = await this.evaluateExpression(this.name, session, stackFrame, context, keepLazyVars); + async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, keepLazyVars?: boolean, location?: IDebugEvaluatePosition): Promise { + const hadDefaultValue = this.value === Expression.DEFAULT_VALUE; + this.available = await this.evaluateExpression(this.name, session, stackFrame, context, keepLazyVars, location); + if (hadDefaultValue || this.valueChanged) { + this._onDidChangeValue.fire(this); + } } override toString(): string { @@ -346,8 +358,10 @@ export class Variable extends ExpressionContainer implements IExpression { public readonly available = true, startOfVariables = 0, idDuplicationIndex = '', + public readonly declarationLocationReference: number | undefined = undefined, + valueLocationReference: number | undefined = undefined, ) { - super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables, presentationHint); + super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables, presentationHint, valueLocationReference); this.value = value || ''; this.type = type; } @@ -1401,12 +1415,14 @@ export class DebugModel extends Disposable implements IDebugModel { private readonly _onDidChangeBreakpoints = this._register(new Emitter()); private readonly _onDidChangeCallStack = this._register(new Emitter()); private readonly _onDidChangeWatchExpressions = this._register(new Emitter()); + private readonly _onDidChangeWatchExpressionValue = this._register(new Emitter()); private readonly _breakpointModes = new Map(); private breakpoints!: Breakpoint[]; private functionBreakpoints!: FunctionBreakpoint[]; private exceptionBreakpoints!: ExceptionBreakpoint[]; private dataBreakpoints!: DataBreakpoint[]; private watchExpressions!: Expression[]; + private watchExpressionChangeListeners: DisposableMap = this._register(new DisposableMap()); private instructionBreakpoints: InstructionBreakpoint[]; constructor( @@ -1432,6 +1448,10 @@ export class DebugModel extends Disposable implements IDebugModel { this.instructionBreakpoints = []; this.sessions = []; + + for (const we of this.watchExpressions) { + this.watchExpressionChangeListeners.set(we.getId(), we.onDidChangeValue((e) => this._onDidChangeWatchExpressionValue.fire(e))); + } } getId(): string { @@ -1495,6 +1515,10 @@ export class DebugModel extends Disposable implements IDebugModel { return this._onDidChangeWatchExpressions.event; } + get onDidChangeWatchExpressionValue(): Event { + return this._onDidChangeWatchExpressionValue.event; + } + rawUpdate(data: IRawModelUpdate): void { const session = this.sessions.find(p => p.getId() === data.sessionId); if (session) { @@ -1547,7 +1571,13 @@ export class DebugModel extends Disposable implements IDebugModel { let topCallStack = Promise.resolve(); const wholeCallStack = new Promise((c, e) => { topCallStack = thread.fetchCallStack(1).then(() => { - if (!this.schedulers.has(thread.getId()) && fetchFullStack) { + if (!fetchFullStack) { + c(); + this._onDidChangeCallStack.fire(); + return; + } + + if (!this.schedulers.has(thread.getId())) { const deferred = new DeferredPromise(); this.schedulers.set(thread.getId(), { completeDeferred: deferred, @@ -1995,6 +2025,7 @@ export class DebugModel extends Disposable implements IDebugModel { addWatchExpression(name?: string): IExpression { const we = new Expression(name || ''); + this.watchExpressionChangeListeners.set(we.getId(), we.onDidChangeValue((e) => this._onDidChangeWatchExpressionValue.fire(e))); this.watchExpressions.push(we); this._onDidChangeWatchExpressions.fire(we); @@ -2012,6 +2043,11 @@ export class DebugModel extends Disposable implements IDebugModel { removeWatchExpressions(id: string | null = null): void { this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : []; this._onDidChangeWatchExpressions.fire(undefined); + if (!id) { + this.watchExpressionChangeListeners.clearAndDisposeAll(); + return; + } + this.watchExpressionChangeListeners.deleteAndDispose(id); } moveWatchExpression(id: string, position: number): void { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 50eacfd6..09dd44e7 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -242,6 +242,11 @@ declare module DebugProtocol { column?: number; /** Additional data to report. For the `telemetry` category the data is sent to telemetry, for the other categories the data is shown in JSON format. */ data?: any; + /** A reference that allows the client to request the location where the new value is declared. For example, if the logged value is function pointer, the adapter may be able to look up the function's location. This should be present only if the adapter is likely to be able to resolve the location. + + This reference shares the same lifetime as the `variablesReference`. See 'Lifetime of Object References' in the Overview section for details. + */ + locationReference?: number; }; } @@ -294,7 +299,7 @@ declare module DebugProtocol { body: { /** The logical name of the process. This is usually the full path to process's executable file. Example: /home/example/myproj/program.js. */ name: string; - /** The system process id of the debugged process. This property is missing for non-system processes. */ + /** The process ID of the debugged process, as assigned by the operating system. This property should be omitted for logical processes that do not map to operating system processes on the machine. */ systemProcessId?: number; /** If true, the process is running on the same computer as the debug adapter. */ isLocalProcess?: boolean; @@ -1232,7 +1237,10 @@ declare module DebugProtocol { value: string; /** The type of the new value. Typically shown in the UI when hovering over the value. */ type?: string; - /** If `variablesReference` is > 0, the new value is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ + /** If `variablesReference` is > 0, the new value is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. + + If this property is included in the response, any `variablesReference` previously associated with the updated variable, and those of its children, are no longer valid. + */ variablesReference?: number; /** The number of named child variables. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1249,6 +1257,11 @@ declare module DebugProtocol { This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; + /** A reference that allows the client to request the location where the new value is declared. For example, if the new value is function pointer, the adapter may be able to look up the function's location. This should be present only if the adapter is likely to be able to resolve the location. + + This reference shares the same lifetime as the `variablesReference`. See 'Lifetime of Object References' in the Overview section for details. + */ + valueLocationReference?: number; }; } @@ -1363,7 +1376,7 @@ declare module DebugProtocol { } /** Evaluate request; value of command field is 'evaluate'. - Evaluates the given expression in the context of the topmost stack frame. + Evaluates the given expression in the context of a stack frame. The expression has access to any variables and arguments that are in scope. */ interface EvaluateRequest extends Request { @@ -1377,6 +1390,15 @@ declare module DebugProtocol { expression: string; /** Evaluate the expression in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. */ frameId?: number; + /** The contextual line where the expression should be evaluated. In the 'hover' context, this should be set to the start of the expression being hovered. */ + line?: number; + /** The contextual column where the expression should be evaluated. This may be provided if `line` is also provided. + + It is measured in UTF-16 code units and the client capability `columnsStartAt1` determines whether it is 0- or 1-based. + */ + column?: number; + /** The contextual source in which the `line` is found. This must be provided if `line` is provided. */ + source?: Source; /** The context in which the evaluate request is used. Values: 'watch': evaluate is called from a watch view context. @@ -1423,6 +1445,11 @@ declare module DebugProtocol { This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; + /** A reference that allows the client to request the location where the returned value is declared. For example, if a function pointer is returned, the adapter may be able to look up the function's location. This should be present only if the adapter is likely to be able to resolve the location. + + This reference shares the same lifetime as the `variablesReference`. See 'Lifetime of Object References' in the Overview section for details. + */ + valueLocationReference?: number; }; } @@ -1477,6 +1504,11 @@ declare module DebugProtocol { This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; + /** A reference that allows the client to request the location where the new value is declared. For example, if the new value is function pointer, the adapter may be able to look up the function's location. This should be present only if the adapter is likely to be able to resolve the location. + + This reference shares the same lifetime as the `variablesReference`. See 'Lifetime of Object References' in the Overview section for details. + */ + valueLocationReference?: number; }; } @@ -1691,6 +1723,36 @@ declare module DebugProtocol { }; } + /** Locations request; value of command field is 'locations'. + Looks up information about a location reference previously returned by the debug adapter. + */ + interface LocationsRequest extends Request { + // command: 'locations'; + arguments: LocationsArguments; + } + + /** Arguments for `locations` request. */ + interface LocationsArguments { + /** Location reference to resolve. */ + locationReference: number; + } + + /** Response to `locations` request. */ + interface LocationsResponse extends Response { + body?: { + /** The source containing the location; either `source.path` or `source.sourceReference` must be specified. */ + source: Source; + /** The line number of the location. The client capability `linesStartAt1` determines whether it is 0- or 1-based. */ + line: number; + /** Position of the location within the `line`. It is measured in UTF-16 code units and the client capability `columnsStartAt1` determines whether it is 0- or 1-based. If no column is given, the first position in the start line is assumed. */ + column?: number; + /** End line of the location, present if the location refers to a range. The client capability `linesStartAt1` determines whether it is 0- or 1-based. */ + endLine?: number; + /** End position of the location within `endLine`, present if the location refers to a range. It is measured in UTF-16 code units and the client capability `columnsStartAt1` determines whether it is 0- or 1-based. */ + endColumn?: number; + }; + } + /** Information about the capabilities of a debug adapter. */ interface Capabilities { /** The debug adapter supports the `configurationDone` request. */ @@ -1923,7 +1985,7 @@ declare module DebugProtocol { endLine?: number; /** End position of the range covered by the stack frame. It is measured in UTF-16 code units and the client capability `columnsStartAt1` determines whether it is 0- or 1-based. */ endColumn?: number; - /** Indicates whether this frame can be restarted with the `restart` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartRequest` is true. If a debug adapter has this capability, then `canRestart` defaults to `true` if the property is absent. */ + /** Indicates whether this frame can be restarted with the `restartFrame` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartFrame` is true. If a debug adapter has this capability, then `canRestart` defaults to `true` if the property is absent. */ canRestart?: boolean; /** A memory reference for the current instruction pointer in this frame. */ instructionPointerReference?: string; @@ -1944,9 +2006,10 @@ declare module DebugProtocol { 'arguments': Scope contains method arguments. 'locals': Scope contains local variables. 'registers': Scope contains registers. Only a single `registers` scope should be returned from a `scopes` request. + 'returnValue': Scope contains one or more return values. etc. */ - presentationHint?: 'arguments' | 'locals' | 'registers' | string; + presentationHint?: 'arguments' | 'locals' | 'registers' | 'returnValue' | string; /** The variables of this scope can be retrieved by passing the value of `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The number of named variables in this scope. @@ -2011,6 +2074,16 @@ declare module DebugProtocol { This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; + /** A reference that allows the client to request the location where the variable is declared. This should be present only if the adapter is likely to be able to resolve the location. + + This reference shares the same lifetime as the `variablesReference`. See 'Lifetime of Object References' in the Overview section for details. + */ + declarationLocationReference?: number; + /** A reference that allows the client to request the location where the variable's value is declared. For example, if the variable contains a function pointer, the adapter may be able to look up the function's location. This should be present only if the adapter is likely to be able to resolve the location. + + This reference shares the same lifetime as the `variablesReference`. See 'Lifetime of Object References' in the Overview section for details. + */ + valueLocationReference?: number; } /** Properties of a variable that can be used to determine how to render the variable in the UI. */ @@ -2401,7 +2474,7 @@ declare module DebugProtocol { Values: 'source': In `SourceBreakpoint`s 'exception': In exception breakpoints applied in the `ExceptionFilterOptions` - 'data': In data breakpoints requested in the the `DataBreakpointInfo` request + 'data': In data breakpoints requested in the `DataBreakpointInfo` request 'instruction': In `InstructionBreakpoint`s etc. */ diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/debugUtils.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/debugUtils.ts index acf1e467..211dbe69 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -102,7 +102,7 @@ export function getExactExpressionStartAndEnd(lineContent: string, looseStart: n // If there are non-word characters after the cursor, we want to truncate the expression then. // For example in expression 'a.b.c.d', if the focus was under 'b', 'a.b' would be evaluated. if (matchingExpression) { - const subExpression: RegExp = /\w+/g; + const subExpression: RegExp = /(\w|\p{L})+/gu; let subExpressionResult: RegExpExecArray | null = null; while (subExpressionResult = subExpression.exec(matchingExpression)) { const subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length; @@ -307,6 +307,9 @@ function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: di.body?.instructions.forEach(di => fixSourcePath(false, di.location)); } break; + case 'locations': + fixSourcePath(false, (response).body?.source); + break; default: break; } diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/debugVisualizers.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/debugVisualizers.ts index 45d19aee..47baa50e 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/common/debugVisualizers.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/debugVisualizers.ts @@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { isDefined } from 'vs/base/common/types'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ExtensionIdentifier, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE, MainThreadDebugVisualization, IDebugVisualization, IDebugVisualizationContext, IExpression, IExpressionContainer, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; @@ -250,7 +250,7 @@ export class DebugVisualizerService implements IDebugVisualizerService { return context; } - private processExtensionRegistration(ext: Readonly) { + private processExtensionRegistration(ext: IExtensionDescription) { const viz = ext.contributes?.debugVisualizers; if (!(viz instanceof Array)) { return; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts index 5e9bb1fa..1784d05a 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts @@ -35,7 +35,7 @@ export async function showLoadedScriptMenu(accessor: ServicesAccessor) { const labelService = accessor.get(ILabelService); const localDisposableStore = new DisposableStore(); - const quickPick = quickInputService.createQuickPick(); + const quickPick = quickInputService.createQuickPick({ useSeparators: true }); localDisposableStore.add(quickPick); quickPick.matchOnLabel = quickPick.matchOnDescription = quickPick.matchOnDetail = quickPick.sortByLabel = false; quickPick.placeholder = nls.localize('moveFocusedView.selectView', "Search loaded scripts by name"); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts new file mode 100644 index 00000000..9199bd55 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; + +export class ReplAccessibilityAnnouncer extends Disposable implements IWorkbenchContribution { + static ID = 'debug.replAccessibilityAnnouncer'; + constructor( + @IDebugService debugService: IDebugService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILogService logService: ILogService + ) { + super(); + const viewModel = debugService.getViewModel(); + this._register(viewModel.onDidFocusSession((session) => { + if (!session) { + return; + } + this._register(session.onDidChangeReplElements((element) => { + if (!element || !('originalExpression' in element)) { + // element was removed or hasn't been resolved yet + return; + } + const value = element.toString(); + accessibilityService.status(value); + logService.trace('ReplAccessibilityAnnouncer#onDidChangeReplElements', element.originalExpression + ': ' + value); + })); + })); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/debug/common/replModel.ts b/patched-vscode/src/vs/workbench/contrib/debug/common/replModel.ts index 4402ed3f..91be4542 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/common/replModel.ts @@ -260,7 +260,7 @@ export interface INewReplElementData { export class ReplModel { private replElements: IReplElement[] = []; - private readonly _onDidChangeElements = new Emitter(); + private readonly _onDidChangeElements = new Emitter(); readonly onDidChangeElements = this._onDidChangeElements.event; constructor(private readonly configurationService: IConfigurationService) { } @@ -269,10 +269,10 @@ export class ReplModel { return this.replElements; } - async addReplExpression(session: IDebugSession, stackFrame: IStackFrame | undefined, name: string): Promise { - this.addReplElement(new ReplEvaluationInput(name)); - const result = new ReplEvaluationResult(name); - await result.evaluateExpression(name, session, stackFrame, 'repl'); + async addReplExpression(session: IDebugSession, stackFrame: IStackFrame | undefined, expression: string): Promise { + this.addReplElement(new ReplEvaluationInput(expression)); + const result = new ReplEvaluationResult(expression); + await result.evaluateExpression(expression, session, stackFrame, 'repl'); this.addReplElement(result); } @@ -306,7 +306,7 @@ export class ReplModel { if (!previousElement.value.endsWith('\n') && !previousElement.value.endsWith('\r\n') && previousElement.count === 1) { this.replElements[this.replElements.length - 1] = new ReplOutputElement( session, getUniqueId(), previousElement.value + output, sev, source); - this._onDidChangeElements.fire(); + this._onDidChangeElements.fire(undefined); return; } } @@ -337,14 +337,13 @@ export class ReplModel { this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH); } } - - this._onDidChangeElements.fire(); + this._onDidChangeElements.fire(newElement); } removeReplExpressions(): void { if (this.replElements.length > 0) { this.replElements = []; - this._onDidChangeElements.fire(); + this._onDidChangeElements.fire(undefined); } } diff --git a/patched-vscode/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/patched-vscode/src/vs/workbench/contrib/debug/node/debugAdapter.ts index 4275f9e0..43f92b45 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -222,13 +222,26 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { throw new Error(nls.localize('unableToLaunchDebugAdapterNoArgs', "Unable to launch debug adapter.")); } } else { + let spawnCommand = command; + let spawnArgs = args; const spawnOptions: cp.SpawnOptions = { env: env }; if (options.cwd) { spawnOptions.cwd = options.cwd; } - this.serverProcess = cp.spawn(command, args, spawnOptions); + if (platform.isWindows && (command.endsWith('.bat') || command.endsWith('.cmd'))) { + // https://github.com/microsoft/vscode/issues/224184 + spawnOptions.shell = true; + spawnCommand = `"${command}"`; + spawnArgs = args.map(a => { + a = a.replace(/"/g, '\\"'); // Escape existing double quotes with \ + // Wrap in double quotes + return `"${a}"`; + }); + } + + this.serverProcess = cp.spawn(spawnCommand, spawnArgs, spawnOptions); } this.serverProcess.on('error', err => { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index a5ba220e..06ae7489 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isWindows } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { NullHoverService } from 'vs/platform/hover/test/browser/nullHoverService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; @@ -23,6 +24,66 @@ import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; const $ = dom.$; +function assertVariable(session: MockSession, scope: Scope, disposables: Pick, linkDetector: LinkDetector, displayType: boolean) { + let variable = new Variable(session, 1, scope, 2, 'foo', 'bar.foo', undefined, 0, 0, undefined, {}, 'string'); + let expression = $('.'); + let name = $('.'); + let type = $('.'); + let value = $('.'); + const label = new HighlightedLabel(name); + const lazyButton = $('.'); + const store = disposables.add(new DisposableStore()); + renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, type, value, label, lazyButton }, false, [], undefined, displayType); + + assert.strictEqual(label.element.textContent, 'foo'); + assert.strictEqual(value.textContent, ''); + + variable.value = 'hey'; + expression = $('.'); + name = $('.'); + type = $('.'); + value = $('.'); + renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, type, value, label, lazyButton }, false, [], linkDetector, displayType); + assert.strictEqual(value.textContent, 'hey'); + assert.strictEqual(label.element.textContent, displayType ? 'foo: ' : 'foo ='); + assert.strictEqual(type.textContent, displayType ? 'string =' : ''); + + variable.value = isWindows ? 'C:\\foo.js:5' : '/foo.js:5'; + expression = $('.'); + name = $('.'); + type = $('.'); + value = $('.'); + renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, type, value, label, lazyButton }, false, [], linkDetector, displayType); + assert.ok(value.querySelector('a')); + assert.strictEqual(value.querySelector('a')!.textContent, variable.value); + + variable = new Variable(session, 1, scope, 2, 'console', 'console', '5', 0, 0, undefined, { kind: 'virtual' }); + expression = $('.'); + name = $('.'); + type = $('.'); + value = $('.'); + renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, type, value, label, lazyButton }, false, [], linkDetector, displayType); + assert.strictEqual(name.className, 'virtual'); + assert.strictEqual(label.element.textContent, 'console ='); + assert.strictEqual(value.className, 'value number'); + + variable = new Variable(session, 1, scope, 2, 'xpto', 'xpto.xpto', undefined, 0, 0, undefined, {}, 'custom-type'); + renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, type, value, label, lazyButton }, false, [], linkDetector, displayType); + assert.strictEqual(label.element.textContent, 'xpto'); + assert.strictEqual(value.textContent, ''); + variable.value = '2'; + expression = $('.'); + name = $('.'); + type = $('.'); + value = $('.'); + renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, type, value, label, lazyButton }, false, [], linkDetector, displayType); + assert.strictEqual(value.textContent, '2'); + assert.strictEqual(label.element.textContent, displayType ? 'xpto: ' : 'xpto ='); + assert.strictEqual(type.textContent, displayType ? 'custom-type =' : ''); + + label.dispose(); +} + suite('Debug - Base Debug View', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let linkDetector: LinkDetector; @@ -33,6 +94,7 @@ suite('Debug - Base Debug View', () => { setup(() => { const instantiationService: TestInstantiationService = workbenchInstantiationService(undefined, disposables); linkDetector = instantiationService.createInstance(LinkDetector); + instantiationService.stub(IHoverService, NullHoverService); }); test('render view tree', () => { @@ -47,37 +109,38 @@ suite('Debug - Base Debug View', () => { test('render expression value', () => { let container = $('.container'); - renderExpressionValue('render \n me', container, {}, NullHoverService); + const store = disposables.add(new DisposableStore()); + renderExpressionValue(store, 'render \n me', container, {}, NullHoverService); assert.strictEqual(container.className, 'value'); assert.strictEqual(container.textContent, 'render \n me'); const expression = new Expression('console'); expression.value = 'Object'; container = $('.container'); - renderExpressionValue(expression, container, { colorize: true }, NullHoverService); + renderExpressionValue(store, expression, container, { colorize: true }, NullHoverService); assert.strictEqual(container.className, 'value unavailable error'); expression.available = true; expression.value = '"string value"'; container = $('.container'); - renderExpressionValue(expression, container, { colorize: true, linkDetector }, NullHoverService); + renderExpressionValue(store, expression, container, { colorize: true, linkDetector }, NullHoverService); assert.strictEqual(container.className, 'value string'); assert.strictEqual(container.textContent, '"string value"'); expression.type = 'boolean'; container = $('.container'); - renderExpressionValue(expression, container, { colorize: true }, NullHoverService); + renderExpressionValue(store, expression, container, { colorize: true }, NullHoverService); assert.strictEqual(container.className, 'value boolean'); assert.strictEqual(container.textContent, expression.value); expression.value = 'this is a long string'; container = $('.container'); - renderExpressionValue(expression, container, { colorize: true, maxValueLength: 4, linkDetector }, NullHoverService); + renderExpressionValue(store, expression, container, { colorize: true, maxValueLength: 4, linkDetector }, NullHoverService); assert.strictEqual(container.textContent, 'this...'); expression.value = isWindows ? 'C:\\foo.js:5' : '/foo.js:5'; container = $('.container'); - renderExpressionValue(expression, container, { colorize: true, linkDetector }, NullHoverService); + renderExpressionValue(store, expression, container, { colorize: true, linkDetector }, NullHoverService); assert.ok(container.querySelector('a')); assert.strictEqual(container.querySelector('a')!.textContent, expression.value); }); @@ -85,47 +148,32 @@ suite('Debug - Base Debug View', () => { test('render variable', () => { const session = new MockSession(); const thread = new Thread(session, 'mockthread', 1); - const stackFrame = new StackFrame(thread, 1, null!, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: undefined!, endColumn: undefined! }, 0, true); + const range = { + startLineNumber: 1, + startColumn: 1, + endLineNumber: undefined!, + endColumn: undefined! + }; + const stackFrame = new StackFrame(thread, 1, null!, 'app.js', 'normal', range, 0, true); const scope = new Scope(stackFrame, 1, 'local', 1, false, 10, 10); - let variable = new Variable(session, 1, scope, 2, 'foo', 'bar.foo', undefined, 0, 0, undefined, {}, 'string'); - let expression = $('.'); - let name = $('.'); - let value = $('.'); - const label = new HighlightedLabel(name); - const lazyButton = $('.'); - const store = disposables.add(new DisposableStore()); - renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, value, label, lazyButton }, false, []); - - assert.strictEqual(label.element.textContent, 'foo'); - assert.strictEqual(value.textContent, ''); - - variable.value = 'hey'; - expression = $('.'); - name = $('.'); - value = $('.'); - renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, value, label, lazyButton }, false, [], linkDetector); - assert.strictEqual(value.textContent, 'hey'); - assert.strictEqual(label.element.textContent, 'foo:'); - - variable.value = isWindows ? 'C:\\foo.js:5' : '/foo.js:5'; - expression = $('.'); - name = $('.'); - value = $('.'); - renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, value, label, lazyButton }, false, [], linkDetector); - assert.ok(value.querySelector('a')); - assert.strictEqual(value.querySelector('a')!.textContent, variable.value); - - variable = new Variable(session, 1, scope, 2, 'console', 'console', '5', 0, 0, undefined, { kind: 'virtual' }); - expression = $('.'); - name = $('.'); - value = $('.'); - renderVariable(store, NullCommandService, NullHoverService, variable, { expression, name, value, label, lazyButton }, false, [], linkDetector); - assert.strictEqual(name.className, 'virtual'); - assert.strictEqual(label.element.textContent, 'console:'); - assert.strictEqual(value.className, 'value number'); - - label.dispose(); + assertVariable(session, scope, disposables, linkDetector, false); + + }); + + test('render variable with display type setting', () => { + const session = new MockSession(); + const thread = new Thread(session, 'mockthread', 1); + const range = { + startLineNumber: 1, + startColumn: 1, + endLineNumber: undefined!, + endColumn: undefined! + }; + const stackFrame = new StackFrame(thread, 1, null!, 'app.js', 'normal', range, 0, true); + const scope = new Scope(stackFrame, 1, 'local', 1, false, 10, 10); + + assertVariable(session, scope, disposables, linkDetector, true); }); test('statusbar in debug mode', () => { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 6b47aa1d..292fa730 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { dispose } from 'vs/base/common/lifecycle'; import { URI as uri } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index f5ec3d56..e5da4f53 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { ThemeIcon } from 'vs/base/common/themables'; import { Constants } from 'vs/base/common/uint'; import { generateUuid } from 'vs/base/common/uuid'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; +import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -41,7 +42,7 @@ export function createTestSession(model: DebugModel, name = 'mockSession', optio } }; } - } as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService()); + } as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService(), undefined!, undefined!, new TestAccessibilityService()); } function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame; secondStackFrame: StackFrame } { @@ -445,7 +446,7 @@ suite('Debug - CallStack', () => { override get state(): State { return State.Stopped; } - }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService()); + }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService(), undefined!, undefined!, new TestAccessibilityService()); disposables.add(session); const runningSession = createTestSession(model); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index c92a240a..41c662c0 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { isHTMLSpanElement } from 'vs/base/browser/dom'; import { Color, RGBA } from 'vs/base/common/color'; import { DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts index 71b53f1a..d6b8d2fc 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -61,7 +61,8 @@ suite('debugConfigurationManager', () => { new TestExtensionService(), new TestHistoryService(), new UriIdentityService(fileService), - new ContextKeyService(configurationService)); + new ContextKeyService(configurationService), + new NullLogService()); }); teardown(() => disposables.dispose()); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts index c45e6dba..480c811f 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { findExpressionInStackFrame } from 'vs/workbench/contrib/debug/browser/debugHover'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts index 7647820f..61e92664 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { decodeBase64, encodeBase64, VSBuffer } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; import { mockObject, MockObject } from 'vs/base/test/common/mock'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugSession.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugSession.test.ts index a4b59eee..b58f2509 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugSession.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugSession.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ThreadStatusScheduler } from 'vs/workbench/contrib/debug/browser/debugSession'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts index 672ae3c3..aad31249 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugUtils.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugUtils.test.ts index 585497db..d6b033ff 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugUtils.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugUtils.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfig } from 'vs/workbench/contrib/debug/common/debug'; import { formatPII, getExactExpressionStartAndEnd, getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils'; @@ -41,6 +41,9 @@ suite('Debug - Utils', () => { assert.deepStrictEqual(getExactExpressionStartAndEnd('var t = a.b;c.d.name', 16, 20), { start: 13, end: 20 }); assert.deepStrictEqual(getExactExpressionStartAndEnd('var t = a.b.c-d.name', 16, 20), { start: 15, end: 20 }); + + assert.deepStrictEqual(getExactExpressionStartAndEnd('var aøñéå文 = a.b.c-d.name', 5, 5), { start: 5, end: 10 }); + assert.deepStrictEqual(getExactExpressionStartAndEnd('aøñéå文.aøñéå文.aøñéå文', 9, 9), { start: 1, end: 13 }); }); test('config presentation', () => { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugViewModel.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugViewModel.test.ts index d42de786..6e2f7644 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugViewModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/debugViewModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { NullLogService } from 'vs/platform/log/common/log'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts index 6a38dac9..1b888a6e 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { isHTMLAnchorElement } from 'vs/base/browser/dom'; import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts index 3f3549c6..5a9f3f2f 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { mock, mockObject } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index f385cbbb..54da8d61 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { timeout } from 'vs/base/common/async'; import severity from 'vs/base/common/severity'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/variablesView.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/variablesView.test.ts new file mode 100644 index 00000000..5eb511bc --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/variablesView.test.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as dom from 'vs/base/browser/dom'; +import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +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 { Scope, StackFrame, Thread, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { MockDebugService, MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { NullHoverService } from 'vs/platform/hover/test/browser/nullHoverService'; +import { IDebugService, IViewModel } from 'vs/workbench/contrib/debug/common/debug'; +import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +const $ = dom.$; + +function assertVariable(disposables: Pick, variablesRenderer: VariablesRenderer, displayType: boolean) { + const session = new MockSession(); + const thread = new Thread(session, 'mockthread', 1); + const range = { + startLineNumber: 1, + startColumn: 1, + endLineNumber: undefined!, + endColumn: undefined! + }; + const stackFrame = new StackFrame(thread, 1, null!, 'app.js', 'normal', range, 0, true); + const scope = new Scope(stackFrame, 1, 'local', 1, false, 10, 10); + const node = { + element: new Variable(session, 1, scope, 2, 'foo', 'bar.foo', undefined, 0, 0, undefined, {}, 'string'), + depth: 0, + visibleChildrenCount: 1, + visibleChildIndex: -1, + collapsible: false, + collapsed: false, + visible: true, + filterData: undefined, + children: [] + }; + const expression = $('.'); + const name = $('.'); + const type = $('.'); + const value = $('.'); + const label = disposables.add(new HighlightedLabel(name)); + const lazyButton = $('.'); + const inputBoxContainer = $('.'); + const elementDisposable = disposables.add(new DisposableStore()); + const templateDisposable = disposables.add(new DisposableStore()); + const currentElement = undefined; + const data = { + expression, + name, + type, + value, + label, + lazyButton, + inputBoxContainer, + elementDisposable, + templateDisposable, + currentElement + }; + variablesRenderer.renderElement(node, 0, data); + assert.strictEqual(value.textContent, ''); + assert.strictEqual(label.element.textContent, 'foo'); + + node.element.value = 'xpto'; + variablesRenderer.renderElement(node, 0, data); + assert.strictEqual(value.textContent, 'xpto'); + assert.strictEqual(type.textContent, displayType ? 'string =' : ''); + assert.strictEqual(label.element.textContent, displayType ? 'foo: ' : 'foo ='); +} + +suite('Debug - Variable Debug View', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + let variablesRenderer: VariablesRenderer; + let instantiationService: TestInstantiationService; + let linkDetector: LinkDetector; + let configurationService: TestConfigurationService; + + setup(() => { + instantiationService = workbenchInstantiationService(undefined, disposables); + linkDetector = instantiationService.createInstance(LinkDetector); + const debugService = new MockDebugService(); + instantiationService.stub(IHoverService, NullHoverService); + debugService.getViewModel = () => { focusedStackFrame: undefined, getSelectedExpression: () => undefined }; + debugService.getViewModel().getSelectedExpression = () => undefined; + instantiationService.stub(IDebugService, debugService); + }); + + test('variable expressions with display type', () => { + configurationService = new TestConfigurationService({ + debug: { + showVariableTypes: true + } + }); + instantiationService.stub(IConfigurationService, configurationService); + variablesRenderer = instantiationService.createInstance(VariablesRenderer, linkDetector); + assertVariable(disposables, variablesRenderer, true); + }); + + test('variable expressions', () => { + configurationService = new TestConfigurationService({ + debug: { + showVariableTypes: false + } + }); + instantiationService.stub(IConfigurationService, configurationService); + variablesRenderer = instantiationService.createInstance(VariablesRenderer, linkDetector); + assertVariable(disposables, variablesRenderer, false); + }); +}); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/watch.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/watch.test.ts index dabf6668..92b54afe 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/watch.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/watch.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DebugModel, Expression } from 'vs/workbench/contrib/debug/common/debugModel'; import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/browser/watchExpressionView.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/watchExpressionView.test.ts new file mode 100644 index 00000000..17ea09ba --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/browser/watchExpressionView.test.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as dom from 'vs/base/browser/dom'; +import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +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 { WatchExpressionsRenderer } from 'vs/workbench/contrib/debug/browser/watchExpressionsView'; +import { Scope, StackFrame, Thread, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { MockDebugService, MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { NullHoverService } from 'vs/platform/hover/test/browser/nullHoverService'; +import { IDebugService, IViewModel } from 'vs/workbench/contrib/debug/common/debug'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +const $ = dom.$; + +function assertWatchVariable(disposables: Pick, watchExpressionsRenderer: WatchExpressionsRenderer, displayType: boolean) { + const session = new MockSession(); + const thread = new Thread(session, 'mockthread', 1); + const range = { + startLineNumber: 1, + startColumn: 1, + endLineNumber: undefined!, + endColumn: undefined! + }; + const stackFrame = new StackFrame(thread, 1, null!, 'app.js', 'normal', range, 0, true); + const scope = new Scope(stackFrame, 1, 'local', 1, false, 10, 10); + const node = { + element: new Variable(session, 1, scope, 2, 'foo', 'bar.foo', undefined, 0, 0, undefined, {}, 'string'), + depth: 0, + visibleChildrenCount: 1, + visibleChildIndex: -1, + collapsible: false, + collapsed: false, + visible: true, + filterData: undefined, + children: [] + }; + const expression = $('.'); + const name = $('.'); + const type = $('.'); + const value = $('.'); + const label = disposables.add(new HighlightedLabel(name)); + const lazyButton = $('.'); + const inputBoxContainer = $('.'); + const elementDisposable = disposables.add(new DisposableStore()); + const templateDisposable = disposables.add(new DisposableStore()); + const currentElement = undefined; + const data = { + expression, + name, + type, + value, + label, + lazyButton, + inputBoxContainer, + elementDisposable, + templateDisposable, + currentElement + }; + watchExpressionsRenderer.renderElement(node, 0, data); + assert.strictEqual(value.textContent, ''); + assert.strictEqual(label.element.textContent, displayType ? 'foo: ' : 'foo ='); + + node.element.value = 'xpto'; + watchExpressionsRenderer.renderElement(node, 0, data); + assert.strictEqual(value.textContent, 'xpto'); + assert.strictEqual(type.textContent, displayType ? 'string =' : ''); + assert.strictEqual(label.element.textContent, displayType ? 'foo: ' : 'foo ='); +} + +suite('Debug - Watch Debug View', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + let watchExpressionsRenderer: WatchExpressionsRenderer; + let instantiationService: TestInstantiationService; + let configurationService: TestConfigurationService; + + setup(() => { + instantiationService = workbenchInstantiationService(undefined, disposables); + const debugService = new MockDebugService(); + instantiationService.stub(IHoverService, NullHoverService); + debugService.getViewModel = () => { focusedStackFrame: undefined, getSelectedExpression: () => undefined }; + debugService.getViewModel().getSelectedExpression = () => undefined; + instantiationService.stub(IDebugService, debugService); + }); + + test('watch expressions with display type', () => { + configurationService = new TestConfigurationService({ + debug: { + showVariableTypes: true + } + }); + instantiationService.stub(IConfigurationService, configurationService); + watchExpressionsRenderer = instantiationService.createInstance(WatchExpressionsRenderer, null as any); + assertWatchVariable(disposables, watchExpressionsRenderer, true); + }); + + test('watch expressions', () => { + configurationService = new TestConfigurationService({ + debug: { + showVariableTypes: false + } + }); + instantiationService.stub(IConfigurationService, configurationService); + watchExpressionsRenderer = instantiationService.createInstance(WatchExpressionsRenderer, null as any); + assertWatchVariable(disposables, watchExpressionsRenderer, false); + }); +}); diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test.ts index 2246b7fa..cfcc1af8 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts index 26c55498..e3cf88f9 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DeferredPromise } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mockObject } from 'vs/base/test/common/mock'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 464a4794..e71bad73 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -13,7 +13,7 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; +import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugLocationReferenced, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; @@ -258,7 +258,7 @@ export class MockSession implements IDebugSession { } removeReplExpressions(): void { } - get onDidChangeReplElements(): Event { + get onDidChangeReplElements(): Event { throw new Error('not implemented'); } @@ -451,6 +451,9 @@ export class MockSession implements IDebugSession { goto(threadId: number, targetId: number): Promise { throw new Error('Method not implemented.'); } + resolveLocationReference(locationReference: number): Promise { + throw new Error('Method not implemented.'); + } } export class MockRawSession { diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/node/debugger.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/node/debugger.test.ts index 4d2d38f1..e618a455 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/node/debugger.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/node/debugger.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { join, normalize } from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { IDebugAdapterExecutable, IConfig, IDebugSession, IAdapterManager } from 'vs/workbench/contrib/debug/common/debug'; @@ -64,7 +64,8 @@ suite('Debug - Debugger', () => { 'debuggers': [ debuggerContribution ] - } + }, + enabledApiProposals: undefined, }; const extensionDescriptor1 = { @@ -89,7 +90,8 @@ suite('Debug - Debugger', () => { args: ['parg'] } ] - } + }, + enabledApiProposals: undefined, }; const extensionDescriptor2 = { @@ -122,7 +124,8 @@ suite('Debug - Debugger', () => { } } ] - } + }, + enabledApiProposals: undefined, }; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts index 9247a0ee..89bb6968 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/node/streamDebugAdapter.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as crypto from 'crypto'; import * as net from 'net'; import * as platform from 'vs/base/common/platform'; diff --git a/patched-vscode/src/vs/workbench/contrib/debug/test/node/terminals.test.ts b/patched-vscode/src/vs/workbench/contrib/debug/test/node/terminals.test.ts index efdf2f94..bd81c9c1 100644 --- a/patched-vscode/src/vs/workbench/contrib/debug/test/node/terminals.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/debug/test/node/terminals.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { prepareCommand } from 'vs/workbench/contrib/debug/node/terminals'; diff --git a/patched-vscode/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/patched-vscode/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index deb9b7e7..2fb635b1 100644 --- a/patched-vscode/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -824,7 +824,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo // Prompt the user to use edit sessions if they currently could benefit from using it if (this.hasEditSession()) { - const quickpick = this.quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const quickpick = disposables.add(this.quickInputService.createQuickPick()); quickpick.placeholder = localize('continue with cloud changes', "Select whether to bring your working changes with you"); quickpick.ok = false; quickpick.ignoreFocusOut = true; @@ -833,14 +834,14 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo quickpick.items = [withCloudChanges, withoutCloudChanges]; const continueWithCloudChanges = await new Promise((resolve, reject) => { - quickpick.onDidAccept(() => { + disposables.add(quickpick.onDidAccept(() => { resolve(quickpick.selectedItems[0] === withCloudChanges); - quickpick.hide(); - }); - quickpick.onDidHide(() => { + disposables.dispose(); + })); + disposables.add(quickpick.onDidHide(() => { reject(new CancellationError()); - quickpick.hide(); - }); + disposables.dispose(); + })); quickpick.show(); }); @@ -960,7 +961,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } private async pickContinueEditSessionDestination(): Promise { - const quickPick = this.quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const quickPick = disposables.add(this.quickInputService.createQuickPick({ useSeparators: true })); const workspaceContext = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? this.contextService.getWorkspace().folders[0].name @@ -972,9 +974,12 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo }); const command = await new Promise((resolve, reject) => { - quickPick.onDidHide(() => resolve(undefined)); + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + resolve(undefined); + })); - quickPick.onDidAccept((e) => { + disposables.add(quickPick.onDidAccept((e) => { const selection = quickPick.activeItems[0].command; if (selection === installAdditionalContinueOnOptionsCommand.id) { @@ -983,16 +988,16 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo resolve(selection); quickPick.hide(); } - }); + })); quickPick.show(); - quickPick.onDidTriggerItemButton(async (e) => { + disposables.add(quickPick.onDidTriggerItemButton(async (e) => { if (e.item.documentation !== undefined) { const uri = URI.isUri(e.item.documentation) ? URI.parse(e.item.documentation) : await this.commandService.executeCommand(e.item.documentation); void this.openerService.open(uri, { openExternal: true }); } - }); + })); }); quickPick.dispose(); diff --git a/patched-vscode/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts b/patched-vscode/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts index f612c22c..bacd9020 100644 --- a/patched-vscode/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts +++ b/patched-vscode/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts @@ -311,24 +311,25 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes * Prompts the user to pick an authentication option for storing and getting edit sessions. */ private async getAccountPreference(reason: 'read' | 'write'): Promise { - const quickpick = this.quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const quickpick = disposables.add(this.quickInputService.createQuickPick({ useSeparators: true })); quickpick.ok = false; quickpick.placeholder = reason === 'read' ? localize('choose account read placeholder', "Select an account to restore your working changes from the cloud") : localize('choose account placeholder', "Select an account to store your working changes in the cloud"); quickpick.ignoreFocusOut = true; quickpick.items = await this.createQuickpickItems(); return new Promise((resolve, reject) => { - quickpick.onDidHide((e) => { + disposables.add(quickpick.onDidHide((e) => { reject(new CancellationError()); - quickpick.dispose(); - }); + disposables.dispose(); + })); - quickpick.onDidAccept(async (e) => { + disposables.add(quickpick.onDidAccept(async (e) => { const selection = quickpick.selectedItems[0]; const session = 'provider' in selection ? { ...await this.authenticationService.createSession(selection.provider.id, selection.provider.scopes), providerId: selection.provider.id } : ('session' in selection ? selection.session : undefined); resolve(session); quickpick.hide(); - }); + })); quickpick.show(); }); diff --git a/patched-vscode/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/patched-vscode/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index c6be96b0..244f3dfe 100644 --- a/patched-vscode/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -20,7 +20,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { mock } from 'vs/base/test/common/mock'; import * as sinon from 'sinon'; -import * as assert from 'assert'; +import assert from 'assert'; import { ChangeType, FileType, IEditSessionsLogService, IEditSessionsStorageService } from 'vs/workbench/contrib/editSessions/common/editSessions'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; diff --git a/patched-vscode/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts b/patched-vscode/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts index f3a0b28b..76d14f29 100644 --- a/patched-vscode/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts @@ -5,7 +5,7 @@ import { IGrammarContributions, EmmetEditorAction } from 'vs/workbench/contrib/emmet/browser/emmetActions'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts b/patched-vscode/src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts index 9969928a..48580f61 100644 --- a/patched-vscode/src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/encryption/electron-sandbox/encryption.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isLinux } from 'vs/base/common/platform'; -import { stripComments } from 'vs/base/common/stripComments'; +import { parse } from 'vs/base/common/jsonc'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -34,7 +34,7 @@ class EncryptionContribution implements IWorkbenchContribution { } try { const content = await this.fileService.readFile(this.environmentService.argvResource); - const argv = JSON.parse(stripComments(content.value.toString())); + const argv = parse(content.value.toString()); if (argv['password-store'] === 'gnome' || argv['password-store'] === 'gnome-keyring') { this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['password-store'], value: 'gnome-libsecret' }], true); } diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index 1ee623c2..a298cf3c 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -369,14 +369,14 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } else { title = nls.localize('extensionActivating', "Extension is activating..."); } - data.elementDisposables.push(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), data.activationTime, title)); + data.elementDisposables.push(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.activationTime, title)); clearNode(data.msgContainer); if (this._getUnresponsiveProfile(element.description.identifier)) { const el = $('span', undefined, ...renderLabelWithIcons(` $(alert) Unresponsive`)); const extensionHostFreezTitle = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); - data.elementDisposables.push(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), el, extensionHostFreezTitle)); + data.elementDisposables.push(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), el, extensionHostFreezTitle)); data.msgContainer.appendChild(el); } @@ -425,7 +425,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const element = $('span', undefined, `${nls.localize('requests count', "{0} Requests: {1} (Overall)", feature.label, accessData.totalCount)}${accessData.current ? nls.localize('session requests count', ", {0} (Session)", accessData.current.count) : ''}`); if (accessData.current) { const title = nls.localize('requests count title', "Last request was {0}.", fromNow(accessData.current.lastAccessed, true, true)); - data.elementDisposables.push(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), element, title)); + data.elementDisposables.push(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), element, title)); } data.msgContainer.appendChild(element); diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index dc3c89dc..5b54f2c8 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -49,11 +49,11 @@ import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { ExtensionFeaturesTab } from 'vs/workbench/contrib/extensions/browser/extensionFeaturesTab'; import { - ActionWithDropDownAction, + ButtonWithDropDownExtensionAction, ClearLanguageAction, DisableDropDownAction, EnableDropDownAction, - ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, + ButtonWithDropdownExtensionActionViewItem, DropDownExtensionAction, ExtensionEditorManageExtensionAction, ExtensionStatusAction, ExtensionStatusLabelAction, @@ -71,7 +71,7 @@ import { UninstallAction, UpdateAction, WebInstallAction, - TogglePreReleaseExtensionAction + TogglePreReleaseExtensionAction, } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList'; import { ExtensionData, ExtensionsGridView, ExtensionsTree, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; @@ -200,7 +200,7 @@ class VersionWidget extends ExtensionWithDifferentGalleryVersionWidget { ) { super(); this.element = append(container, $('code.version')); - this._register(hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.element, localize('extension version', "Extension Version"))); + this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, localize('extension version', "Extension Version"))); this.render(); } render(): void { @@ -287,11 +287,11 @@ export class ExtensionEditor extends EditorPane { const details = append(header, $('.details')); const title = append(details, $('.title')); const name = append(title, $('span.name.clickable', { role: 'heading', tabIndex: 0 })); - this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), name, localize('name', "Extension name"))); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), name, localize('name', "Extension name"))); const versionWidget = new VersionWidget(title, this.hoverService); const preview = append(title, $('span.preview')); - this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), preview, localize('preview', "Preview"))); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), preview, localize('preview', "Preview"))); preview.textContent = localize('preview', "Preview"); const builtin = append(title, $('span.builtin')); @@ -299,7 +299,7 @@ export class ExtensionEditor extends EditorPane { const subtitle = append(details, $('.subtitle')); const publisher = append(append(subtitle, $('.subtitle-entry')), $('.publisher.clickable', { tabIndex: 0 })); - this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), publisher, localize('publisher', "Publisher"))); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), publisher, localize('publisher', "Publisher"))); publisher.setAttribute('role', 'button'); const publisherDisplayName = append(publisher, $('.publisher-name')); const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $('.verified-publisher')), false); @@ -308,11 +308,11 @@ export class ExtensionEditor extends EditorPane { resource.setAttribute('role', 'button'); const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { tabIndex: 0 })); - this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), installCount, localize('install count', "Install count"))); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), installCount, localize('install count', "Install count"))); const installCountWidget = this.instantiationService.createInstance(InstallCountWidget, installCount, false); const rating = append(append(subtitle, $('.subtitle-entry')), $('span.rating.clickable', { tabIndex: 0 })); - this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), rating, localize('rating', "Rating"))); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), rating, localize('rating', "Rating"))); rating.setAttribute('role', 'link'); // #132645 const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, rating, false); @@ -333,8 +333,7 @@ export class ExtensionEditor extends EditorPane { const actions = [ this.instantiationService.createInstance(ExtensionRuntimeStateAction), this.instantiationService.createInstance(ExtensionStatusLabelAction), - this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.updateActions', '', - [[this.instantiationService.createInstance(UpdateAction, true)], [this.instantiationService.createInstance(ToggleAutoUpdateForExtensionAction, true, [true, 'onlyEnabledExtensions'])]]), + this.instantiationService.createInstance(UpdateAction, true), this.instantiationService.createInstance(SetColorThemeAction), this.instantiationService.createInstance(SetFileIconThemeAction), this.instantiationService.createInstance(SetProductIconThemeAction), @@ -348,26 +347,35 @@ export class ExtensionEditor extends EditorPane { this.instantiationService.createInstance(WebInstallAction), installAction, this.instantiationService.createInstance(InstallingLabelAction), - this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.uninstall', UninstallAction.UninstallLabel, [ + this.instantiationService.createInstance(ButtonWithDropDownExtensionAction, 'extensions.uninstall', UninstallAction.UninstallClass, [ [ this.instantiationService.createInstance(MigrateDeprecatedExtensionAction, false), this.instantiationService.createInstance(UninstallAction), - this.instantiationService.createInstance(InstallAnotherVersionAction), + this.instantiationService.createInstance(InstallAnotherVersionAction, null, true), ] ]), this.instantiationService.createInstance(TogglePreReleaseExtensionAction), - this.instantiationService.createInstance(ToggleAutoUpdateForExtensionAction, false, [false, 'onlySelectedExtensions']), + this.instantiationService.createInstance(ToggleAutoUpdateForExtensionAction), new ExtensionEditorManageExtensionAction(this.scopedContextKeyService || this.contextKeyService, this.instantiationService), ]; const actionsAndStatusContainer = append(details, $('.actions-status-container')); const extensionActionBar = this._register(new ActionBar(actionsAndStatusContainer, { actionViewItemProvider: (action: IAction, options) => { - if (action instanceof ExtensionDropDownAction) { + if (action instanceof DropDownExtensionAction) { return action.createActionViewItem(options); } - if (action instanceof ActionWithDropDownAction) { - return new ExtensionActionWithDropdownActionViewItem(action, { ...options, icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); + if (action instanceof ButtonWithDropDownExtensionAction) { + return new ButtonWithDropdownExtensionActionViewItem( + action, + { + ...options, + icon: true, + label: true, + menuActionsOrProvider: { getActions: () => action.menuActions }, + menuActionClassNames: action.menuActionClassNames + }, + this.contextMenuService); } if (action instanceof ToggleAutoUpdateForExtensionAction) { return new CheckboxActionViewItem(undefined, action, { ...options, icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); @@ -463,9 +471,16 @@ export class ExtensionEditor extends EditorPane { const currentOptions: IExtensionEditorOptions | undefined = this.options; super.setOptions(options); this.updatePreReleaseVersionContext(); + if (this.input && this.template && currentOptions?.showPreReleaseVersion !== options?.showPreReleaseVersion) { this.render((this.input as ExtensionsInput).extension, this.template, !!options?.preserveFocus); + return; } + + if (options?.tab) { + this.template?.navbar.switch(options.tab); + } + } private updatePreReleaseVersionContext(): void { @@ -552,14 +567,14 @@ export class ExtensionEditor extends EditorPane { const workspaceFolder = this.contextService.getWorkspaceFolder(location); if (workspaceFolder && extension.isWorkspaceScoped) { template.resource.parentElement?.classList.add('clickable'); - this.transientDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), template.resource, this.uriIdentityService.extUri.relativePath(workspaceFolder.uri, location))); + this.transientDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), template.resource, this.uriIdentityService.extUri.relativePath(workspaceFolder.uri, location))); template.resource.textContent = localize('workspace extension', "Workspace Extension"); this.transientDisposables.add(onClick(template.resource, () => { this.viewsService.openView(EXPLORER_VIEW_ID, true).then(() => this.explorerService.select(location, true)); })); } else { template.resource.parentElement?.classList.remove('clickable'); - this.transientDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), template.resource, location.path)); + this.transientDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), template.resource, location.path)); template.resource.textContent = localize('local extension', "Local Extension"); } } @@ -774,7 +789,7 @@ export class ExtensionEditor extends EditorPane { return ''; } - const content = await renderMarkdownDocument(contents, this.extensionService, this.languageService, extension.type !== ExtensionType.System, false, token); + const content = await renderMarkdownDocument(contents, this.extensionService, this.languageService, { shouldSanitize: extension.type !== ExtensionType.System, token }); if (token?.isCancellationRequested) { return ''; } @@ -968,7 +983,7 @@ export class ExtensionEditor extends EditorPane { for (const [label, uri] of resources) { const resource = append(resourcesElement, $('a.resource', { tabindex: '0' }, label)); this.transientDisposables.add(onClick(resource, () => this.openerService.open(uri))); - this.transientDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), resource, uri.toString())); + this.transientDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), resource, uri.toString())); } } } diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index 42680a4b..794127b7 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -178,10 +178,10 @@ export class ExtensionFeaturesTab extends Themable { return; } - const splitView = new SplitView(this.domNode, { + const splitView = this._register(new SplitView(this.domNode, { orientation: Orientation.HORIZONTAL, proportionalLayout: true - }); + })); this.layoutParticipants.push({ layout: (height: number, width: number) => { splitView.el.style.height = `${height - 14}px`; @@ -190,7 +190,7 @@ export class ExtensionFeaturesTab extends Themable { }); const featuresListContainer = $('.features-list-container'); - const list = this.createFeaturesList(featuresListContainer); + const list = this._register(this.createFeaturesList(featuresListContainer)); list.splice(0, list.length, features); const featureViewContainer = $('.feature-view-container'); diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts index 4152bf2f..1f510c15 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts @@ -14,6 +14,7 @@ import { isString } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionRecommendationNotificationService, IExtensionRecommendations, RecommendationsNotificationResult, RecommendationSource, RecommendationSourceToString } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -283,9 +284,18 @@ export class ExtensionRecommendationNotificationService extends Disposable imple const installExtensions = async (isMachineScoped: boolean) => { this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue)); onDidInstallRecommendedExtensions(extensions); + const galleryExtensions: IGalleryExtension[] = [], resourceExtensions: IExtension[] = []; + for (const extension of extensions) { + if (extension.gallery) { + galleryExtensions.push(extension.gallery); + } else if (extension.resourceExtension) { + resourceExtensions.push(extension); + } + } await Promises.settled([ Promises.settled(extensions.map(extension => this.extensionsWorkbenchService.open(extension, { pinned: true }))), - this.extensionManagementService.installGalleryExtensions(extensions.map(e => ({ extension: e.gallery!, options: { isMachineScoped } }))) + galleryExtensions.length ? this.extensionManagementService.installGalleryExtensions(galleryExtensions.map(e => ({ extension: e, options: { isMachineScoped } }))) : Promise.resolve(), + resourceExtensions.length ? Promise.allSettled(resourceExtensions.map(r => this.extensionsWorkbenchService.install(r))) : Promise.resolve() ]); }; choices.push({ diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 0ff4dca2..60aa8d8a 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -13,8 +13,8 @@ import { EnablementState, IExtensionManagementServerService, IWorkbenchExtension import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, IExtension, extensionsSearchActionsMenu, UPDATE_ACTIONS_GROUP, IExtensionArg } from 'vs/workbench/contrib/extensions/common/extensions'; -import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, IExtension, extensionsSearchActionsMenu, UPDATE_ACTIONS_GROUP, IExtensionArg, ExtensionRuntimeActionType } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction, InstallAnotherVersionAction, InstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; @@ -49,7 +49,6 @@ import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/brow import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { ExtensionRecommendationNotificationService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService'; -import { IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ResourceContextKey, WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; @@ -61,11 +60,9 @@ import { ExtensionEnablementWorkspaceTrustTransitionParticipant } from 'vs/workb import { clearSearchResultsIcon, configureRecommendedIcon, extensionsViewIcon, filterIcon, installWorkspaceRecommendedIcon, refreshIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; import { Disposable, DisposableStore, IDisposable, isDisposable } from 'vs/base/common/lifecycle'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; -import { Promises } from 'vs/base/common/async'; import { EditorExtensions } from 'vs/workbench/common/editor'; import { WORKSPACE_TRUST_EXTENSION_SUPPORT } from 'vs/workbench/services/workspaces/common/workspaceTrust'; import { ExtensionsCompletionItemsProvider } from 'vs/workbench/contrib/extensions/browser/extensionsCompletionItemsProvider'; @@ -81,6 +78,7 @@ import { CONTEXT_KEYBINDINGS_EDITOR } from 'vs/workbench/contrib/preferences/com import { DeprecatedExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker'; import { ProgressLocation } from 'vs/platform/progress/common/progress'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IConfigurationMigrationRegistry, Extensions as ConfigurationMigrationExtensions } from 'vs/workbench/common/configuration'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); @@ -128,17 +126,15 @@ Registry.as(ConfigurationExtensions.Configuration) ...extensionsConfigurationNodeBase, properties: { 'extensions.autoUpdate': { - enum: [true, 'onlyEnabledExtensions', 'onlySelectedExtensions', false,], + enum: [true, 'onlyEnabledExtensions', false,], enumItemLabels: [ localize('all', "All Extensions"), localize('enabled', "Only Enabled Extensions"), - localize('selected', "Only Selected Extensions"), localize('none', "None"), ], enumDescriptions: [ - localize('extensions.autoUpdate.true', 'Download and install updates automatically for all extensions except for those updates are ignored.'), - localize('extensions.autoUpdate.enabled', 'Download and install updates automatically only for enabled extensions except for those updates are ignored. Disabled extensions are not updated automatically.'), - localize('extensions.autoUpdate.selected', 'Download and install updates automatically only for selected extensions.'), + localize('extensions.autoUpdate.true', 'Download and install updates automatically for all extensions.'), + localize('extensions.autoUpdate.enabled', 'Download and install updates automatically only for enabled extensions.'), localize('extensions.autoUpdate.false', 'Extensions are not automatically updated.'), ], description: localize('extensions.autoUpdate', "Controls the automatic update behavior of extensions. The updates are fetched from a Microsoft online service."), @@ -380,7 +376,7 @@ CommandsRegistry.registerCommand({ } } else { const vsix = URI.revive(arg); - await extensionsWorkbenchService.install(vsix, { installOnlyNewlyAddedFromExtensionPack: options?.installOnlyNewlyAddedFromExtensionPackVSIX }); + await extensionsWorkbenchService.install(vsix, { installOnlyNewlyAddedFromExtensionPack: options?.installOnlyNewlyAddedFromExtensionPackVSIX, installGivenVersion: true }); } } catch (e) { onUnexpectedError(e); @@ -635,57 +631,38 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } }); - const autoUpdateExtensionsSubMenu = new MenuId('autoUpdateExtensionsSubMenu'); - MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { - submenu: autoUpdateExtensionsSubMenu, - title: localize('configure auto updating extensions', "Auto Update Extensions"), - when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), CONTEXT_HAS_GALLERY), - group: '1_updates', - order: 5, - }); - - this.registerExtensionAction({ - id: 'configureExtensionsAutoUpdate.all', - title: localize('configureExtensionsAutoUpdate.all', "All Extensions"), - toggled: ContextKeyExpr.and(ContextKeyExpr.has(`config.${AutoUpdateConfigurationKey}`), ContextKeyExpr.notEquals(`config.${AutoUpdateConfigurationKey}`, 'onlyEnabledExtensions'), ContextKeyExpr.notEquals(`config.${AutoUpdateConfigurationKey}`, 'onlySelectedExtensions')), - menu: [{ - id: autoUpdateExtensionsSubMenu, - order: 1, - }], - run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, true) - }); - - this.registerExtensionAction({ - id: 'configureExtensionsAutoUpdate.enabled', - title: localize('configureExtensionsAutoUpdate.enabled', "Enabled Extensions"), - toggled: ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, 'onlyEnabledExtensions'), - menu: [{ - id: autoUpdateExtensionsSubMenu, - order: 2, - }], - run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, 'onlyEnabledExtensions') - }); - + const enableAutoUpdateWhenCondition = ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, false); this.registerExtensionAction({ - id: 'configureExtensionsAutoUpdate.selected', - title: localize('configureExtensionsAutoUpdate.selected', "Selected Extensions"), - toggled: ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, 'onlySelectedExtensions'), + id: 'workbench.extensions.action.enableAutoUpdate', + title: localize2('enableAutoUpdate', 'Enable Auto Update for All Extensions'), + category: ExtensionsLocalizedLabel, + precondition: enableAutoUpdateWhenCondition, menu: [{ - id: autoUpdateExtensionsSubMenu, - order: 2, + id: MenuId.ViewContainerTitle, + order: 5, + group: '1_updates', + when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), enableAutoUpdateWhenCondition) + }, { + id: MenuId.CommandPalette, }], - run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, 'onlySelectedExtensions') + run: (accessor: ServicesAccessor) => accessor.get(IExtensionsWorkbenchService).updateAutoUpdateValue(true) }); + const disableAutoUpdateWhenCondition = ContextKeyExpr.notEquals(`config.${AutoUpdateConfigurationKey}`, false); this.registerExtensionAction({ - id: 'configureExtensionsAutoUpdate.none', - title: localize('configureExtensionsAutoUpdate.none', "None"), - toggled: ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, false), + id: 'workbench.extensions.action.disableAutoUpdate', + title: localize2('disableAutoUpdate', 'Disable Auto Update for All Extensions'), + precondition: disableAutoUpdateWhenCondition, + category: ExtensionsLocalizedLabel, menu: [{ - id: autoUpdateExtensionsSubMenu, - order: 3, + id: MenuId.ViewContainerTitle, + order: 5, + group: '1_updates', + when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), disableAutoUpdateWhenCondition) + }, { + id: MenuId.CommandPalette, }], - run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, false) + run: (accessor: ServicesAccessor) => accessor.get(IExtensionsWorkbenchService).updateAutoUpdateValue(false) }); this.registerExtensionAction({ @@ -724,24 +701,6 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } }); - this.registerExtensionAction({ - id: 'workbench.extensions.action.disableAutoUpdate', - title: localize2('disableAutoUpdate', 'Disable Auto Update for All Extensions'), - category: ExtensionsLocalizedLabel, - f1: true, - precondition: CONTEXT_HAS_GALLERY, - run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, false) - }); - - this.registerExtensionAction({ - id: 'workbench.extensions.action.enableAutoUpdate', - title: localize2('enableAutoUpdate', 'Enable Auto Update for All Extensions'), - category: ExtensionsLocalizedLabel, - f1: true, - precondition: CONTEXT_HAS_GALLERY, - run: (accessor: ServicesAccessor) => accessor.get(IConfigurationService).updateValue(AutoUpdateConfigurationKey, true) - }); - this.registerExtensionAction({ id: 'workbench.extensions.action.enableAll', title: localize2('enableAll', 'Enable All Extensions'), @@ -854,29 +813,51 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi when: ContextKeyExpr.and(ResourceContextKey.Extension.isEqualTo('.vsix'), ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER)), }], run: async (accessor: ServicesAccessor, resources: URI[] | URI) => { - const extensionService = accessor.get(IExtensionService); const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); const hostService = accessor.get(IHostService); const notificationService = accessor.get(INotificationService); - const extensions = Array.isArray(resources) ? resources : [resources]; - await Promises.settled(extensions.map(async (vsix) => await extensionsWorkbenchService.install(vsix))) - .then(async (extensions) => { - for (const extension of extensions) { - const requireReload = !(extension.local && extensionService.canAddExtension(toExtensionDescription(extension.local))); - const message = requireReload ? localize('InstallVSIXAction.successReload', "Completed installing {0} extension from VSIX. Please reload Visual Studio Code to enable it.", extension.displayName || extension.name) - : localize('InstallVSIXAction.success', "Completed installing {0} extension from VSIX.", extension.displayName || extension.name); - const actions = requireReload ? [{ - label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => hostService.reload() - }] : []; - notificationService.prompt( - Severity.Info, - message, - actions - ); - } - }); + const vsixs = Array.isArray(resources) ? resources : [resources]; + const result = await Promise.allSettled(vsixs.map(async (vsix) => await extensionsWorkbenchService.install(vsix, { installGivenVersion: true }))); + let error: Error | undefined, requireReload = false, requireRestart = false; + for (const r of result) { + if (r.status === 'rejected') { + error = new Error(r.reason); + break; + } + requireReload = requireReload || r.value.runtimeState?.action === ExtensionRuntimeActionType.ReloadWindow; + requireRestart = requireRestart || r.value.runtimeState?.action === ExtensionRuntimeActionType.RestartExtensions; + } + if (error) { + throw error; + } + if (requireReload) { + notificationService.prompt( + Severity.Info, + localize('InstallVSIXAction.successReload', "Completed installing extension from VSIX. Please reload Visual Studio Code to enable it."), + [{ + label: localize('InstallVSIXAction.reloadNow', "Reload Now"), + run: () => hostService.reload() + }] + ); + } + else if (requireRestart) { + notificationService.prompt( + Severity.Info, + localize('InstallVSIXAction.successRestart', "Completed installing extension from VSIX. Please restart extensions to enable it."), + [{ + label: localize('InstallVSIXAction.restartExtensions', "Restart Extensions"), + run: () => extensionsWorkbenchService.updateRunningExtensions() + }] + ); + } + else { + notificationService.prompt( + Severity.Info, + localize('InstallVSIXAction.successNoReload', "Completed installing extension."), + [] + ); + } } }); @@ -1364,18 +1345,23 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: ToggleAutoUpdateForExtensionAction.ID, title: ToggleAutoUpdateForExtensionAction.LABEL, category: ExtensionsLocalizedLabel, + precondition: ContextKeyExpr.and(ContextKeyExpr.or(ContextKeyExpr.notEquals(`config.${AutoUpdateConfigurationKey}`, 'onlyEnabledExtensions'), ContextKeyExpr.equals('isExtensionEnabled', true)), ContextKeyExpr.not('extensionDisallowInstall')), menu: { id: MenuId.ExtensionContext, group: UPDATE_ACTIONS_GROUP, order: 1, - when: ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension'), ContextKeyExpr.or(ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, 'onlySelectedExtensions'), ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, false)),) + when: ContextKeyExpr.and( + ContextKeyExpr.not('inExtensionEditor'), + ContextKeyExpr.equals('extensionStatus', 'installed'), + ContextKeyExpr.not('isBuiltinExtension'), + ) }, run: async (accessor: ServicesAccessor, id: string) => { const instantiationService = accessor.get(IInstantiationService); const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id })); if (extension) { - const action = instantiationService.createInstance(ToggleAutoUpdateForExtensionAction, false, []); + const action = instantiationService.createInstance(ToggleAutoUpdateForExtensionAction); action.extension = extension; return action.run(); } @@ -1386,11 +1372,12 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: ToggleAutoUpdatesForPublisherAction.ID, title: { value: ToggleAutoUpdatesForPublisherAction.LABEL, original: 'Auto Update (Publisher)' }, category: ExtensionsLocalizedLabel, + precondition: ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, false), menu: { id: MenuId.ExtensionContext, group: UPDATE_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension'), ContextKeyExpr.or(ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, 'onlySelectedExtensions'), ContextKeyExpr.equals(`config.${AutoUpdateConfigurationKey}`, false)),) + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { const instantiationService = accessor.get(IInstantiationService); @@ -1467,6 +1454,72 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } }); + this.registerExtensionAction({ + id: 'workbench.extensions.action.installAndDonotSync', + title: localize('install installAndDonotSync', "Install (Do not Sync)"), + menu: { + id: MenuId.ExtensionContext, + group: '0_install', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.not('extensionDisallowInstall'), CONTEXT_SYNC_ENABLEMENT), + order: 1 + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + const instantiationService = accessor.get(IInstantiationService); + const extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; + if (extension) { + const action = instantiationService.createInstance(InstallAction, { + isMachineScoped: true, + }); + action.extension = extension; + return action.run(); + } + } + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.installPrereleaseAndDonotSync', + title: localize('installPrereleaseAndDonotSync', "Install Pre-Release (Do not Sync)"), + menu: { + id: MenuId.ExtensionContext, + group: '0_install', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('extensionDisallowInstall'), CONTEXT_SYNC_ENABLEMENT), + order: 2 + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + const instantiationService = accessor.get(IInstantiationService); + const extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; + if (extension) { + const action = instantiationService.createInstance(InstallAction, { + isMachineScoped: true, + preRelease: true + }); + action.extension = extension; + return action.run(); + } + } + }); + + this.registerExtensionAction({ + id: InstallAnotherVersionAction.ID, + title: InstallAnotherVersionAction.LABEL, + menu: { + id: MenuId.ExtensionContext, + group: '0_install', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.not('extensionDisallowInstall')), + order: 3 + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + const instantiationService = accessor.get(IInstantiationService); + const extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await this.extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; + if (extension) { + return instantiationService.createInstance(InstallAnotherVersionAction, extension, false).run(); + } + } + }); + this.registerExtensionAction({ id: 'workbench.extensions.action.copyExtension', title: localize2('workbench.extensions.action.copyExtension', 'Copy'), @@ -1550,7 +1603,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi menu: { id: MenuId.ExtensionContext, group: '2_configure', - when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, ContextKeyExpr.equals('isWorkspaceScopedExtension', false)), + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), CONTEXT_SYNC_ENABLEMENT, ContextKeyExpr.equals('isWorkspaceScopedExtension', false)), order: 4 }, run: async (accessor: ServicesAccessor, id: string) => { @@ -1756,3 +1809,14 @@ if (isWeb) { // Running Extensions registerAction2(ShowRuntimeExtensionsAction); + +Registry.as(ConfigurationMigrationExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: AutoUpdateConfigurationKey, + migrateFn: (value, accessor) => { + if (value === 'onlySelectedExtensions') { + return { value: false }; + } + return []; + } + }]); diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index fc174208..c5d00d34 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -5,17 +5,17 @@ import 'vs/css!./media/extensionActions'; import { localize, localize2 } from 'vs/nls'; -import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions'; +import { IAction, Action, Separator, SubmenuAction, IActionChangeEvent } from 'vs/base/common/actions'; import { Delayer, Promises, Throttler } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { disposeIfDisposable } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue, ExtensionEditorTab, ExtensionRuntimeActionType, IExtensionArg } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, ExtensionEditorTab, ExtensionRuntimeActionType, IExtensionArg, AutoUpdateConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, getWorkspaceSupportTypeMessage, TargetPlatform, isApplicationScopedExtension } from 'vs/platform/extensions/common/extensions'; @@ -52,7 +52,6 @@ import { IActionViewItemOptions, ActionViewItem } from 'vs/base/browser/ui/actio import { EXTENSIONS_CONFIG, IExtensionsConfigContent } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; import { getErrorMessage, isCancellationError } from 'vs/base/common/errors'; import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; -import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { ILogService } from 'vs/platform/log/common/log'; import { errorIcon, infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; @@ -73,6 +72,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { Registry } from 'vs/platform/registry/common/platform'; import { IUpdateService } from 'vs/platform/update/common/update'; +import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; export class PromptExtensionInstallFailureAction extends Action { @@ -134,7 +134,7 @@ export class PromptExtensionInstallFailureAction extends Action { return; } - if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { + if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleApi, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { await this.dialogService.info(getErrorMessage(this.error)); return; } @@ -216,21 +216,52 @@ export class PromptExtensionInstallFailureAction extends Action { } +export interface IExtensionActionChangeEvent extends IActionChangeEvent { + readonly hidden?: boolean; + readonly menuActions?: IAction[]; +} + export abstract class ExtensionAction extends Action implements IExtensionContainer { + + protected override _onDidChange = this._register(new Emitter()); + override readonly onDidChange = this._onDidChange.event; + static readonly EXTENSION_ACTION_CLASS = 'extension-action'; static readonly TEXT_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} text`; static readonly LABEL_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} label`; + static readonly PROMINENT_LABEL_ACTION_CLASS = `${ExtensionAction.LABEL_ACTION_CLASS} prominent`; static readonly ICON_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} icon`; + private _extension: IExtension | null = null; get extension(): IExtension | null { return this._extension; } set extension(extension: IExtension | null) { this._extension = extension; this.update(); } + + private _hidden: boolean = false; + get hidden(): boolean { return this._hidden; } + set hidden(hidden: boolean) { + if (this._hidden !== hidden) { + this._hidden = hidden; + this._onDidChange.fire({ hidden }); + } + } + + protected override _setEnabled(value: boolean): void { + super._setEnabled(value); + if (this.hideOnDisabled) { + this.hidden = !value; + } + } + + protected hideOnDisabled: boolean = true; + abstract update(): void; } -export class ActionWithDropDownAction extends ExtensionAction { +export class ButtonWithDropDownExtensionAction extends ExtensionAction { - private action: IAction | undefined; + private primaryAction: IAction | undefined; + readonly menuActionClassNames: string[] = []; private _menuActions: IAction[] = []; get menuActions(): IAction[] { return [...this._menuActions]; } @@ -246,10 +277,14 @@ export class ActionWithDropDownAction extends ExtensionAction { protected readonly extensionActions: ExtensionAction[]; constructor( - id: string, label: string, + id: string, + clazz: string, private readonly actionsGroups: ExtensionAction[][], ) { - super(id, label); + clazz = `${clazz} action-dropdown`; + super(id, undefined, clazz); + this.menuActionClassNames = clazz.split(' '); + this.hideOnDisabled = false; this.extensionActions = actionsGroups.flat(); this.update(); this._register(Event.any(...this.extensionActions.map(a => a.onDidChange))(() => this.update(true))); @@ -261,36 +296,35 @@ export class ActionWithDropDownAction extends ExtensionAction { this.extensionActions.forEach(a => a.update()); } - const enabledActionsGroups = this.actionsGroups.map(actionsGroup => actionsGroup.filter(a => a.enabled)); + const actionsGroups = this.actionsGroups.map(actionsGroup => actionsGroup.filter(a => !a.hidden)); let actions: IAction[] = []; - for (const enabledActions of enabledActionsGroups) { - if (enabledActions.length) { - actions = [...actions, ...enabledActions, new Separator()]; + for (const visibleActions of actionsGroups) { + if (visibleActions.length) { + actions = [...actions, ...visibleActions, new Separator()]; } } actions = actions.length ? actions.slice(0, actions.length - 1) : actions; - this.action = actions[0]; + this.primaryAction = actions[0]; this._menuActions = actions.length > 1 ? actions : []; + this._onDidChange.fire({ menuActions: this._menuActions }); - this.enabled = !!this.action; - if (this.action) { - this.label = this.getLabel(this.action as ExtensionAction); - this.tooltip = this.action.tooltip; - } - - let clazz = (this.action || this.extensionActions[0])?.class || ''; - clazz = clazz ? `${clazz} action-dropdown` : 'action-dropdown'; - if (this._menuActions.length === 0) { - clazz += ' action-dropdown'; + if (this.primaryAction) { + this.hidden = false; + this.enabled = this.primaryAction.enabled; + this.label = this.getLabel(this.primaryAction as ExtensionAction); + this.tooltip = this.primaryAction.tooltip; + } else { + this.hidden = true; + this.enabled = false; } - this.class = clazz; } - override run(): Promise { - const enabledActions = this.extensionActions.filter(a => a.enabled); - return enabledActions[0].run(); + override async run(): Promise { + if (this.enabled) { + await this.primaryAction?.run(); + } } protected getLabel(action: ExtensionAction): string { @@ -298,9 +332,42 @@ export class ActionWithDropDownAction extends ExtensionAction { } } +export class ButtonWithDropdownExtensionActionViewItem extends ActionWithDropdownActionViewItem { + + constructor( + action: ButtonWithDropDownExtensionAction, + options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions, + contextMenuProvider: IContextMenuProvider + ) { + super(null, action, options, contextMenuProvider); + this._register(action.onDidChange(e => { + if (e.hidden !== undefined || e.menuActions !== undefined) { + this.updateClass(); + } + })); + } + + override render(container: HTMLElement): void { + super.render(container); + this.updateClass(); + } + + protected override updateClass(): void { + super.updateClass(); + if (this.element && this.dropdownMenuActionViewItem?.element) { + this.element.classList.toggle('hide', (this._action).hidden); + const isMenuEmpty = (this._action).menuActions.length === 0; + this.element.classList.toggle('empty', isMenuEmpty); + this.dropdownMenuActionViewItem.element.classList.toggle('hide', isMenuEmpty); + } + } + +} + export class InstallAction extends ExtensionAction { - static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install`; + static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent install`; + private static readonly HIDE = `${this.CLASS} hide`; protected _manifest: IExtensionManifest | null = null; set manifest(manifest: IExtensionManifest | null) { @@ -323,8 +390,9 @@ export class InstallAction extends ExtensionAction { @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, ) { - super('extensions.install', localize('install', "Install"), InstallAction.Class, false); - this.options = { ...options, isMachineScoped: false }; + super('extensions.install', localize('install', "Install"), InstallAction.CLASS, false); + this.hideOnDisabled = false; + this.options = { isMachineScoped: false, ...options }; this.update(); this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); } @@ -335,6 +403,8 @@ export class InstallAction extends ExtensionAction { protected async computeAndUpdateEnablement(): Promise { this.enabled = false; + this.class = InstallAction.HIDE; + this.hidden = true; if (!this.extension) { return; } @@ -344,8 +414,19 @@ export class InstallAction extends ExtensionAction { if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) { return; } - if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) { - this.enabled = this.options.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion; + if (this.extension.state !== ExtensionState.Uninstalled) { + return; + } + if (this.options.installPreReleaseVersion && !this.extension.hasPreReleaseVersion) { + return; + } + if (!this.options.installPreReleaseVersion && !this.extension.hasReleaseVersion) { + return; + } + this.hidden = false; + this.class = InstallAction.CLASS; + if (await this.extensionsWorkbenchService.canInstall(this.extension)) { + this.enabled = true; this.updateLabel(); } } @@ -518,7 +599,7 @@ export class InstallAction extends ExtensionAction { } -export class InstallDropdownAction extends ActionWithDropDownAction { +export class InstallDropdownAction extends ButtonWithDropDownExtensionAction { set manifest(manifest: IExtensionManifest | null) { this.extensionActions.forEach(a => (a).manifest = manifest); @@ -529,7 +610,7 @@ export class InstallDropdownAction extends ActionWithDropDownAction { @IInstantiationService instantiationService: IInstantiationService, @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, ) { - super(`extensions.installActions`, '', [ + super(`extensions.installActions`, InstallAction.CLASS, [ [ instantiationService.createInstance(InstallAction, { installPreReleaseVersion: extensionsWorkbenchService.preferPreReleases }), instantiationService.createInstance(InstallAction, { installPreReleaseVersion: !extensionsWorkbenchService.preferPreReleases }), @@ -562,8 +643,8 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { protected static readonly INSTALL_LABEL = localize('install', "Install"); protected static readonly INSTALLING_LABEL = localize('installing', "Installing"); - private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install`; - private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install installing`; + private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install-other-server`; + private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install-other-server installing`; updateWhenCounterExtensionChanges: boolean = true; @@ -721,7 +802,7 @@ export class UninstallAction extends ExtensionAction { static readonly UninstallLabel = localize('uninstallAction', "Uninstall"); private static readonly UninstallingLabel = localize('Uninstalling', "Uninstalling"); - private static readonly UninstallClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall`; + static readonly UninstallClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall`; private static readonly UnInstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall uninstalling`; constructor( @@ -774,28 +855,36 @@ export class UninstallAction extends ExtensionAction { await this.extensionsWorkbenchService.uninstall(this.extension); alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension.displayName)); } catch (error) { - this.dialogService.error(getErrorMessage(error)); + if (!isCancellationError(error)) { + this.dialogService.error(getErrorMessage(error)); + } } } } -abstract class AbstractUpdateAction extends ExtensionAction { +export class UpdateAction extends ExtensionAction { - private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} prominent update`; - private static readonly DisabledClass = `${AbstractUpdateAction.EnabledClass} disabled`; + private static readonly EnabledClass = `${this.LABEL_ACTION_CLASS} prominent update`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; private readonly updateThrottler = new Throttler(); constructor( - id: string, label: string | undefined, - protected readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + private readonly verbose: boolean, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IDialogService private readonly dialogService: IDialogService, + @IOpenerService private readonly openerService: IOpenerService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(id, label, AbstractUpdateAction.DisabledClass, false); + super(`extensions.update`, localize('update', "Update"), UpdateAction.DisabledClass, false); this.update(); } update(): void { this.updateThrottler.queue(() => this.computeAndUpdateEnablement()); + if (this.extension) { + this.label = this.verbose ? localize('update to', "Update to v{0}", this.extension.latestVersion) : localize('update', "Update"); + } } private async computeAndUpdateEnablement(): Promise { @@ -814,31 +903,45 @@ abstract class AbstractUpdateAction extends ExtensionAction { const isInstalled = this.extension.state === ExtensionState.Installed; this.enabled = canInstall && isInstalled && this.extension.outdated; - this.class = this.enabled ? AbstractUpdateAction.EnabledClass : AbstractUpdateAction.DisabledClass; - } -} - -export class UpdateAction extends AbstractUpdateAction { - - constructor( - private readonly verbose: boolean, - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - ) { - super(`extensions.update`, localize('update', "Update"), extensionsWorkbenchService); - } - - override update(): void { - super.update(); - if (this.extension) { - this.label = this.verbose ? localize('update to', "Update to v{0}", this.extension.latestVersion) : localize('update', "Update"); - } + this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass; } override async run(): Promise { if (!this.extension) { return; } + + const consent = await this.extensionsWorkbenchService.shouldRequireConsentToUpdate(this.extension); + if (consent) { + const { result } = await this.dialogService.prompt<'update' | 'review' | 'cancel'>({ + type: 'warning', + title: localize('updateExtensionConsentTitle', "Update {0} Extension", this.extension.displayName), + message: localize('updateExtensionConsent', "{0}\n\nWould you like to update the extension?", consent), + buttons: [{ + label: localize('update', "Update"), + run: () => 'update' + }, { + label: localize('review', "Review"), + run: () => 'review' + }, { + label: localize('cancel', "Cancel"), + run: () => 'cancel' + }] + }); + if (result === 'cancel') { + return; + } + if (result === 'review') { + if (this.extension.hasChangelog()) { + return this.extensionsWorkbenchService.open(this.extension, { tab: ExtensionEditorTab.Changelog }); + } + if (this.extension.repository) { + return this.openerService.open(this.extension.repository); + } + return this.extensionsWorkbenchService.open(this.extension); + } + } + alert(localize('updateExtensionStart', "Updating extension {0} to version {1} started.", this.extension.displayName, this.extension.latestVersion)); return this.install(this.extension); } @@ -859,14 +962,12 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { static readonly LABEL = localize2('enableAutoUpdateLabel', "Auto Update"); private static readonly EnabledClass = `${ExtensionAction.EXTENSION_ACTION_CLASS} auto-update`; - private static readonly DisabledClass = `${ToggleAutoUpdateForExtensionAction.EnabledClass} hide`; + private static readonly DisabledClass = `${this.EnabledClass} hide`; constructor( - private readonly enableWhenOutdated: boolean, - private readonly enableWhenAutoUpdateValue: AutoUpdateConfigurationValue[], @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IConfigurationService configurationService: IConfigurationService, - ) { super(ToggleAutoUpdateForExtensionAction.ID, ToggleAutoUpdateForExtensionAction.LABEL.value, ToggleAutoUpdateForExtensionAction.DisabledClass); this._register(configurationService.onDidChangeConfiguration(e => { @@ -886,10 +987,10 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { if (this.extension.isBuiltin) { return; } - if (this.enableWhenOutdated && (this.extension.state !== ExtensionState.Installed || !this.extension.outdated)) { + if (this.extension.deprecationInfo?.disallowInstall) { return; } - if (!this.enableWhenAutoUpdateValue.includes(this.extensionsWorkbenchService.getAutoUpdateValue())) { + if (this.extensionsWorkbenchService.getAutoUpdateValue() === 'onlyEnabledExtensions' && !this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState)) { return; } this.enabled = true; @@ -944,7 +1045,7 @@ export class ToggleAutoUpdatesForPublisherAction extends ExtensionAction { export class MigrateDeprecatedExtensionAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} migrate`; - private static readonly DisabledClass = `${MigrateDeprecatedExtensionAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( private readonly small: boolean, @@ -987,32 +1088,7 @@ export class MigrateDeprecatedExtensionAction extends ExtensionAction { } } -export class ExtensionActionWithDropdownActionViewItem extends ActionWithDropdownActionViewItem { - - constructor( - action: ActionWithDropDownAction, - options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions, - contextMenuProvider: IContextMenuProvider - ) { - super(null, action, options, contextMenuProvider); - } - - override render(container: HTMLElement): void { - super.render(container); - this.updateClass(); - } - - protected override updateClass(): void { - super.updateClass(); - if (this.element && this.dropdownMenuActionViewItem && this.dropdownMenuActionViewItem.element) { - this.element.classList.toggle('empty', (this._action).menuActions.length === 0); - this.dropdownMenuActionViewItem.element.classList.toggle('hide', (this._action).menuActions.length === 0); - } - } - -} - -export abstract class ExtensionDropDownAction extends ExtensionAction { +export abstract class DropDownExtensionAction extends ExtensionAction { constructor( id: string, @@ -1024,9 +1100,9 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { super(id, label, cssClass, enabled); } - private _actionViewItem: DropDownMenuActionViewItem | null = null; - createActionViewItem(options: IActionViewItemOptions): DropDownMenuActionViewItem { - this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, options); + private _actionViewItem: DropDownExtensionActionViewItem | null = null; + createActionViewItem(options: IActionViewItemOptions): DropDownExtensionActionViewItem { + this._actionViewItem = this.instantiationService.createInstance(DropDownExtensionActionViewItem, this, options); return this._actionViewItem; } @@ -1036,10 +1112,10 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { } } -export class DropDownMenuActionViewItem extends ActionViewItem { +export class DropDownExtensionActionViewItem extends ActionViewItem { constructor( - action: ExtensionDropDownAction, + action: DropDownExtensionAction, options: IActionViewItemOptions, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { @@ -1072,6 +1148,7 @@ export class DropDownMenuActionViewItem extends ActionViewItem { async function getContextMenuActionsGroups(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): Promise<[string, Array][]> { return instantiationService.invokeFunction(async accessor => { const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService); const menuService = accessor.get(IMenuService); const extensionRecommendationsService = accessor.get(IExtensionRecommendationsService); const extensionIgnoredRecommendationsService = accessor.get(IExtensionIgnoredRecommendationsService); @@ -1084,6 +1161,7 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['isDefaultApplicationScopedExtension', extension.local && isApplicationScopedExtension(extension.local.manifest)]); cksOverlay.push(['isApplicationScopedExtension', extension.local && extension.local.isApplicationScoped]); cksOverlay.push(['isWorkspaceScopedExtension', extension.isWorkspaceScoped]); + cksOverlay.push(['isGalleryExtension', !!extension.identifier.uuid]); if (extension.local) { cksOverlay.push(['extensionSource', extension.local.source]); } @@ -1093,14 +1171,29 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['isExtensionRecommended', !!extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]]); cksOverlay.push(['isExtensionWorkspaceRecommended', extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]?.reasonId === ExtensionRecommendationReason.Workspace]); cksOverlay.push(['isUserIgnoredRecommendation', extensionIgnoredRecommendationsService.globalIgnoredRecommendations.some(e => e === extension.identifier.id.toLowerCase())]); - if (extension.state === ExtensionState.Installed) { - cksOverlay.push(['extensionStatus', 'installed']); + cksOverlay.push(['isExtensionPinned', extension.pinned]); + cksOverlay.push(['isExtensionEnabled', extensionEnablementService.isEnabledEnablementState(extension.enablementState)]); + switch (extension.state) { + case ExtensionState.Installing: + cksOverlay.push(['extensionStatus', 'installing']); + break; + case ExtensionState.Installed: + cksOverlay.push(['extensionStatus', 'installed']); + break; + case ExtensionState.Uninstalling: + cksOverlay.push(['extensionStatus', 'uninstalling']); + break; + case ExtensionState.Uninstalled: + cksOverlay.push(['extensionStatus', 'uninstalled']); + break; } cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]); cksOverlay.push(['installedExtensionIsOptedToPreRelease', !!extension.local?.preRelease]); cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]); cksOverlay.push(['galleryExtensionHasPreReleaseVersion', extension.gallery?.hasPreReleaseVersion]); + cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]); cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]); + cksOverlay.push(['extensionDisallowInstall', !!extension.deprecationInfo?.disallowInstall]); const [colorThemes, fileIconThemes, productIconThemes] = await Promise.all([workbenchThemeService.getColorThemes(), workbenchThemeService.getFileIconThemes(), workbenchThemeService.getProductIconThemes()]); cksOverlay.push(['extensionHasColorThemes', colorThemes.some(theme => isThemeFromExtension(theme, extension))]); @@ -1111,9 +1204,7 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['isActiveLanguagePackExtension', extension.gallery && language === getLocale(extension.gallery)]); } - const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay)); - const actionsGroups = menu.getActions({ shouldForwardArgs: true }); - menu.dispose(); + const actionsGroups = menuService.getMenuActions(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay), { shouldForwardArgs: true }); return actionsGroups; }); } @@ -1137,12 +1228,12 @@ export async function getContextMenuActions(extension: IExtension | undefined | return toActions(actionsGroups, instantiationService); } -export class ManageExtensionAction extends ExtensionDropDownAction { +export class ManageExtensionAction extends DropDownExtensionAction { static readonly ID = 'extensions.manage'; private static readonly Class = `${ExtensionAction.ICON_ACTION_CLASS} manage ` + ThemeIcon.asClassName(manageExtensionIcon); - private static readonly HideManageExtensionClass = `${ManageExtensionAction.Class} hide`; + private static readonly HideManageExtensionClass = `${this.Class} hide`; constructor( @IInstantiationService instantiationService: IInstantiationService, @@ -1190,7 +1281,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { } groups.push([ ...(installActions.length ? installActions : []), - this.instantiationService.createInstance(InstallAnotherVersionAction), + this.instantiationService.createInstance(InstallAnotherVersionAction, this.extension, false), this.instantiationService.createInstance(UninstallAction), ]); @@ -1221,7 +1312,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { } } -export class ExtensionEditorManageExtensionAction extends ExtensionDropDownAction { +export class ExtensionEditorManageExtensionAction extends DropDownExtensionAction { constructor( private readonly contextKeyService: IContextKeyService, @@ -1255,6 +1346,14 @@ export class MenuItemExtensionAction extends ExtensionAction { super(action.id, action.label); } + override get enabled(): boolean { + return this.action.enabled; + } + + override set enabled(value: boolean) { + this.action.enabled = value; + } + update() { if (!this.extension) { return; @@ -1291,7 +1390,7 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { static readonly LABEL = localize('togglePreRleaseLabel', "Pre-Release"); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} pre-release`; - private static readonly DisabledClass = `${TogglePreReleaseExtensionAction.EnabledClass} hide`; + private static readonly DisabledClass = `${this.EnabledClass} hide`; constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -1348,29 +1447,39 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { export class InstallAnotherVersionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.install.anotherVersion'; - static readonly LABEL = localize('install another version', "Install Another Version..."); + static readonly LABEL = localize('install another version', "Install Specific Version..."); constructor( + extension: IExtension | null, + private readonly whenInstalled: boolean, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, ) { super(InstallAnotherVersionAction.ID, InstallAnotherVersionAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this.extension = extension; this.update(); } update(): void { - this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery && !!this.extension.local && !!this.extension.server && this.extension.state === ExtensionState.Installed && !this.extension.deprecationInfo; + this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.identifier.uuid && !this.extension.deprecationInfo; + if (this.enabled && this.whenInstalled) { + this.enabled = !!this.extension?.local && !!this.extension.server && this.extension.state === ExtensionState.Installed; + } } override async run(): Promise { if (!this.enabled) { return; } - const targetPlatform = await this.extension!.server!.extensionManagementService.getTargetPlatform(); - const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension!.gallery!, this.extension!.local!.preRelease, targetPlatform); + if (!this.extension) { + return; + } + const targetPlatform = this.extension.server ? await this.extension.server.extensionManagementService.getTargetPlatform() : await this.extensionManagementService.getTargetPlatform(); + const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension.identifier, this.extension.local?.preRelease ?? this.extension.gallery?.properties.isPreReleaseVersion ?? false, targetPlatform); if (!allVersions.length) { await this.dialogService.info(localize('no versions', "This extension has no other versions.")); return; @@ -1380,8 +1489,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { return { id: v.version, label: v.version, - description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension!.version ? ` (${localize('current', "current")})` : ''}`, - latest: i === 0, + description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension?.local?.manifest.version ? ` (${localize('current', "current")})` : ''}`, ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`, isPreReleaseVersion: v.isPreReleaseVersion }; @@ -1392,18 +1500,13 @@ export class InstallAnotherVersionAction extends ExtensionAction { matchOnDetail: true }); if (pick) { - if (this.extension!.version === pick.id) { + if (this.extension.local?.manifest.version === pick.id) { return; } try { - if (pick.latest) { - const [extension] = pick.id !== this.extension?.version ? await this.extensionsWorkbenchService.getExtensions([{ id: this.extension!.identifier.id, preRelease: pick.isPreReleaseVersion }], CancellationToken.None) : [this.extension]; - await this.extensionsWorkbenchService.install(extension ?? this.extension!, { installPreReleaseVersion: pick.isPreReleaseVersion }); - } else { - await this.extensionsWorkbenchService.install(this.extension!, { installPreReleaseVersion: pick.isPreReleaseVersion, version: pick.id }); - } + await this.extensionsWorkbenchService.install(this.extension, { installPreReleaseVersion: pick.isPreReleaseVersion, version: pick.id }); } catch (error) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, pick.latest ? this.extension!.latestVersion : pick.id, InstallOperation.Install, error).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension, pick.id, InstallOperation.Install, error).run(); } } return null; @@ -1540,12 +1643,12 @@ export class DisableGloballyAction extends ExtensionAction { } } -export class EnableDropDownAction extends ActionWithDropDownAction { +export class EnableDropDownAction extends ButtonWithDropDownExtensionAction { constructor( @IInstantiationService instantiationService: IInstantiationService ) { - super('extensions.enable', localize('enableAction', "Enable"), [ + super('extensions.enable', ExtensionAction.LABEL_ACTION_CLASS, [ [ instantiationService.createInstance(EnableGloballyAction), instantiationService.createInstance(EnableForWorkspaceAction) @@ -1554,12 +1657,12 @@ export class EnableDropDownAction extends ActionWithDropDownAction { } } -export class DisableDropDownAction extends ActionWithDropDownAction { +export class DisableDropDownAction extends ButtonWithDropDownExtensionAction { constructor( @IInstantiationService instantiationService: IInstantiationService ) { - super('extensions.disable', localize('disableAction', "Disable"), [[ + super('extensions.disable', ExtensionAction.LABEL_ACTION_CLASS, [[ instantiationService.createInstance(DisableGloballyAction), instantiationService.createInstance(DisableForWorkspaceAction) ]]); @@ -1570,7 +1673,7 @@ export class DisableDropDownAction extends ActionWithDropDownAction { export class ExtensionRuntimeStateAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} reload`; - private static readonly DisabledClass = `${ExtensionRuntimeStateAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; updateWhenCounterExtensionChanges: boolean = true; @@ -1684,7 +1787,7 @@ export class SetColorThemeAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.setColorTheme', 'Set Color Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; - private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, @@ -1735,7 +1838,7 @@ export class SetFileIconThemeAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.setFileIconTheme', 'Set File Icon Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; - private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, @@ -1785,7 +1888,7 @@ export class SetProductIconThemeAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.setProductIconTheme', 'Set Product Icon Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; - private static readonly DisabledClass = `${SetProductIconThemeAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, @@ -1836,7 +1939,7 @@ export class SetLanguageAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.setDisplayLanguage', 'Set Display Language'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; - private static readonly DisabledClass = `${SetLanguageAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -1872,7 +1975,7 @@ export class ClearLanguageAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.clearLanguage', 'Clear Display Language'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; - private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -2184,7 +2287,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac export class ExtensionStatusLabelAction extends Action implements IExtensionContainer { private static readonly ENABLED_CLASS = `${ExtensionAction.TEXT_ACTION_CLASS} extension-status-label`; - private static readonly DISABLED_CLASS = `${ExtensionStatusLabelAction.ENABLED_CLASS} hide`; + private static readonly DISABLED_CLASS = `${this.ENABLED_CLASS} hide`; private initialStatus: ExtensionState | null = null; private status: ExtensionState | null = null; @@ -2284,10 +2387,10 @@ export class ExtensionStatusLabelAction extends Action implements IExtensionCont } -export class ToggleSyncExtensionAction extends ExtensionDropDownAction { +export class ToggleSyncExtensionAction extends DropDownExtensionAction { private static readonly IGNORED_SYNC_CLASS = `${ExtensionAction.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncIgnoredIcon)}`; - private static readonly SYNC_CLASS = `${ToggleSyncExtensionAction.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncEnabledIcon)}`; + private static readonly SYNC_CLASS = `${this.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncEnabledIcon)}`; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -2332,8 +2435,8 @@ export class ExtensionStatusAction extends ExtensionAction { updateWhenCounterExtensionChanges: boolean = true; - private _status: ExtensionStatus | undefined; - get status(): ExtensionStatus | undefined { return this._status; } + private _status: ExtensionStatus[] = []; + get status(): ExtensionStatus[] { return this._status; } private readonly _onDidChangeStatus = this._register(new Emitter()); readonly onDidChangeStatus = this._onDidChangeStatus.event; @@ -2399,6 +2502,23 @@ export class ExtensionStatusAction extends ExtensionAction { return; } + if (this.extension.outdated && this.extensionsWorkbenchService.isAutoUpdateEnabledFor(this.extension)) { + const message = await this.extensionsWorkbenchService.shouldRequireConsentToUpdate(this.extension); + if (message) { + const markdown = new MarkdownString(); + markdown.appendMarkdown(`${message} `); + markdown.appendMarkdown( + localize('auto update message', "Please [review the extension]({0}) and update it manually.", + this.extension.hasChangelog() + ? URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Changelog]))}`).toString() + : this.extension.repository + ? this.extension.repository + : URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id]))}`).toString() + )); + this.updateStatus({ icon: warningIcon, message: markdown }, true); + } + } + if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) { if (this.extensionManagementServerService.localExtensionManagementServer || this.extensionManagementServerService.remoteExtensionManagementServer) { const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform()); @@ -2600,24 +2720,43 @@ export class ExtensionStatusAction extends ExtensionAction { } private updateStatus(status: ExtensionStatus | undefined, updateClass: boolean): void { - if (this._status === status) { - return; + if (status) { + if (this._status.some(s => s.message.value === status.message.value && s.icon?.id === status.icon?.id)) { + return; + } + } else { + if (this._status.length === 0) { + return; + } + this._status = []; } - if (this._status && status && this._status.message === status.message && this._status.icon?.id === status.icon?.id) { - return; + + if (status) { + this._status.push(status); + this._status.sort((a, b) => + b.icon === trustIcon ? -1 : + a.icon === trustIcon ? 1 : + b.icon === errorIcon ? -1 : + a.icon === errorIcon ? 1 : + b.icon === warningIcon ? -1 : + a.icon === warningIcon ? 1 : + b.icon === infoIcon ? -1 : + a.icon === infoIcon ? 1 : + 0 + ); } - this._status = status; + if (updateClass) { - if (this._status?.icon === errorIcon) { + if (status?.icon === errorIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-error ${ThemeIcon.asClassName(errorIcon)}`; } - else if (this._status?.icon === warningIcon) { + else if (status?.icon === warningIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-warning ${ThemeIcon.asClassName(warningIcon)}`; } - else if (this._status?.icon === infoIcon) { + else if (status?.icon === infoIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-info ${ThemeIcon.asClassName(infoIcon)}`; } - else if (this._status?.icon === trustIcon) { + else if (status?.icon === trustIcon) { this.class = `${ExtensionStatusAction.CLASS} ${ThemeIcon.asClassName(trustIcon)}`; } else { @@ -2628,7 +2767,7 @@ export class ExtensionStatusAction extends ExtensionAction { } override async run(): Promise { - if (this._status?.icon === trustIcon) { + if (this._status[0]?.icon === trustIcon) { return this.commandService.executeCommand('workbench.trust.manage'); } } @@ -2723,16 +2862,14 @@ export class InstallSpecificVersionOfExtensionAction extends Action { override async run(): Promise { const extensionPick = await this.quickInputService.pick(this.getExtensionEntries(), { placeHolder: localize('selectExtension', "Select Extension"), matchOnDetail: true }); if (extensionPick && extensionPick.extension) { - const action = this.instantiationService.createInstance(InstallAnotherVersionAction); - action.extension = extensionPick.extension; + const action = this.instantiationService.createInstance(InstallAnotherVersionAction, extensionPick.extension, true); await action.run(); await this.instantiationService.createInstance(SearchExtensionsAction, extensionPick.extension.identifier.id).run(); } } private isEnabled(extension: IExtension): boolean { - const action = this.instantiationService.createInstance(InstallAnotherVersionAction); - action.extension = extension; + const action = this.instantiationService.createInstance(InstallAnotherVersionAction, extension, true); return action.enabled && !!extension.local && this.extensionEnablementService.isEnabled(extension.local); } @@ -3015,12 +3152,7 @@ registerColor('extensionButton.hoverBackground', { hcLight: null }, localize('extensionButtonHoverBackground', "Button background hover color for extension actions.")); -registerColor('extensionButton.separator', { - dark: buttonSeparator, - light: buttonSeparator, - hcDark: buttonSeparator, - hcLight: buttonSeparator -}, localize('extensionButtonSeparator', "Button separator color for extension actions")); +registerColor('extensionButton.separator', buttonSeparator, localize('extensionButtonSeparator', "Button separator color for extension actions")); export const extensionButtonProminentBackground = registerColor('extensionButton.prominentBackground', { dark: buttonBackground, diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 683098f0..b9f501d5 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -13,13 +13,12 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { ManageExtensionAction, ExtensionRuntimeStateAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction, UpdateAction, ToggleAutoUpdateForExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { ManageExtensionAction, ExtensionRuntimeStateAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ButtonWithDropDownExtensionAction, InstallDropdownAction, InstallingLabelAction, ButtonWithDropdownExtensionActionViewItem, DropDownExtensionAction, WebInstallAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction, UpdateAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, extensionVerifiedPublisherIconColor, VerifiedPublisherWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; -import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; -import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; @@ -69,8 +68,8 @@ export class Renderer implements IPagedRenderer { @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly extensionService: IExtensionService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { } @@ -100,10 +99,19 @@ export class Renderer implements IPagedRenderer { const publisherDisplayName = append(publisher, $('.publisher-name.ellipsis')); const actionbar = new ActionBar(footer, { actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { - if (action instanceof ActionWithDropDownAction) { - return new ExtensionActionWithDropdownActionViewItem(action, { ...options, icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); + if (action instanceof ButtonWithDropDownExtensionAction) { + return new ButtonWithDropdownExtensionActionViewItem( + action, + { + ...options, + icon: true, + label: true, + menuActionsOrProvider: { getActions: () => action.menuActions }, + menuActionClassNames: action.menuActionClassNames + }, + this.contextMenuService); } - if (action instanceof ExtensionDropDownAction) { + if (action instanceof DropDownExtensionAction) { return action.createActionViewItem(options); } return undefined; @@ -118,8 +126,7 @@ export class Renderer implements IPagedRenderer { this.instantiationService.createInstance(ExtensionStatusLabelAction), this.instantiationService.createInstance(MigrateDeprecatedExtensionAction, true), this.instantiationService.createInstance(ExtensionRuntimeStateAction), - this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.updateActions', '', - [[this.instantiationService.createInstance(UpdateAction, false)], [this.instantiationService.createInstance(ToggleAutoUpdateForExtensionAction, true, [true, 'onlyEnabledExtensions'])]]), + this.instantiationService.createInstance(UpdateAction, false), this.instantiationService.createInstance(InstallDropdownAction), this.instantiationService.createInstance(InstallingLabelAction), this.instantiationService.createInstance(SetLanguageAction), @@ -185,23 +192,8 @@ export class Renderer implements IPagedRenderer { data.extensionDisposables = dispose(data.extensionDisposables); - const computeEnablement = async () => { - if (extension.state === ExtensionState.Uninstalled) { - if (!!extension.deprecationInfo) { - return true; - } - if (this.extensionsWorkbenchService.canSetLanguage(extension)) { - return false; - } - return !(await this.extensionsWorkbenchService.canInstall(extension)); - } else if (extension.local && !isLanguagePackExtension(extension.local.manifest)) { - const runningExtension = this.extensionService.extensions.filter(e => areSameExtensions({ id: e.identifier.value }, extension.identifier))[0]; - return !(runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); - } - return false; - }; - const updateEnablement = async () => { - const disabled = await computeEnablement(); + const updateEnablement = () => { + const disabled = extension.state === ExtensionState.Installed && extension.local && !this.extensionEnablementService.isEnabled(extension.local); const deprecated = !!extension.deprecationInfo; data.element.classList.toggle('deprecated', deprecated); data.root.classList.toggle('disabled', disabled); @@ -263,6 +255,6 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = const verifiedPublisherIconColor = theme.getColor(extensionVerifiedPublisherIconColor); if (verifiedPublisherIconColor) { const disabledVerifiedPublisherIconColor = verifiedPublisherIconColor.transparent(.5).makeOpaque(WORKBENCH_BACKGROUND(theme)); - collector.addRule(`.extensions-list .monaco-list .monaco-list-row.disabled .author .verified-publisher ${ThemeIcon.asCSSSelector(verifiedPublisherThemeIcon)} { color: ${disabledVerifiedPublisherIconColor}; }`); + collector.addRule(`.extensions-list .monaco-list .monaco-list-row.disabled:not(.selected) .author .verified-publisher ${ThemeIcon.asCSSSelector(verifiedPublisherThemeIcon)} { color: ${disabledVerifiedPublisherIconColor}; }`); } }); diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 7ca65d95..9a81262e 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -16,7 +16,7 @@ import { append, $, Dimension, hide, show, DragAndDropObserver, trackFocus } fro import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, extensionsSearchActionsMenu } from '../common/extensions'; +import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, extensionsSearchActionsMenu, AutoRestartConfigurationKey } from '../common/extensions'; import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -846,7 +846,8 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution constructor( @IActivityService private readonly activityService: IActivityService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); this.onServiceChange(); @@ -856,7 +857,7 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution private onServiceChange(): void { this.badgeHandle.clear(); - const actionRequired = this.extensionsWorkbenchService.installed.filter(e => e.runtimeState !== undefined); + const actionRequired = this.configurationService.getValue(AutoRestartConfigurationKey) === true ? [] : this.extensionsWorkbenchService.installed.filter(e => e.runtimeState !== undefined); const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) && !actionRequired.includes(e) ? 1 : 0), 0); const newBadgeNumber = outdated + actionRequired.length; if (newBadgeNumber > 0) { diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 4dcb55bb..f2eea7c6 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { isCancellationError, getErrorMessage } from 'vs/base/common/errors'; import { createErrorWithActions } from 'vs/base/common/errorMessage'; import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging'; -import { SortOrder, IQueryOptions as IGalleryQueryOptions, SortBy as GallerySortBy, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { SortOrder, IQueryOptions as IGalleryQueryOptions, SortBy as GallerySortBy, InstallExtensionInfo, ExtensionGalleryErrorCode, ExtensionGalleryError } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementServer, IExtensionManagementServerService, EnablementState, IWorkbenchExtensionManagementService, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { areSameExtensions, getExtensionDependencies } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -41,7 +41,6 @@ import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -214,9 +213,7 @@ export class ExtensionsListView extends ViewPane { return localize('extensions', "Extensions"); } }, - overrideStyles: { - listBackground: SIDE_BAR_BACKGROUND - }, + overrideStyles: this.getLocationBasedColors().listOverrideStyles, openOnSingleClick: true }) as WorkbenchPagedList; this._register(this.list.onContextMenu(e => this.onContextMenu(e), this)); @@ -900,10 +897,16 @@ export class ExtensionsListView extends ViewPane { } } if (galleryExtensions.length) { - const extensions = await this.extensionsWorkbenchService.getExtensions(galleryExtensions.map(id => ({ id })), { source: options.source }, token); - for (const extension of extensions) { - if (extension.gallery && !extension.deprecationInfo && (await this.extensionManagementService.canInstall(extension.gallery))) { - result.push(extension); + try { + const extensions = await this.extensionsWorkbenchService.getExtensions(galleryExtensions.map(id => ({ id })), { source: options.source }, token); + for (const extension of extensions) { + if (extension.gallery && !extension.deprecationInfo && (await this.extensionManagementService.canInstall(extension.gallery))) { + result.push(extension); + } + } + } catch (error) { + if (!resourceExtensions.length || !this.isOfflineError(error)) { + throw error; } } } @@ -1070,7 +1073,7 @@ export class ExtensionsListView extends ViewPane { if (count === 0 && this.isBodyVisible()) { if (error) { - if (isOfflineError(error)) { + if (this.isOfflineError(error)) { this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning); this.bodyTemplate.messageBox.textContent = localize('offline error', "Unable to search the Marketplace when offline, please check your network connection."); } else { @@ -1088,6 +1091,13 @@ export class ExtensionsListView extends ViewPane { this.updateSize(); } + private isOfflineError(error: Error): boolean { + if (error instanceof ExtensionGalleryError) { + return error.code === ExtensionGalleryErrorCode.Offline; + } + return isOfflineError(error); + } + protected updateSize() { if (this.options.flexibleHeight) { this.maximumBodySize = this.list?.model.length ? Number.POSITIVE_INFINITY : 0; diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 3d725460..23b84778 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -42,7 +42,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension | null = null; @@ -126,7 +126,7 @@ export class InstallCountWidget extends ExtensionWidget { export class RatingsWidget extends ExtensionWidget { - private readonly containerHover: IUpdatableHover; + private readonly containerHover: IManagedHover; constructor( private container: HTMLElement, @@ -140,7 +140,7 @@ export class RatingsWidget extends ExtensionWidget { container.classList.add('small'); } - this.containerHover = this._register(hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), container, '')); + this.containerHover = this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), container, '')); this.render(); } @@ -192,7 +192,7 @@ export class RatingsWidget extends ExtensionWidget { export class VerifiedPublisherWidget extends ExtensionWidget { private readonly disposables = this._register(new DisposableStore()); - private readonly containerHover: IUpdatableHover; + private readonly containerHover: IManagedHover; constructor( private container: HTMLElement, @@ -201,7 +201,7 @@ export class VerifiedPublisherWidget extends ExtensionWidget { @IOpenerService private readonly openerService: IOpenerService, ) { super(); - this.containerHover = this._register(hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), container, '')); + this.containerHover = this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), container, '')); this.render(); } @@ -258,7 +258,7 @@ export class SponsorWidget extends ExtensionWidget { } const sponsor = append(this.container, $('span.sponsor.clickable', { tabIndex: 0 })); - this.disposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), sponsor, this.extension?.publisherSponsorLink.toString() ?? '')); + this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), sponsor, this.extension?.publisherSponsorLink.toString() ?? '')); sponsor.setAttribute('role', 'link'); // #132645 const sponsorIconElement = renderIcon(sponsorIcon); const label = $('span', undefined, localize('sponsor', "Sponsor")); @@ -294,9 +294,7 @@ export class RecommendationWidget extends ExtensionWidget { } private clear(): void { - if (this.element) { - this.parent.removeChild(this.element); - } + this.element?.remove(); this.element = undefined; this.disposables.clear(); } @@ -330,9 +328,7 @@ export class PreReleaseBookmarkWidget extends ExtensionWidget { } private clear(): void { - if (this.element) { - this.parent.removeChild(this.element); - } + this.element?.remove(); this.element = undefined; this.disposables.clear(); } @@ -367,9 +363,7 @@ export class RemoteBadgeWidget extends ExtensionWidget { } private clear(): void { - if (this.remoteBadge.value) { - this.element.removeChild(this.remoteBadge.value.element); - } + this.remoteBadge.value?.element.remove(); this.remoteBadge.clear(); } @@ -386,7 +380,7 @@ export class RemoteBadgeWidget extends ExtensionWidget { class RemoteBadge extends Disposable { readonly element: HTMLElement; - readonly elementHover: IUpdatableHover; + readonly elementHover: IManagedHover; constructor( private readonly tooltip: boolean, @@ -397,7 +391,7 @@ class RemoteBadge extends Disposable { ) { super(); this.element = $('div.extension-badge.extension-remote-badge'); - this.elementHover = this._register(hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.element, '')); + this.elementHover = this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, '')); this.render(); } @@ -478,7 +472,7 @@ export class SyncIgnoredWidget extends ExtensionWidget { if (this.extension && this.extension.state === ExtensionState.Installed && this.userDataSyncEnablementService.isEnabled() && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)) { const element = append(this.container, $('span.extension-sync-ignored' + ThemeIcon.asCSSSelector(syncIgnoredIcon))); - this.disposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), element, localize('syncingore.label', "This extension is ignored during sync."))); + this.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), element, localize('syncingore.label', "This extension is ignored during sync."))); element.classList.add(...ThemeIcon.asClassNameArray(syncIgnoredIcon)); } } @@ -551,7 +545,7 @@ export class ExtensionHoverWidget extends ExtensionWidget { render(): void { this.hover.value = undefined; if (this.extension) { - this.hover.value = this.hoverService.setupUpdatableHover({ + this.hover.value = this.hoverService.setupManagedHover({ delay: this.configurationService.getValue('workbench.hover.delay'), showHover: (options, focus) => { return this.hoverService.showHover({ @@ -567,7 +561,18 @@ export class ExtensionHoverWidget extends ExtensionWidget { }, focus); }, placement: 'element' - }, this.options.target, { markdown: () => Promise.resolve(this.getHoverMarkdown()), markdownNotSupportedFallback: undefined }); + }, + this.options.target, + { + markdown: () => Promise.resolve(this.getHoverMarkdown()), + markdownNotSupportedFallback: undefined + }, + { + appearance: { + showHoverHint: true + } + } + ); } } @@ -647,7 +652,7 @@ export class ExtensionHoverWidget extends ExtensionWidget { const runtimeState = this.extension.runtimeState; const recommendationMessage = this.getRecommendationMessage(this.extension); - if (extensionRuntimeStatus || extensionStatus || runtimeState || recommendationMessage || preReleaseMessage) { + if (extensionRuntimeStatus || extensionStatus.length || runtimeState || recommendationMessage || preReleaseMessage) { markdown.appendMarkdown(`---`); markdown.appendText(`\n`); @@ -673,11 +678,11 @@ export class ExtensionHoverWidget extends ExtensionWidget { } } - if (extensionStatus) { - if (extensionStatus.icon) { - markdown.appendMarkdown(`$(${extensionStatus.icon.id}) `); + for (const status of extensionStatus) { + if (status.icon) { + markdown.appendMarkdown(`$(${status.icon.id}) `); } - markdown.appendMarkdown(extensionStatus.message.value); + markdown.appendMarkdown(status.message.value); if (this.extension.enablementState === EnablementState.DisabledByExtensionDependency && this.extension.local) { markdown.appendMarkdown(` [${localize('dependencies', "Show Dependencies")}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Dependencies]))}`)})`); } @@ -762,12 +767,18 @@ export class ExtensionStatusWidget extends ExtensionWidget { const disposables = new DisposableStore(); this.renderDisposables.value = disposables; const extensionStatus = this.extensionStatusAction.status; - if (extensionStatus) { + if (extensionStatus.length) { const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); - if (extensionStatus.icon) { - markdown.appendMarkdown(`$(${extensionStatus.icon.id}) `); + for (let i = 0; i < extensionStatus.length; i++) { + const status = extensionStatus[i]; + if (status.icon) { + markdown.appendMarkdown(`$(${status.icon.id}) `); + } + markdown.appendMarkdown(status.message.value); + if (i < extensionStatus.length - 1) { + markdown.appendText(`\n`); + } } - markdown.appendMarkdown(extensionStatus.message.value); const rendered = disposables.add(renderMarkdown(markdown, { actionHandler: { callback: (content) => { @@ -830,7 +841,7 @@ export class ExtensionRecommendationWidget extends ExtensionWidget { } export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hcDark: '#FF8E00', hcLight: textLinkForeground }, localize('extensionIconStarForeground', "The icon color for extension ratings."), true); -export const extensionVerifiedPublisherIconColor = registerColor('extensionIcon.verifiedForeground', { dark: textLinkForeground, light: textLinkForeground, hcDark: textLinkForeground, hcLight: textLinkForeground }, localize('extensionIconVerifiedForeground', "The icon color for extension verified publisher."), true); +export const extensionVerifiedPublisherIconColor = registerColor('extensionIcon.verifiedForeground', textLinkForeground, localize('extensionIconVerifiedForeground', "The icon color for extension verified publisher."), true); export const extensionPreReleaseIconColor = registerColor('extensionIcon.preReleaseForeground', { dark: '#1d9271', light: '#1d9271', hcDark: '#1d9271', hcLight: textLinkForeground }, localize('extensionPreReleaseForeground', "The icon color for pre-release extension."), true); export const extensionSponsorIconColor = registerColor('extensionIcon.sponsorForeground', { light: '#B51E78', dark: '#D758B3', hcDark: null, hcLight: '#B51E78' }, localize('extensionIcon.sponsorForeground', "The icon color for extension sponsor."), true); diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 3eb8c7ee..ba69585c 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -9,28 +9,29 @@ import { Event, Emitter } from 'vs/base/common/event'; import { firstOrDefault, index } from 'vs/base/common/arrays'; import { CancelablePromise, Promises, ThrottledDelayer, createCancelablePromise } from 'vs/base/common/async'; import { CancellationError, isCancellationError } from 'vs/base/common/errors'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult, IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX, - InstallOptions, IProductVersion + InstallOptions, IProductVersion, + UninstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath, IResourceExtension } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath, IResourceExtension, extensionsConfigurationNodeBase } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { URI } from 'vs/base/common/uri'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType, AutoRestartConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressOptions, IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, NotificationPriority, Severity } from 'vs/platform/notification/common/notification'; import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -42,7 +43,7 @@ import { FileAccess } from 'vs/base/common/network'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { IUserDataAutoSyncService, IUserDataSyncEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { isBoolean, isString, isUndefined } from 'vs/base/common/types'; +import { isBoolean, isDefined, isString, isUndefined } from 'vs/base/common/types'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IExtensionService, IExtensionsStatus, toExtension, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { isWeb, language } from 'vs/base/common/platform'; @@ -58,6 +59,8 @@ import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator' import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; interface IExtensionStateProvider { (extension: Extension): T; @@ -77,14 +80,15 @@ type ExtensionsLoadClassification = { export class Extension implements IExtension { public enablementState: EnablementState = EnablementState.EnabledGlobally; - public readonly resourceExtension: IResourceExtension | undefined; + + private galleryResourcesCache = new Map(); constructor( private stateProvider: IExtensionStateProvider, private runtimeStateProvider: IExtensionStateProvider, public readonly server: IExtensionManagementServer | undefined, public local: ILocalExtension | undefined, - public gallery: IGalleryExtension | undefined, + private _gallery: IGalleryExtension | undefined, private readonly resourceExtensionInfo: { resourceExtension: IResourceExtension; isWorkspaceScoped: boolean } | undefined, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -92,7 +96,32 @@ export class Extension implements IExtension { @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService ) { - this.resourceExtension = resourceExtensionInfo?.resourceExtension; + } + + get resourceExtension(): IResourceExtension | undefined { + if (this.resourceExtensionInfo) { + return this.resourceExtensionInfo.resourceExtension; + } + if (this.local?.isWorkspaceScoped) { + return { + type: 'resource', + identifier: this.local.identifier, + location: this.local.location, + manifest: this.local.manifest, + changelogUri: this.local.changelogUrl, + readmeUri: this.local.readmeUrl, + }; + } + return undefined; + } + + get gallery(): IGalleryExtension | undefined { + return this._gallery; + } + + set gallery(gallery: IGalleryExtension | undefined) { + this._gallery = gallery; + this.galleryResourcesCache.clear(); } get type(): ExtensionType { @@ -339,8 +368,9 @@ export class Extension implements IExtension { return !!this.gallery?.properties.isPreReleaseVersion; } + private _extensionEnabledWithPreRelease: boolean | undefined; get hasPreReleaseVersion(): boolean { - return !!this.gallery?.hasPreReleaseVersion || !!this.local?.hasPreReleaseVersion; + return !!this.gallery?.hasPreReleaseVersion || !!this.local?.hasPreReleaseVersion || !!this._extensionEnabledWithPreRelease; } get hasReleaseVersion(): boolean { @@ -358,11 +388,7 @@ export class Extension implements IExtension { } if (this.gallery) { - if (this.gallery.assets.manifest) { - return this.galleryService.getManifest(this.gallery, token); - } - this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id); - return null; + return this.getGalleryManifest(token); } if (this.resourceExtension) { @@ -372,6 +398,25 @@ export class Extension implements IExtension { return null; } + async getGalleryManifest(token: CancellationToken = CancellationToken.None): Promise { + if (this.gallery) { + let cache = this.galleryResourcesCache.get('manifest'); + if (!cache) { + if (this.gallery.assets.manifest) { + this.galleryResourcesCache.set('manifest', cache = this.galleryService.getManifest(this.gallery, token) + .catch(e => { + this.galleryResourcesCache.delete('manifest'); + throw e; + })); + } else { + this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id); + } + } + return cache; + } + return null; + } + hasReadme(): boolean { if (this.local && this.local.readmeUrl) { return true; @@ -498,6 +543,12 @@ ${this.description} return []; } + setExtensionsControlManifest(extensionsControlManifest: IExtensionsControlManifest): void { + this.isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(this.identifier, identifier)); + this.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[this.identifier.id.toLowerCase()] : undefined; + this._extensionEnabledWithPreRelease = extensionsControlManifest?.extensionsEnabledWithPreRelease?.includes(this.identifier.id.toLowerCase()); + } + private getManifestFromLocalOrResource(): IExtensionManifest | null { if (this.local) { return this.local.manifest; @@ -510,14 +561,10 @@ ${this.description} } const EXTENSIONS_AUTO_UPDATE_KEY = 'extensions.autoUpdate'; +const EXTENSIONS_DONOT_AUTO_UPDATE_KEY = 'extensions.donotAutoUpdate'; class Extensions extends Disposable { - static updateExtensionFromControlManifest(extension: Extension, extensionsControlManifest: IExtensionsControlManifest): void { - extension.isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier)); - extension.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()] : undefined; - } - private readonly _onChange = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>()); get onChange() { return this._onChange.event; } @@ -536,6 +583,7 @@ class Extensions extends Disposable { @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IWorkbenchExtensionManagementService private readonly workbenchExtensionManagementService: IWorkbenchExtensionManagementService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { @@ -544,7 +592,7 @@ class Extensions extends Disposable { this._register(server.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e))); this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e.identifier))); this._register(server.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e))); - this._register(server.extensionManagementService.onDidUpdateExtensionMetadata(e => this.onDidUpdateExtensionMetadata(e))); + this._register(server.extensionManagementService.onDidUpdateExtensionMetadata(e => this.onDidUpdateExtensionMetadata(e.local))); this._register(server.extensionManagementService.onDidChangeProfile(() => this.reset())); this._register(extensionEnablementService.onEnablementChanged(e => this.onEnablementChanged(e))); this._register(Event.any(this.onChange, this.onReset)(() => this._local = undefined)); @@ -666,7 +714,7 @@ class Extensions extends Disposable { const galleryWithLocalVersion: IGalleryExtension | undefined = (await this.galleryService.getExtensions([{ ...localExtension.identifier, version: localExtension.manifest.version }], CancellationToken.None))[0]; isPreReleaseVersion = !!galleryWithLocalVersion?.properties?.isPreReleaseVersion; } - return this.server.extensionManagementService.updateMetadata(localExtension, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId, isPreReleaseVersion }); + return this.server.extensionManagementService.updateMetadata(localExtension, { id: gallery.identifier.uuid, publisherDisplayName: gallery.publisherDisplayName, publisherId: gallery.publisherId, isPreReleaseVersion }, this.userDataProfileService.currentProfile.extensionsResource); } canInstall(galleryExtension: IGalleryExtension): Promise { @@ -720,7 +768,7 @@ class Extensions extends Disposable { const extension = byId[local.identifier.id] || this.instantiationService.createInstance(Extension, this.stateProvider, this.runtimeStateProvider, this.server, local, undefined, undefined); extension.local = local; extension.enablementState = this.extensionEnablementService.getEnablementState(local); - Extensions.updateExtensionFromControlManifest(extension, extensionsControlManifest); + extension.setExtensionsControlManifest(extensionsControlManifest); return extension; }); } @@ -756,7 +804,7 @@ class Extensions extends Disposable { if (!extension.gallery) { extension.gallery = gallery; } - Extensions.updateExtensionFromControlManifest(extension, await this.server.extensionManagementService.getExtensionsControlManifest()); + extension.setExtensionsControlManifest(await this.server.extensionManagementService.getExtensionsControlManifest()); extension.enablementState = this.extensionEnablementService.getEnablementState(local); } } @@ -949,9 +997,27 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension urlService.registerHandler(this); + if (this.productService.quality !== 'stable') { + this.registerAutoRestartConfig(); + } + this.whenInitialized = this.initialize(); } + private registerAutoRestartConfig(): void { + Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration({ + ...extensionsConfigurationNodeBase, + properties: { + [AutoRestartConfigurationKey]: { + type: 'boolean', + description: nls.localize('autoRestart', "If activated, extensions will automatically restart following an update if the window is not in focus. There can be a data loss if you have open Notebooks or Custom Editors."), + default: false, + } + } + }); + } + private async initialize(): Promise { // initialize local extensions await Promise.all([this.queryLocal(), this.extensionService.whenInstalledExtensionsRegistered()]); @@ -968,14 +1034,29 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.initializeAutoUpdate(); this.reportInstalledExtensionsTelemetry(); this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.reportProgressFromOtherSources())); - this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange(false))); + this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange())); + this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EXTENSIONS_DONOT_AUTO_UPDATE_KEY, this._store)(e => this.onDidSelectedExtensionToAutoUpdateValueChange())); } private initializeAutoUpdate(): void { + // Initialise Auto Update Value + let autoUpdateValue = this.getAutoUpdateValue(); + // Register listeners for auto updates this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { - this.onDidAutoUpdateConfigurationChange(); + const wasAutoUpdateEnabled = autoUpdateValue !== false; + autoUpdateValue = this.getAutoUpdateValue(); + const isAutoUpdateEnabled = this.isAutoUpdateEnabled(); + if (wasAutoUpdateEnabled !== isAutoUpdateEnabled) { + this.setEnabledAutoUpdateExtensions([]); + this.setDisabledAutoUpdateExtensions([]); + this._onChange.fire(undefined); + this.updateExtensionsPinnedState(!isAutoUpdateEnabled); + } + if (isAutoUpdateEnabled) { + this.eventuallyAutoUpdateExtensions(); + } } if (e.affectsConfiguration(AutoCheckUpdatesConfigurationKey)) { if (this.isAutoCheckUpdatesEnabled()) { @@ -990,11 +1071,14 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension })); this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0))); this._register(this.updateService.onStateChange(e => { - if (!this.isAutoUpdateEnabled()) { - return; - } if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloaded) { - this.eventuallyCheckForUpdates(true); + this.telemetryService.publicLog2<{}, { + owner: 'sandy081'; + comment: 'Report when update check is triggered on product update'; + }>('extensions:updatecheckonproductupdate'); + if (this.isAutoCheckUpdatesEnabled()) { + this.checkForUpdates(); + } } })); @@ -1011,6 +1095,55 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.autoUpdateBuiltinExtensions(); } } + + this.registerAutoRestartListener(); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AutoRestartConfigurationKey)) { + this.registerAutoRestartListener(); + } + })); + } + + private isAutoUpdateEnabled(): boolean { + return this.getAutoUpdateValue() !== false; + } + + getAutoUpdateValue(): AutoUpdateConfigurationValue { + const autoUpdate = this.configurationService.getValue(AutoUpdateConfigurationKey); + if (autoUpdate === 'onlySelectedExtensions') { + return false; + } + return isBoolean(autoUpdate) || autoUpdate === 'onlyEnabledExtensions' ? autoUpdate : true; + } + + async updateAutoUpdateValue(value: AutoUpdateConfigurationValue): Promise { + const wasEnabled = this.isAutoUpdateEnabled(); + const isEnabled = value !== false; + if (wasEnabled !== isEnabled) { + const result = await this.dialogService.confirm({ + title: nls.localize('confirmEnableDisableAutoUpdate', "Auto Update Extensions"), + message: isEnabled + ? nls.localize('confirmEnableAutoUpdate', "Do you want to enable auto update for all extensions?") + : nls.localize('confirmDisableAutoUpdate', "Do you want to disable auto update for all extensions?"), + detail: nls.localize('confirmEnableDisableAutoUpdateDetail', "This will reset any auto update settings you have set for individual extensions."), + }); + if (!result.confirmed) { + return; + } + } + await this.configurationService.updateValue(AutoUpdateConfigurationKey, value); + } + + private readonly autoRestartListenerDisposable = this._register(new MutableDisposable()); + private registerAutoRestartListener(): void { + this.autoRestartListenerDisposable.value = undefined; + if (this.configurationService.getValue(AutoRestartConfigurationKey) === true) { + this.autoRestartListenerDisposable.value = this.hostService.onDidChangeFocus(focus => { + if (!focus && this.configurationService.getValue(AutoRestartConfigurationKey) === true) { + this.updateRunningExtensions(true); + } + }); + } } private reportInstalledExtensionsTelemetry() { @@ -1054,6 +1187,13 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } } + private updateExtensionsPinnedState(pinned: boolean): Promise { + return this.progressService.withProgress({ + location: ProgressLocation.Extensions, + title: nls.localize('updatingExtensions', "Updating Extensions Auto Update State"), + }, () => this.extensionManagementService.resetPinnedStateForAllUserExtensions(pinned)); + } + private reset(): void { for (const task of this.tasksInProgress) { task.cancel(); @@ -1217,7 +1357,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension let extension = this.getInstalledExtensionMatchingGallery(gallery); if (!extension) { extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined); - Extensions.updateExtensionFromControlManifest(extension, extensionsControlManifest); + (extension).setExtensionsControlManifest(extensionsControlManifest); } return extension; } @@ -1262,7 +1402,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return undefined; } - async updateRunningExtensions(): Promise { + async updateRunningExtensions(auto: boolean = false): Promise { const toAdd: ILocalExtension[] = []; const toRemove: string[] = []; @@ -1303,8 +1443,26 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } if (toAdd.length || toRemove.length) { - if (await this.extensionService.stopExtensionHosts(nls.localize('restart', "Enable or Disable extensions"))) { + if (await this.extensionService.stopExtensionHosts(nls.localize('restart', "Enable or Disable extensions"), auto)) { await this.extensionService.startExtensionHosts({ toAdd, toRemove }); + if (auto) { + this.notificationService.notify({ + severity: Severity.Info, + message: nls.localize('extensionsAutoRestart', "Extensions were auto restarted to enable updates."), + priority: NotificationPriority.SILENT + }); + } + type ExtensionsAutoRestartClassification = { + owner: 'sandy081'; + comment: 'Report when extensions are auto restarted'; + count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions auto restarted' }; + auto: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the restart was triggered automatically' }; + }; + type ExtensionsAutoRestartEvent = { + count: number; + auto: boolean; + }; + this.telemetryService.publicLog2('extensions:autorestart', { count: toAdd.length + toRemove.length, auto }); } } } @@ -1546,15 +1704,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return ExtensionState.Uninstalled; } - private async onDidAutoUpdateConfigurationChange(): Promise { - await this.updateExtensionsPinnedState(); - if (this.isAutoUpdateEnabled()) { - this.checkForUpdates(); - } else { - this.setSelectedExtensionsToAutoUpdate([]); - } - } - async checkForUpdates(onlyBuiltin?: boolean): Promise { if (!this.galleryService.isEnabled()) { return; @@ -1617,6 +1766,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension operation: InstallOperation.Update, installPreReleaseVersion: extension.local?.isPreReleaseVersion, profileLocation: this.userDataProfileService.currentProfile.extensionsResource, + isApplicationScoped: extension.local?.isApplicationScoped, } }); } @@ -1639,20 +1789,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return; } await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery, this.getProductVersion()))); - if (this.isAutoUpdateEnabled()) { + if (this.outdated.length) { this.eventuallyAutoUpdateExtensions(); } } - getAutoUpdateValue(): AutoUpdateConfigurationValue { - const autoUpdate = this.configurationService.getValue(AutoUpdateConfigurationKey); - return isBoolean(autoUpdate) || autoUpdate === 'onlyEnabledExtensions' || autoUpdate === 'onlySelectedExtensions' ? autoUpdate : true; - } - - isAutoUpdateEnabled(): boolean { - return this.getAutoUpdateValue() !== false; - } - private isAutoCheckUpdatesEnabled(): boolean { return this.configurationService.getValue(AutoCheckUpdatesConfigurationKey); } @@ -1660,7 +1801,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private eventuallyCheckForUpdates(immediate = false): void { this.updatesCheckDelayer.cancel(); this.updatesCheckDelayer.trigger(async () => { - if (this.isAutoUpdateEnabled() || this.isAutoCheckUpdatesEnabled()) { + if (this.isAutoCheckUpdatesEnabled()) { await this.checkForUpdates(); } this.eventuallyCheckForUpdates(); @@ -1701,11 +1842,17 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } private async autoUpdateExtensions(): Promise { - if (!this.isAutoUpdateEnabled()) { - return; + const toUpdate: IExtension[] = []; + for (const extension of this.outdated) { + if (!this.shouldAutoUpdateExtension(extension)) { + continue; + } + if (await this.shouldRequireConsentToUpdate(extension)) { + continue; + } + toUpdate.push(extension); } - const toUpdate = this.outdated.filter(e => !e.local?.pinned && this.shouldAutoUpdateExtension(e)); if (!toUpdate.length) { return; } @@ -1737,32 +1884,72 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return undefined; } - private async updateExtensionsPinnedState(): Promise { - await Promise.all(this.installed.map(async e => { - if (e.isBuiltin) { - return; + private shouldAutoUpdateExtension(extension: IExtension): boolean { + if (extension.deprecationInfo?.disallowInstall) { + return false; + } + + const autoUpdateValue = this.getAutoUpdateValue(); + + if (autoUpdateValue === false) { + const extensionsToAutoUpdate = this.getEnabledAutoUpdateExtensions(); + const extensionId = extension.identifier.id.toLowerCase(); + if (extensionsToAutoUpdate.includes(extensionId)) { + return true; } - const shouldBePinned = !this.shouldAutoUpdateExtension(e); - if (e.local && e.local.pinned !== shouldBePinned) { - await this.extensionManagementService.updateMetadata(e.local, { pinned: shouldBePinned }); + if (this.isAutoUpdateEnabledForPublisher(extension.publisher) && !extensionsToAutoUpdate.includes(`-${extensionId}`)) { + return true; } - })); - } + return false; + } - private shouldAutoUpdateExtension(extension: IExtension): boolean { - const autoUpdate = this.getAutoUpdateValue(); - if (isBoolean(autoUpdate)) { - return autoUpdate; + if (extension.pinned) { + return false; + } + + const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions(); + if (disabledAutoUpdateExtensions.includes(extension.identifier.id.toLowerCase())) { + return false; } - if (autoUpdate === 'onlyEnabledExtensions') { + if (autoUpdateValue === true) { + return true; + } + + if (autoUpdateValue === 'onlyEnabledExtensions') { return this.extensionEnablementService.isEnabledEnablementState(extension.enablementState); } - const extensionsToAutoUpdate = this.getSelectedExtensionsToAutoUpdate(); - const extensionId = extension.identifier.id.toLowerCase(); - return extensionsToAutoUpdate.includes(extensionId) || - (!extensionsToAutoUpdate.includes(`-${extensionId}`) && this.isAutoUpdateEnabledForPublisher(extension.publisher)); + return false; + } + + async shouldRequireConsentToUpdate(extension: IExtension): Promise { + if (!extension.outdated) { + return; + } + + if (extension.local?.manifest.main || extension.local?.manifest.browser) { + return; + } + + if (!extension.gallery) { + return; + } + + if (isDefined(extension.gallery.properties?.executesCode)) { + if (!extension.gallery.properties.executesCode) { + return; + } + } else { + const manifest = extension instanceof Extension + ? await extension.getGalleryManifest() + : await this.galleryService.getManifest(extension.gallery, CancellationToken.None); + if (!manifest?.main && !manifest?.browser) { + return; + } + } + + return nls.localize('consentRequiredToUpdate', "The update for {0} extension introduces executable code, which is not present in the currently installed version.", extension.displayName); } isAutoUpdateEnabledFor(extensionOrPublisher: IExtension | string): boolean { @@ -1770,16 +1957,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) { throw new Error('Expected publisher string, found extension identifier'); } - const autoUpdate = this.getAutoUpdateValue(); - if (isBoolean(autoUpdate)) { - return autoUpdate; - } - if (autoUpdate === 'onlyEnabledExtensions') { - return false; + if (this.isAutoUpdateEnabled()) { + return true; } return this.isAutoUpdateEnabledForPublisher(extensionOrPublisher); } - return !extensionOrPublisher.local?.pinned && this.shouldAutoUpdateExtension(extensionOrPublisher); + return this.shouldAutoUpdateExtension(extensionOrPublisher); } private isAutoUpdateEnabledForPublisher(publisher: string): boolean { @@ -1788,101 +1971,131 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } async updateAutoUpdateEnablementFor(extensionOrPublisher: IExtension | string, enable: boolean): Promise { - const autoUpdateValue = this.getAutoUpdateValue(); - - if (autoUpdateValue === true || autoUpdateValue === 'onlyEnabledExtensions') { + if (this.isAutoUpdateEnabled()) { if (isString(extensionOrPublisher)) { throw new Error('Expected extension, found publisher string'); } - if (!extensionOrPublisher.local) { - throw new Error('Only installed extensions can be pinned'); - } - await this.extensionManagementService.updateMetadata(extensionOrPublisher.local, { pinned: !enable }); + const disabledAutoUpdateExtensions = this.getDisabledAutoUpdateExtensions(); + const extensionId = extensionOrPublisher.identifier.id.toLowerCase(); + const extensionIndex = disabledAutoUpdateExtensions.indexOf(extensionId); if (enable) { - this.eventuallyAutoUpdateExtensions(); - } - return; - } - - if (autoUpdateValue === false && enable) { - await this.configurationService.updateValue(AutoUpdateConfigurationKey, 'onlySelectedExtensions'); - } - - let update = false; - const autoUpdateExtensions = this.getSelectedExtensionsToAutoUpdate(); - if (isString(extensionOrPublisher)) { - if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) { - throw new Error('Expected publisher string, found extension identifier'); + if (extensionIndex !== -1) { + disabledAutoUpdateExtensions.splice(extensionIndex, 1); + } } - extensionOrPublisher = extensionOrPublisher.toLowerCase(); - if (this.isAutoUpdateEnabledFor(extensionOrPublisher) !== enable) { - update = true; - if (enable) { - autoUpdateExtensions.push(extensionOrPublisher); - } else { - if (autoUpdateExtensions.includes(extensionOrPublisher)) { - autoUpdateExtensions.splice(autoUpdateExtensions.indexOf(extensionOrPublisher), 1); - } + else { + if (extensionIndex === -1) { + disabledAutoUpdateExtensions.push(extensionId); } } - } else { - const extensionId = extensionOrPublisher.identifier.id.toLowerCase(); - const enableAutoUpdatesForPublisher = this.isAutoUpdateEnabledFor(extensionOrPublisher.publisher.toLowerCase()); - const enableAutoUpdatesForExtension = autoUpdateExtensions.includes(extensionId); - const disableAutoUpdatesForExtension = autoUpdateExtensions.includes(`-${extensionId}`); + this.setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions); + if (enable && extensionOrPublisher.local && extensionOrPublisher.pinned) { + await this.extensionManagementService.updateMetadata(extensionOrPublisher.local, { pinned: false }); + } + this._onChange.fire(extensionOrPublisher); + } - if (enable) { - if (disableAutoUpdatesForExtension) { - autoUpdateExtensions.splice(autoUpdateExtensions.indexOf(`-${extensionId}`), 1); - update = true; + else { + const enabledAutoUpdateExtensions = this.getEnabledAutoUpdateExtensions(); + if (isString(extensionOrPublisher)) { + if (EXTENSION_IDENTIFIER_REGEX.test(extensionOrPublisher)) { + throw new Error('Expected publisher string, found extension identifier'); } - if (enableAutoUpdatesForPublisher) { - if (enableAutoUpdatesForExtension) { - autoUpdateExtensions.splice(autoUpdateExtensions.indexOf(extensionId), 1); - update = true; + extensionOrPublisher = extensionOrPublisher.toLowerCase(); + if (this.isAutoUpdateEnabledFor(extensionOrPublisher) !== enable) { + if (enable) { + enabledAutoUpdateExtensions.push(extensionOrPublisher); + } else { + if (enabledAutoUpdateExtensions.includes(extensionOrPublisher)) { + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionOrPublisher), 1); + } } - } else { - if (!enableAutoUpdatesForExtension) { - autoUpdateExtensions.push(extensionId); - update = true; + } + this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions); + for (const e of this.installed) { + if (e.publisher.toLowerCase() === extensionOrPublisher) { + this._onChange.fire(e); } } - if (extensionOrPublisher.local?.pinned) { - await this.extensionManagementService.updateMetadata(extensionOrPublisher.local, { pinned: false }); + } else { + const extensionId = extensionOrPublisher.identifier.id.toLowerCase(); + const enableAutoUpdatesForPublisher = this.isAutoUpdateEnabledFor(extensionOrPublisher.publisher.toLowerCase()); + const enableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(extensionId); + const disableAutoUpdatesForExtension = enabledAutoUpdateExtensions.includes(`-${extensionId}`); + + if (enable) { + if (disableAutoUpdatesForExtension) { + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1); + } + if (enableAutoUpdatesForPublisher) { + if (enableAutoUpdatesForExtension) { + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1); + } + } else { + if (!enableAutoUpdatesForExtension) { + enabledAutoUpdateExtensions.push(extensionId); + } + } } - } - // Disable Auto Updates - else { - if (enableAutoUpdatesForExtension) { - autoUpdateExtensions.splice(autoUpdateExtensions.indexOf(extensionId), 1); - update = true; - } - if (enableAutoUpdatesForPublisher) { - if (!disableAutoUpdatesForExtension) { - autoUpdateExtensions.push(`-${extensionId}`); - update = true; + // Disable Auto Updates + else { + if (enableAutoUpdatesForExtension) { + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(extensionId), 1); } - } else { - if (disableAutoUpdatesForExtension) { - autoUpdateExtensions.splice(autoUpdateExtensions.indexOf(`-${extensionId}`), 1); - update = true; + if (enableAutoUpdatesForPublisher) { + if (!disableAutoUpdatesForExtension) { + enabledAutoUpdateExtensions.push(`-${extensionId}`); + } + } else { + if (disableAutoUpdatesForExtension) { + enabledAutoUpdateExtensions.splice(enabledAutoUpdateExtensions.indexOf(`-${extensionId}`), 1); + } } } + this.setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions); + this._onChange.fire(extensionOrPublisher); } } - if (update) { - this.setSelectedExtensionsToAutoUpdate(autoUpdateExtensions); - await this.onDidSelectedExtensionToAutoUpdateValueChange(true); - if (autoUpdateValue === 'onlySelectedExtensions' && autoUpdateExtensions.length === 0) { - await this.configurationService.updateValue(AutoUpdateConfigurationKey, false); - } + + if (enable) { + this.autoUpdateExtensions(); } } - private async onDidSelectedExtensionToAutoUpdateValueChange(forceUpdate: boolean): Promise { - if (forceUpdate || this.selectedExtensionsToAutoUpdateValue !== this.getSelectedExtensionsToAutoUpdateValue() /* This checks if current window changed the value or not */) { - await this.updateExtensionsPinnedState(); - this.eventuallyAutoUpdateExtensions(); + private onDidSelectedExtensionToAutoUpdateValueChange(): void { + if ( + this.enabledAuotUpdateExtensionsValue !== this.getEnabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */ + || this.disabledAutoUpdateExtensionsValue !== this.getDisabledAutoUpdateExtensionsValue() /* This checks if current window changed the value or not */ + ) { + const userExtensions = this.installed.filter(e => !e.isBuiltin); + const groupBy = (extensions: IExtension[]): IExtension[][] => { + const shouldAutoUpdate: IExtension[] = []; + const shouldNotAutoUpdate: IExtension[] = []; + for (const extension of extensions) { + if (this.shouldAutoUpdateExtension(extension)) { + shouldAutoUpdate.push(extension); + } else { + shouldNotAutoUpdate.push(extension); + } + } + return [shouldAutoUpdate, shouldNotAutoUpdate]; + }; + + const [wasShouldAutoUpdate, wasShouldNotAutoUpdate] = groupBy(userExtensions); + this._enabledAutoUpdateExtensionsValue = undefined; + this._disabledAutoUpdateExtensionsValue = undefined; + const [shouldAutoUpdate, shouldNotAutoUpdate] = groupBy(userExtensions); + + for (const e of wasShouldAutoUpdate ?? []) { + if (shouldNotAutoUpdate?.includes(e)) { + this._onChange.fire(e); + } + } + for (const e of wasShouldNotAutoUpdate ?? []) { + if (shouldAutoUpdate?.includes(e)) { + this._onChange.fire(e); + } + } } } @@ -1951,7 +2164,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } if (!extension && gallery) { extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined); - Extensions.updateExtensionFromControlManifest(extension as Extension, await this.extensionManagementService.getExtensionsControlManifest()); + (extension).setExtensionsControlManifest(await this.extensionManagementService.getExtensionsControlManifest()); } if (extension?.isMalicious) { throw new Error(nls.localize('malicious', "This extension is reported to be problematic.")); @@ -2023,10 +2236,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension throw new Error(nls.localize('unknown', "Unable to install extension")); } - if (installOptions.version) { - await this.updateAutoUpdateEnablementFor(extension, false); - } - if (installOptions.enable) { if (extension.enablementState === EnablementState.DisabledWorkspace || extension.enablementState === EnablementState.DisabledGlobally) { if (installOptions.justification) { @@ -2113,18 +2322,101 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return this.promptAndSetEnablement(extensions, enablementState); } - uninstall(extension: IExtension): Promise { - const ext = extension.local ? extension : this.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]; - const toUninstall: ILocalExtension | null = ext && ext.local ? ext.local : null; + async uninstall(e: IExtension): Promise { + const extension = e.local ? e : this.local.find(local => areSameExtensions(local.identifier, e.identifier)); + if (!extension?.local) { + throw new Error('Missing local'); + } - if (!toUninstall) { - return Promise.reject(new Error('Missing local')); + const extensionsToUninstall: UninstallExtensionInfo[] = [{ extension: extension.local }]; + for (const packExtension of this.getAllPackExtensionsToUninstall(extension.local, this.local)) { + if (!extensionsToUninstall.some(e => areSameExtensions(e.extension.identifier, packExtension.identifier))) { + extensionsToUninstall.push({ extension: packExtension }); + } } + + const dependents: IExtension[] = []; + for (const { extension } of extensionsToUninstall) { + for (const local of this.local) { + if (!local.local) { + continue; + } + if (areSameExtensions(local.identifier, extension.identifier)) { + continue; + } + if (local.dependencies.length === 0) { + continue; + } + if (extension.manifest.extensionPack?.some(id => areSameExtensions({ id }, local.identifier))) { + continue; + } + if (dependents.some(d => d.extensionPack.some(id => areSameExtensions({ id }, local.identifier)))) { + continue; + } + if (local.dependencies.some(dep => areSameExtensions(extension.identifier, { id: dep }))) { + dependents.push(local); + extensionsToUninstall.push({ extension: local.local }); + } + } + } + + if (dependents.length) { + const { result } = await this.dialogService.prompt({ + title: nls.localize('uninstallDependents', "Uninstall Extension with Dependents"), + type: Severity.Warning, + message: this.getErrorMessageForUninstallingAnExtensionWithDependents(extension, dependents), + buttons: [{ + label: nls.localize('uninstallAll', "Uninstall All"), + run: () => true + }], + cancelButton: { + run: () => false + } + }); + if (!result) { + throw new CancellationError(); + } + } + return this.withProgress({ location: ProgressLocation.Extensions, title: nls.localize('uninstallingExtension', 'Uninstalling extension....'), - source: `${toUninstall.identifier.id}` - }, () => this.extensionManagementService.uninstall(toUninstall).then(() => undefined)); + source: `${extension.identifier.id}` + }, () => this.extensionManagementService.uninstallExtensions(extensionsToUninstall).then(() => undefined)); + } + + private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: IExtension[], checked: ILocalExtension[] = []): ILocalExtension[] { + if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) { + return []; + } + checked.push(extension); + const extensionsPack = extension.manifest.extensionPack ?? []; + if (extensionsPack.length) { + const packedExtensions: ILocalExtension[] = []; + for (const i of installed) { + if (i.local && !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier))) { + packedExtensions.push(i.local); + } + } + const packOfPackedExtensions: ILocalExtension[] = []; + for (const packedExtension of packedExtensions) { + packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked)); + } + return [...packedExtensions, ...packOfPackedExtensions]; + } + return []; + } + + private getErrorMessageForUninstallingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string { + if (dependents.length === 1) { + return nls.localize('singleDependentUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' extension depends on this. Do you want to uninstall all these extensions?", extension.displayName, dependents[0].displayName); + } + if (dependents.length === 2) { + return nls.localize('twoDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to uninstall all these extensions?", + extension.displayName, dependents[0].displayName, dependents[1].displayName); + } + return nls.localize('multipleDependentsUninstallError', "Cannot uninstall '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to uninstall all these extensions?", + extension.displayName, dependents[0].displayName, dependents[1].displayName); } reinstall(extension: IExtension): Promise { @@ -2309,30 +2601,29 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } } - private checkAndSetEnablement(extensions: IExtension[], otherExtensions: IExtension[], enablementState: EnablementState): Promise { + private async checkAndSetEnablement(extensions: IExtension[], otherExtensions: IExtension[], enablementState: EnablementState): Promise { const allExtensions = [...extensions, ...otherExtensions]; const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace; if (!enable) { for (const extension of extensions) { const dependents = this.getDependentsAfterDisablement(extension, allExtensions, this.local); if (dependents.length) { - return new Promise((resolve, reject) => { - this.notificationService.prompt(Severity.Error, this.getDependentsErrorMessage(extension, allExtensions, dependents), [ - { - label: nls.localize('disable all', 'Disable All'), - run: async () => { - try { - await this.checkAndSetEnablement(dependents, [extension], enablementState); - resolve(); - } catch (error) { - reject(error); - } - } - } - ], { - onCancel: () => reject(new CancellationError()) - }); + const { result } = await this.dialogService.prompt({ + title: nls.localize('disableDependents', "Disable Extension with Dependents"), + type: Severity.Warning, + message: this.getDependentsErrorMessageForDisablement(extension, allExtensions, dependents), + buttons: [{ + label: nls.localize('disable all', 'Disable All'), + run: () => true + }], + cancelButton: { + run: () => false + } }); + if (!result) { + throw new CancellationError(); + } + await this.checkAndSetEnablement(dependents, [extension], enablementState); } } } @@ -2387,7 +2678,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }); } - private getDependentsErrorMessage(extension: IExtension, allDisabledExtensions: IExtension[], dependents: IExtension[]): string { + private getDependentsErrorMessageForDisablement(extension: IExtension, allDisabledExtensions: IExtension[], dependents: IExtension[]): string { for (const e of [extension, ...allDisabledExtensions]) { const dependentsOfTheExtension = dependents.filter(d => d.dependencies.some(id => areSameExtensions({ id }, e.identifier))); if (dependentsOfTheExtension.length) { @@ -2510,12 +2801,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } private getPublishersToAutoUpdate(): string[] { - return this.getSelectedExtensionsToAutoUpdate().filter(id => !EXTENSION_IDENTIFIER_REGEX.test(id)); + return this.getEnabledAutoUpdateExtensions().filter(id => !EXTENSION_IDENTIFIER_REGEX.test(id)); } - getSelectedExtensionsToAutoUpdate(): string[] { + getEnabledAutoUpdateExtensions(): string[] { try { - const parsedValue = JSON.parse(this.selectedExtensionsToAutoUpdateValue); + const parsedValue = JSON.parse(this.enabledAuotUpdateExtensionsValue); if (Array.isArray(parsedValue)) { return parsedValue; } @@ -2523,32 +2814,70 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return []; } - private setSelectedExtensionsToAutoUpdate(selectedExtensionsToAutoUpdate: string[]): void { - this.selectedExtensionsToAutoUpdateValue = JSON.stringify(selectedExtensionsToAutoUpdate); + private setEnabledAutoUpdateExtensions(enabledAutoUpdateExtensions: string[]): void { + this.enabledAuotUpdateExtensionsValue = JSON.stringify(enabledAutoUpdateExtensions); } - private _selectedExtensionsToAutoUpdateValue: string | undefined; - private get selectedExtensionsToAutoUpdateValue(): string { - if (!this._selectedExtensionsToAutoUpdateValue) { - this._selectedExtensionsToAutoUpdateValue = this.getSelectedExtensionsToAutoUpdateValue(); + private _enabledAutoUpdateExtensionsValue: string | undefined; + private get enabledAuotUpdateExtensionsValue(): string { + if (!this._enabledAutoUpdateExtensionsValue) { + this._enabledAutoUpdateExtensionsValue = this.getEnabledAutoUpdateExtensionsValue(); } - return this._selectedExtensionsToAutoUpdateValue; + return this._enabledAutoUpdateExtensionsValue; } - private set selectedExtensionsToAutoUpdateValue(placeholderViewContainesValue: string) { - if (this.selectedExtensionsToAutoUpdateValue !== placeholderViewContainesValue) { - this._selectedExtensionsToAutoUpdateValue = placeholderViewContainesValue; - this.setSelectedExtensionsToAutoUpdateValue(placeholderViewContainesValue); + private set enabledAuotUpdateExtensionsValue(enabledAuotUpdateExtensionsValue: string) { + if (this.enabledAuotUpdateExtensionsValue !== enabledAuotUpdateExtensionsValue) { + this._enabledAutoUpdateExtensionsValue = enabledAuotUpdateExtensionsValue; + this.setEnabledAutoUpdateExtensionsValue(enabledAuotUpdateExtensionsValue); } } - private getSelectedExtensionsToAutoUpdateValue(): string { + private getEnabledAutoUpdateExtensionsValue(): string { return this.storageService.get(EXTENSIONS_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]'); } - private setSelectedExtensionsToAutoUpdateValue(value: string): void { + private setEnabledAutoUpdateExtensionsValue(value: string): void { this.storageService.store(EXTENSIONS_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER); } + getDisabledAutoUpdateExtensions(): string[] { + try { + const parsedValue = JSON.parse(this.disabledAutoUpdateExtensionsValue); + if (Array.isArray(parsedValue)) { + return parsedValue; + } + } catch (e) { /* Ignore */ } + return []; + } + + private setDisabledAutoUpdateExtensions(disabledAutoUpdateExtensions: string[]): void { + this.disabledAutoUpdateExtensionsValue = JSON.stringify(disabledAutoUpdateExtensions); + } + + private _disabledAutoUpdateExtensionsValue: string | undefined; + private get disabledAutoUpdateExtensionsValue(): string { + if (!this._disabledAutoUpdateExtensionsValue) { + this._disabledAutoUpdateExtensionsValue = this.getDisabledAutoUpdateExtensionsValue(); + } + + return this._disabledAutoUpdateExtensionsValue; + } + + private set disabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue: string) { + if (this.disabledAutoUpdateExtensionsValue !== disabledAutoUpdateExtensionsValue) { + this._disabledAutoUpdateExtensionsValue = disabledAutoUpdateExtensionsValue; + this.setDisabledAutoUpdateExtensionsValue(disabledAutoUpdateExtensionsValue); + } + } + + private getDisabledAutoUpdateExtensionsValue(): string { + return this.storageService.get(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, StorageScope.APPLICATION, '[]'); + } + + private setDisabledAutoUpdateExtensionsValue(value: string): void { + this.storageService.store(EXTENSIONS_DONOT_AUTO_UPDATE_KEY, value, StorageScope.APPLICATION, StorageTarget.USER); + } + } diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extension.css b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extension.css index 985b5511..30bbba04 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -192,14 +192,6 @@ font-weight: 600; } -.monaco-list-row.disabled .extension-list-item > .details > .footer > .author > .publisher-name{ - color: var(--vscode-disabledForeground); -} - -.monaco-list-row.disabled.selected .extension-list-item > .details > .footer > .author > .publisher-name{ - color: unset; -} - .monaco-list-row.selected .extension-list-item > .details > .footer > .author > .publisher-name{ color: unset; } @@ -235,12 +227,16 @@ min-width: 0; } -.monaco-list-row.disabled .extension-list-item { +.monaco-list-row.disabled:not(.selected) .extension-list-item > .details > .footer > .author > .publisher-name { + color: var(--vscode-disabledForeground); +} + +.monaco-list-row.disabled:not(.selected) .extension-list-item { color: var(--vscode-disabledForeground); } -.monaco-list-row.disabled.selected .extension-list-item .details .header .name, -.monaco-list-row.disabled.selected .extension-list-item .details .description { +.monaco-list-row.disabled:not(.selected) .extension-list-item .details .header .name, +.monaco-list-row.disabled:not(.selected) .extension-list-item .details .description { color: unset; } diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 7bca4243..ed8c3395 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -74,15 +74,15 @@ border-bottom: 1px solid var(--vscode-contrastBorder); } -.monaco-action-bar .action-item .action-label.extension-action.extension-status-error { +.monaco-action-bar .action-item .action-label.extension-action.extension-status-error::before { color: var(--vscode-editorError-foreground); } -.monaco-action-bar .action-item .action-label.extension-action.extension-status-warning { +.monaco-action-bar .action-item .action-label.extension-action.extension-status-warning::before { color: var(--vscode-editorWarning-foreground); } -.monaco-action-bar .action-item .action-label.extension-action.extension-status-info { +.monaco-action-bar .action-item .action-label.extension-action.extension-status-info::before { color: var(--vscode-editorInfo-foreground); } @@ -97,7 +97,8 @@ .monaco-action-bar .action-item.disabled .action-label.extension-action.hide, .monaco-action-bar .action-item.disabled .action-label.extension-action.ignore, .monaco-action-bar .action-item.disabled .action-label.extension-action.undo-ignore, -.monaco-action-bar .action-item.disabled .action-label.extension-action.install:not(.installing), +.monaco-action-bar .action-item .action-label.extension-action.install.hide, +.monaco-action-bar .action-item.disabled .action-label.extension-action.install-other-server:not(.installing), .monaco-action-bar .action-item.disabled .action-label.extension-action.uninstall:not(.uninstalling), .monaco-action-bar .action-item.disabled .action-label.extension-action.hide-when-disabled, .monaco-action-bar .action-item.disabled .action-label.extension-action.update, @@ -105,7 +106,7 @@ .monaco-action-bar .action-item.disabled .action-label.extension-action.theme, .monaco-action-bar .action-item.disabled .action-label.extension-action.language, .monaco-action-bar .action-item.disabled .action-label.extension-action.extension-sync, -.monaco-action-bar .action-item.action-dropdown-item.disabled, +.monaco-action-bar .action-item.action-dropdown-item.hide, .monaco-action-bar .action-item.action-dropdown-item .action-label.extension-action.hide, .monaco-action-bar .action-item.disabled .action-label.extension-action.reload, .monaco-action-bar .action-item.disabled .action-label.disable-status.hide, @@ -115,6 +116,10 @@ display: none; } +.monaco-action-bar .action-item.disabled .action-label.extension-action.label { + opacity: 0.4 !important; +} + .monaco-action-bar .action-item.checkbox-action-item.disabled { display: none; } diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index a59e7c53..1de72e81 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -485,6 +485,7 @@ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + text-decoration: var(--text-link-decoration); } .extension-editor > .body > .content > .details > .additional-details-container .resources-container > .resources > .resource:hover { diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 552e9cf5..a4514bd0 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -144,10 +144,10 @@ max-width: 100px; } -.monaco-workbench.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .icon-container > .icon, -.monaco-workbench.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .icon-container > .icon, -.monaco-workbench.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .details > .header-container .codicon, -.monaco-workbench.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension-list-item > .details > .header-container .codicon { +.monaco-workbench.vs .extensions-viewlet > .extensions .monaco-list-row.disabled:not(.selected) > .extension-list-item > .icon-container > .icon, +.monaco-workbench.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled:not(.selected) > .extension-list-item > .icon-container > .icon, +.monaco-workbench.vs .extensions-viewlet > .extensions .monaco-list-row.disabled:not(.selected) > .extension-list-item > .details > .header-container .codicon, +.monaco-workbench.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled:not(.selected) > .extension-list-item > .details > .header-container .codicon { opacity: 0.5; } diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css index d77881df..6a7ab84a 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/media/extensionsWidgets.css @@ -39,6 +39,7 @@ .extension-verified-publisher > .extension-verified-publisher-domain { padding-left: 2px; color: var(--vscode-extensionIcon-verifiedForeground); + text-decoration: var(--text-link-decoration); } .extension-bookmark { @@ -66,7 +67,7 @@ bottom: 9px; left: 1px; color: inherit; - font-size: 80%; + font-size: 80% !important; } .extension-bookmark .recommendation { diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts index 586c9f83..c604e316 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts @@ -55,6 +55,8 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { this._register(this.fileService.watch(this.uriIdentityService.extUri.joinPath(folder.uri, WORKSPACE_EXTENSIONS_FOLDER))); } + this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.onDidChangeWorkspaceExtensionsScheduler.schedule())); + this._register(this.fileService.onDidFilesChange(e => { if (this.contextService.getWorkspace().folders.some(folder => e.affects(this.uriIdentityService.extUri.joinPath(folder.uri, WORKSPACE_EXTENSIONS_FOLDER), FileChangeType.ADDED, FileChangeType.DELETED)) diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/common/extensions.ts b/patched-vscode/src/vs/workbench/contrib/extensions/common/extensions.ts index 179624e2..db54e378 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -62,6 +62,7 @@ export interface IExtension { readonly publisherUrl?: URI; readonly publisherDomain?: { link: string; verified: boolean }; readonly publisherSponsorLink?: URI; + readonly pinned: boolean; readonly version: string; readonly latestVersion: string; readonly preRelease: boolean; @@ -137,8 +138,9 @@ export interface IExtensionsWorkbenchService { setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; isAutoUpdateEnabledFor(extensionOrPublisher: IExtension | string): boolean; updateAutoUpdateEnablementFor(extensionOrPublisher: IExtension | string, enable: boolean): Promise; + shouldRequireConsentToUpdate(extension: IExtension): Promise; open(extension: IExtension | string, options?: IExtensionEditorOptions): Promise; - isAutoUpdateEnabled(): boolean; + updateAutoUpdateValue(value: AutoUpdateConfigurationValue): Promise; getAutoUpdateValue(): AutoUpdateConfigurationValue; checkForUpdates(): Promise; getExtensionStatus(extension: IExtension): IExtensionsStatus | undefined; @@ -163,6 +165,7 @@ export const ConfigurationKey = 'extensions'; export const AutoUpdateConfigurationKey = 'extensions.autoUpdate'; export const AutoCheckUpdatesConfigurationKey = 'extensions.autoCheckUpdates'; export const CloseExtensionDetailsOnViewChangeKey = 'extensions.closeExtensionDetailsOnViewChange'; +export const AutoRestartConfigurationKey = 'extensions.autoRestart'; export type AutoUpdateConfigurationValue = boolean | 'onlyEnabledExtensions' | 'onlySelectedExtensions'; diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts b/patched-vscode/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts index 4ba4300b..47325650 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction.ts @@ -4,37 +4,43 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; +import { Disposable } from 'vs/base/common/lifecycle'; import { randomPort } from 'vs/base/common/ports'; import * as nls from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INativeHostService } from 'vs/platform/native/common/native'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensionHostKind'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class DebugExtensionHostAction extends Action { static readonly ID = 'workbench.extensions.action.debugExtensionHost'; - static readonly LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host"); + static readonly LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host In New Window"); static readonly CSS_CLASS = 'debug-extension-host'; constructor( - @IDebugService private readonly _debugService: IDebugService, @INativeHostService private readonly _nativeHostService: INativeHostService, @IDialogService private readonly _dialogService: IDialogService, @IExtensionService private readonly _extensionService: IExtensionService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IHostService private readonly _hostService: IHostService, ) { super(DebugExtensionHostAction.ID, DebugExtensionHostAction.LABEL, DebugExtensionHostAction.CSS_CLASS); } - override async run(): Promise { - + override async run(_args: unknown): Promise { const inspectPorts = await this._extensionService.getInspectPorts(ExtensionHostKind.LocalProcess, false); if (inspectPorts.length === 0) { const res = await this._dialogService.confirm({ - message: nls.localize('restart1', "Profile Extensions"), - detail: nls.localize('restart2', "In order to profile extensions a restart is required. Do you want to restart '{0}' now?", this.productService.nameLong), + message: nls.localize('restart1', "Debug Extensions"), + detail: nls.localize('restart2', "In order to debug extensions a restart is required. Do you want to restart '{0}' now?", this.productService.nameLong), primaryButton: nls.localize({ key: 'restart3', comment: ['&& denotes a mnemonic'] }, "&&Restart") }); if (res.confirmed) { @@ -49,11 +55,52 @@ export class DebugExtensionHostAction extends Action { console.warn(`There are multiple extension hosts available for debugging. Picking the first one...`); } - return this._debugService.startDebugging(undefined, { - type: 'node', - name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"), - request: 'attach', - port: inspectPorts[0].port, - }); + const s = this._instantiationService.createInstance(Storage); + s.storeDebugOnNewWindow(inspectPorts[0].port); + + this._hostService.openWindow(); + } +} + +class Storage { + constructor(@IStorageService private readonly _storageService: IStorageService,) { + } + + storeDebugOnNewWindow(targetPort: number) { + this._storageService.store('debugExtensionHost.debugPort', targetPort, StorageScope.APPLICATION, StorageTarget.MACHINE); + } + + getAndDeleteDebugPortIfSet(): number | undefined { + const port = this._storageService.getNumber('debugExtensionHost.debugPort', StorageScope.APPLICATION); + if (port !== undefined) { + this._storageService.remove('debugExtensionHost.debugPort', StorageScope.APPLICATION); + } + return port; + } +} + +export class DebugExtensionsContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IDebugService private readonly _debugService: IDebugService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IProgressService _progressService: IProgressService, + ) { + super(); + + const storage = this._instantiationService.createInstance(Storage); + const port = storage.getAndDeleteDebugPortIfSet(); + if (port !== undefined) { + _progressService.withProgress({ + location: ProgressLocation.Notification, + title: nls.localize('debugExtensionHost.progress', "Attaching Debugger To Extension Host"), + }, async p => { + await this._debugService.startDebugging(undefined, { + type: 'node', + name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"), + request: 'attach', + port, + }); + }); + } } } diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts b/patched-vscode/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts index ff915c04..024719af 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution.ts @@ -13,7 +13,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { RuntimeExtensionsEditor, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED, SaveExtensionHostProfileAction, IExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor'; -import { DebugExtensionHostAction } from 'vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction'; +import { DebugExtensionHostAction, DebugExtensionsContribution } from 'vs/workbench/contrib/extensions/electron-sandbox/debugExtensionHostAction'; import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; @@ -75,11 +75,12 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(RemoteExtensionsInitializerContribution, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(DebugExtensionsContribution, LifecyclePhase.Restored); // Register Commands -CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor) => { +CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor, ...args) => { const instantiationService = accessor.get(IInstantiationService); - instantiationService.createInstance(DebugExtensionHostAction).run(); + return instantiationService.createInstance(DebugExtensionHostAction).run(args); }); CommandsRegistry.registerCommand(StartExtensionHostProfileAction.ID, (accessor: ServicesAccessor) => { @@ -109,6 +110,15 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { when: ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID) }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: DebugExtensionHostAction.ID, + title: localize('debugExtensionHost', "Debug Extensions In New Window"), + category: localize('developer', "Developer"), + icon: Codicon.debugStart + }, +}); + MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: StartExtensionHostProfileAction.ID, diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts b/patched-vscode/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts index a743e9e1..588f6a3c 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; suite('Extension query', () => { diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts index 8c96cf7a..efdcd3ab 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extension.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions'; import { Extension } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IGalleryExtension, IGalleryExtensionProperties, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts index 4e62c458..be1ca52a 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as sinon from 'sinon'; -import * as assert from 'assert'; +import assert from 'assert'; import * as uuid from 'vs/base/common/uuid'; import { IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, IExtensionTipsService, getTargetPlatform, diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts index 00c2b9a6..197f6059 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { generateUuid } from 'vs/base/common/uuid'; import { IExtensionsWorkbenchService, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions'; import * as ExtensionsActions from 'vs/workbench/contrib/extensions/browser/extensionsActions'; @@ -60,6 +60,9 @@ import { IUpdateService, State } from 'vs/platform/update/common/update'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { Mutable } from 'vs/base/common/types'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; +import { toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; let instantiationService: TestInstantiationService; let installEvent: Emitter, @@ -126,6 +129,7 @@ function setupTest(disposables: Pick) { } }); + instantiationService.stub(IUserDataProfileService, disposables.add(new UserDataProfileService(toUserDataProfile('test', 'test', URI.file('foo'), URI.file('cache'))))); instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); instantiationService.stub(ILabelService, { onDidChangeFormatters: disposables.add(new Emitter()).event }); @@ -173,7 +177,7 @@ suite('ExtensionsActions', () => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); assert.strictEqual('Install', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install hide', testObject.class); }); }); }); @@ -187,7 +191,7 @@ suite('ExtensionsActions', () => { return workbenchService.queryGallery(CancellationToken.None) .then((paged) => { testObject.extension = paged.firstPage[0]; - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); assert.ok(!testObject.enabled); assert.strictEqual('Installing', testObject.label); @@ -202,7 +206,7 @@ suite('ExtensionsActions', () => { const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); const paged = await workbenchService.queryGallery(CancellationToken.None); - const promise = Event.toPromise(testObject.onDidChange); + const promise = Event.toPromise(Event.filter(testObject.onDidChange, e => e.enabled === true)); testObject.extension = paged.firstPage[0]; await promise; assert.ok(testObject.enabled); @@ -217,8 +221,8 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - uninstallEvent.fire({ identifier: local.identifier }); - didUninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); + didUninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -232,8 +236,8 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - uninstallEvent.fire({ identifier: local.identifier }); - didUninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); + didUninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -255,7 +259,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { testObject.extension = extensions[0]; - uninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); assert.strictEqual('Uninstalling', testObject.label); assert.strictEqual('extension-action label uninstall uninstalling', testObject.class); @@ -303,7 +307,7 @@ suite('ExtensionsActions', () => { const gallery = aGalleryExtension('a'); const extension = extensions[0]; extension.gallery = gallery; - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); testObject.extension = extension; assert.ok(!testObject.enabled); }); @@ -318,9 +322,9 @@ suite('ExtensionsActions', () => { const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); testObject.extension = paged.firstPage[0]; - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); const promise = Event.toPromise(testObject.onDidChange); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery), profileLocation: null! }]); await promise; assert.ok(testObject.enabled); @@ -429,7 +433,7 @@ suite('ExtensionsActions', () => { c(); } })); - installEvent.fire({ identifier: local.identifier, source: gallery }); + installEvent.fire({ identifier: local.identifier, source: gallery, profileLocation: null! }); }); }); @@ -480,7 +484,7 @@ suite('ExtensionsActions', () => { .then(page => { testObject.extension = page.firstPage[0]; - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); assert.ok(!testObject.enabled); assert.strictEqual('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class); assert.strictEqual('Manage', testObject.tooltip); @@ -495,9 +499,9 @@ suite('ExtensionsActions', () => { const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); testObject.extension = paged.firstPage[0]; - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); const promise = Event.toPromise(testObject.onDidChange); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery), profileLocation: null! }]); await promise; assert.ok(testObject.enabled); @@ -529,7 +533,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { testObject.extension = extensions[0]; - uninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); assert.strictEqual('extension-action icon manage codicon codicon-extensions-manage', testObject.class); @@ -735,7 +739,7 @@ suite('ExtensionsActions', () => { testObject.extension = page.firstPage[0]; disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); assert.ok(!testObject.enabled); }); }); @@ -748,7 +752,7 @@ suite('ExtensionsActions', () => { .then(extensions => { const testObject: ExtensionsActions.EnableDropDownAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableDropDownAction)); testObject.extension = extensions[0]; - uninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); }); @@ -929,7 +933,7 @@ suite('ExtensionsActions', () => { const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = page.firstPage[0]; disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); assert.ok(!testObject.enabled); }); }); @@ -948,7 +952,7 @@ suite('ExtensionsActions', () => { const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = extensions[0]; disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); - uninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); }); @@ -976,7 +980,7 @@ suite('ExtensionRuntimeStateAction', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); const paged = await workbenchService.queryGallery(CancellationToken.None); testObject.extension = paged.firstPage[0]; - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); assert.ok(!testObject.enabled); }); @@ -989,7 +993,7 @@ suite('ExtensionRuntimeStateAction', () => { const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); testObject.extension = extensions[0]; - uninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); @@ -1010,9 +1014,9 @@ suite('ExtensionRuntimeStateAction', () => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); const promise = Event.toPromise(testObject.onDidChange); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery), profileLocation: null! }]); await promise; assert.ok(testObject.enabled); assert.strictEqual(testObject.tooltip, `Please restart extensions to enable this extension.`); @@ -1035,8 +1039,8 @@ suite('ExtensionRuntimeStateAction', () => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery), profileLocation: null! }]); assert.ok(!testObject.enabled); }); @@ -1056,10 +1060,10 @@ suite('ExtensionRuntimeStateAction', () => { testObject.extension = paged.firstPage[0]; const identifier = gallery.identifier; - installEvent.fire({ identifier, source: gallery }); - didInstallEvent.fire([{ identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, { identifier }) }]); - uninstallEvent.fire({ identifier }); - didUninstallEvent.fire({ identifier }); + installEvent.fire({ identifier, source: gallery, profileLocation: null! }); + didInstallEvent.fire([{ identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, { identifier }), profileLocation: null! }]); + uninstallEvent.fire({ identifier, profileLocation: null! }); + didUninstallEvent.fire({ identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); @@ -1080,8 +1084,8 @@ suite('ExtensionRuntimeStateAction', () => { const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); testObject.extension = extensions[0]; - uninstallEvent.fire({ identifier: local.identifier }); - didUninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); + didUninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(testObject.enabled); assert.strictEqual(testObject.tooltip, `Please restart extensions to complete the uninstallation of this extension.`); }); @@ -1101,8 +1105,8 @@ suite('ExtensionRuntimeStateAction', () => { const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); testObject.extension = extensions[0]; - uninstallEvent.fire({ identifier: local.identifier }); - didUninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); + didUninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); @@ -1121,13 +1125,13 @@ suite('ExtensionRuntimeStateAction', () => { const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); testObject.extension = extensions[0]; - uninstallEvent.fire({ identifier: local.identifier }); - didUninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); + didUninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); const gallery = aGalleryExtension('a'); const identifier = gallery.identifier; - installEvent.fire({ identifier, source: gallery }); - didInstallEvent.fire([{ identifier, source: gallery, operation: InstallOperation.Install, local }]); + installEvent.fire({ identifier, source: gallery, profileLocation: null! }); + didInstallEvent.fire([{ identifier, source: gallery, operation: InstallOperation.Install, local, profileLocation: null! }]); assert.ok(!testObject.enabled); }); @@ -1156,8 +1160,8 @@ suite('ExtensionRuntimeStateAction', () => { } })); const gallery = aGalleryExtension('a', { uuid: local.identifier.id, version: '1.0.2' }); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery), profileLocation: null! }]); }); }); @@ -1179,8 +1183,8 @@ suite('ExtensionRuntimeStateAction', () => { testObject.extension = extensions[0]; const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Update, local: aLocalExtension('a', gallery, gallery) }]); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Update, local: aLocalExtension('a', gallery, gallery), profileLocation: null! }]); assert.ok(!testObject.enabled); }); @@ -1290,8 +1294,8 @@ suite('ExtensionRuntimeStateAction', () => { testObject.extension = extensions[0]; const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery), profileLocation: null! }]); await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); await testObject.update(); assert.ok(testObject.enabled); @@ -1315,8 +1319,8 @@ suite('ExtensionRuntimeStateAction', () => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', { ...gallery, ...{ contributes: { localizations: [{ languageId: 'de', translations: [] }] } } }, gallery) }]); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', { ...gallery, ...{ contributes: { localizations: [{ languageId: 'de', translations: [] }] } } }, gallery), profileLocation: null! }]); assert.ok(!testObject.enabled); }); @@ -1337,8 +1341,8 @@ suite('ExtensionRuntimeStateAction', () => { testObject.extension = extensions[0]; const gallery = aGalleryExtension('a', { uuid: local.identifier.id, version: '1.0.2' }); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', { ...gallery, ...{ contributes: { localizations: [{ languageId: 'de', translations: [] }] } } }, gallery) }]); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', { ...gallery, ...{ contributes: { localizations: [{ languageId: 'de', translations: [] }] } } }, gallery), profileLocation: null! }]); assert.ok(!testObject.enabled); }); @@ -1378,7 +1382,7 @@ suite('ExtensionRuntimeStateAction', () => { const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const localExtensionManagementService = createExtensionManagementService([localExtension]); const uninstallEvent = new Emitter(); - const onDidUninstallEvent = new Emitter<{ identifier: IExtensionIdentifier }>(); + const onDidUninstallEvent = new Emitter<{ identifier: IExtensionIdentifier; profileLocation: URI }>(); localExtensionManagementService.onUninstallExtension = uninstallEvent.event; localExtensionManagementService.onDidUninstallExtension = onDidUninstallEvent.event; const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); @@ -1404,8 +1408,8 @@ suite('ExtensionRuntimeStateAction', () => { assert.ok(testObject.extension); assert.ok(!testObject.enabled); - uninstallEvent.fire({ identifier: localExtension.identifier }); - didUninstallEvent.fire({ identifier: localExtension.identifier }); + uninstallEvent.fire({ identifier: localExtension.identifier, profileLocation: null! }); + didUninstallEvent.fire({ identifier: localExtension.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); @@ -1442,7 +1446,7 @@ suite('ExtensionRuntimeStateAction', () => { const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const promise = Event.toPromise(testObject.onDidChange); - onDidInstallEvent.fire([{ identifier: remoteExtension.identifier, local: remoteExtension, operation: InstallOperation.Install }]); + onDidInstallEvent.fire([{ identifier: remoteExtension.identifier, local: remoteExtension, operation: InstallOperation.Install, profileLocation: null! }]); await promise; assert.ok(testObject.enabled); @@ -1481,7 +1485,7 @@ suite('ExtensionRuntimeStateAction', () => { const localExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a') }); const promise = Event.toPromise(Event.filter(testObject.onDidChange, () => testObject.enabled)); - onDidInstallEvent.fire([{ identifier: localExtension.identifier, local: localExtension, operation: InstallOperation.Install }]); + onDidInstallEvent.fire([{ identifier: localExtension.identifier, local: localExtension, operation: InstallOperation.Install, profileLocation: null! }]); await promise; assert.ok(testObject.enabled); @@ -1729,7 +1733,7 @@ suite('RemoteInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test remote install action when installing local workspace extension', async () => { @@ -1755,12 +1759,12 @@ suite('RemoteInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); - onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, source: gallery }); + onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, source: gallery, profileLocation: null! }); assert.ok(testObject.enabled); assert.strictEqual('Installing', testObject.label); - assert.strictEqual('extension-action label install installing', testObject.class); + assert.strictEqual('extension-action label install-other-server installing', testObject.class); }); test('Test remote install action when installing local workspace extension is finished', async () => { @@ -1788,16 +1792,16 @@ suite('RemoteInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); - onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, source: gallery }); + onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, source: gallery, profileLocation: null! }); assert.ok(testObject.enabled); assert.strictEqual('Installing', testObject.label); - assert.strictEqual('extension-action label install installing', testObject.class); + assert.strictEqual('extension-action label install-other-server installing', testObject.class); const installedExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const promise = Event.toPromise(testObject.onDidChange); - onDidInstallEvent.fire([{ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }]); + onDidInstallEvent.fire([{ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install, profileLocation: null! }]); await promise; assert.ok(!testObject.enabled); }); @@ -1822,7 +1826,7 @@ suite('RemoteInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test remote install action is enabled local workspace+ui extension', async () => { @@ -1844,7 +1848,7 @@ suite('RemoteInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test remote install action is enabled for local ui+workapace extension if can install is true', async () => { @@ -1866,7 +1870,7 @@ suite('RemoteInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test remote install action is disabled for local ui+workapace extension if can install is false', async () => { @@ -1987,7 +1991,7 @@ suite('RemoteInstallAction', () => { assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - uninstallEvent.fire({ identifier: localWorkspaceExtension.identifier }); + uninstallEvent.fire({ identifier: localWorkspaceExtension.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); @@ -2107,7 +2111,7 @@ suite('RemoteInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test remote install action is disabled if local language pack extension is uninstalled', async () => { @@ -2131,7 +2135,7 @@ suite('RemoteInstallAction', () => { assert.ok(testObject.enabled); assert.strictEqual('Install in remote', testObject.label); - uninstallEvent.fire({ identifier: languagePackExtension.identifier }); + uninstallEvent.fire({ identifier: languagePackExtension.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); }); @@ -2160,7 +2164,7 @@ suite('LocalInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install Locally', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test local install action is enabled for remote ui+workspace extension', async () => { @@ -2181,7 +2185,7 @@ suite('LocalInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install Locally', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test local install action when installing remote ui extension', async () => { @@ -2207,12 +2211,12 @@ suite('LocalInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install Locally', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); - onInstallExtension.fire({ identifier: remoteUIExtension.identifier, source: gallery }); + onInstallExtension.fire({ identifier: remoteUIExtension.identifier, source: gallery, profileLocation: null! }); assert.ok(testObject.enabled); assert.strictEqual('Installing', testObject.label); - assert.strictEqual('extension-action label install installing', testObject.class); + assert.strictEqual('extension-action label install-other-server installing', testObject.class); }); test('Test local install action when installing remote ui extension is finished', async () => { @@ -2240,16 +2244,16 @@ suite('LocalInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install Locally', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); - onInstallExtension.fire({ identifier: remoteUIExtension.identifier, source: gallery }); + onInstallExtension.fire({ identifier: remoteUIExtension.identifier, source: gallery, profileLocation: null! }); assert.ok(testObject.enabled); assert.strictEqual('Installing', testObject.label); - assert.strictEqual('extension-action label install installing', testObject.class); + assert.strictEqual('extension-action label install-other-server installing', testObject.class); const installedExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); const promise = Event.toPromise(testObject.onDidChange); - onDidInstallEvent.fire([{ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }]); + onDidInstallEvent.fire([{ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install, profileLocation: null! }]); await promise; assert.ok(!testObject.enabled); }); @@ -2274,7 +2278,7 @@ suite('LocalInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install Locally', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test local install action is disabled when extension is not set', async () => { @@ -2399,7 +2403,7 @@ suite('LocalInstallAction', () => { assert.ok(testObject.enabled); assert.strictEqual('Install Locally', testObject.label); - uninstallEvent.fire({ identifier: remoteUIExtension.identifier }); + uninstallEvent.fire({ identifier: remoteUIExtension.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); @@ -2498,7 +2502,7 @@ suite('LocalInstallAction', () => { testObject.extension = extensions[0]; assert.ok(testObject.enabled); assert.strictEqual('Install Locally', testObject.label); - assert.strictEqual('extension-action label prominent install', testObject.class); + assert.strictEqual('extension-action label prominent install-other-server', testObject.class); }); test('Test local install action is disabled if remote language pack extension is uninstalled', async () => { @@ -2522,7 +2526,7 @@ suite('LocalInstallAction', () => { assert.ok(testObject.enabled); assert.strictEqual('Install Locally', testObject.label); - uninstallEvent.fire({ identifier: languagePackExtension.identifier }); + uninstallEvent.fire({ identifier: languagePackExtension.identifier, profileLocation: null! }); assert.ok(!testObject.enabled); }); @@ -2638,7 +2642,7 @@ function createExtensionManagementService(installed: ILocalExtension[] = []): IP getInstalled: () => Promise.resolve(installed), canInstall: async (extension: IGalleryExtension) => { return true; }, installFromGallery: (extension: IGalleryExtension) => Promise.reject(new Error('not supported')), - updateMetadata: async (local: Mutable, metadata: Partial) => { + updateMetadata: async (local: Mutable, metadata: Partial, profileLocation: URI) => { local.identifier.uuid = metadata.id; local.publisherDisplayName = metadata.publisherDisplayName!; local.publisherId = metadata.publisherId!; @@ -2648,5 +2652,3 @@ function createExtensionManagementService(installed: ILocalExtension[] = []): IP async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; }, }; } - - diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts index 7ff4ea1b..ef60bf96 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { generateUuid } from 'vs/base/common/uuid'; import { ExtensionsListView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -51,6 +51,9 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { IUpdateService, State } from 'vs/platform/update/common/update'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; +import { toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; suite('ExtensionsViews Tests', () => { @@ -122,6 +125,7 @@ suite('ExtensionsViews Tests', () => { }); instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); + instantiationService.stub(IUserDataProfileService, disposableStore.add(new UserDataProfileService(toUserDataProfile('test', 'test', URI.file('foo'), URI.file('cache'))))); const reasons: { [key: string]: any } = {}; reasons[workspaceRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace }; @@ -501,9 +505,6 @@ suite('ExtensionsViews Tests', () => { }] }); - testableView.dispose(); - testableView = disposableStore.add(instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' })); - return testableView.show('search-me').then(result => { const options: IQueryOptions = queryTarget.args[0][0]; @@ -528,9 +529,6 @@ suite('ExtensionsViews Tests', () => { const queryTarget = instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...realResults)); - testableView.dispose(); - disposableStore.add(testableView = instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' })); - return testableView.show('search-me @sort:installs').then(result => { const options: IQueryOptions = queryTarget.args[0][0]; @@ -570,4 +568,3 @@ suite('ExtensionsViews Tests', () => { } }); - diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts index e42cf861..a2ce22d1 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as sinon from 'sinon'; -import * as assert from 'assert'; +import assert from 'assert'; import { generateUuid } from 'vs/base/common/uuid'; import { ExtensionState, AutoCheckUpdatesConfigurationKey, AutoUpdateConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; @@ -25,7 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestExtensionTipsService, TestSharedProcessService } from 'vs/workbench/test/electron-sandbox/workbenchTestServices'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; @@ -54,6 +54,10 @@ import { Mutable } from 'vs/base/common/types'; import { IUpdateService, State } from 'vs/platform/update/common/update'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; +import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; suite('ExtensionsWorkbenchServiceTest', () => { @@ -89,6 +93,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { stubConfiguration(); instantiationService.stub(IRemoteAgentService, RemoteAgentService); + instantiationService.stub(IUserDataProfileService, disposableStore.add(new UserDataProfileService(toUserDataProfile('test', 'test', URI.file('foo'), URI.file('cache'))))); instantiationService.stub(IWorkbenchExtensionManagementService, { onDidInstallExtensions: didInstallEvent.event, @@ -107,7 +112,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { return local; }, async canInstall() { return true; }, - getTargetPlatform: async () => getTargetPlatform(platform, arch) + getTargetPlatform: async () => getTargetPlatform(platform, arch), + async resetPinnedStateForAllUserExtensions(pinned: boolean) { } }); instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService({ @@ -374,7 +380,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const identifier = gallery.identifier; // Installing - installEvent.fire({ identifier, source: gallery }); + installEvent.fire({ identifier, source: gallery, profileLocation: null! }); const local = testObject.local; assert.strictEqual(1, local.length); const actual = local[0]; @@ -382,18 +388,18 @@ suite('ExtensionsWorkbenchServiceTest', () => { assert.strictEqual(ExtensionState.Installing, actual.state); // Installed - didInstallEvent.fire([{ identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension(gallery.name, gallery, { identifier }) }]); + didInstallEvent.fire([{ identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension(gallery.name, gallery, { identifier }), profileLocation: null! }]); assert.strictEqual(ExtensionState.Installed, actual.state); assert.strictEqual(1, testObject.local.length); testObject.uninstall(actual); // Uninstalling - uninstallEvent.fire({ identifier }); + uninstallEvent.fire({ identifier, profileLocation: null! }); assert.strictEqual(ExtensionState.Uninstalling, actual.state); // Uninstalled - didUninstallEvent.fire({ identifier }); + didUninstallEvent.fire({ identifier, profileLocation: null! }); assert.strictEqual(ExtensionState.Uninstalled, actual.state); assert.strictEqual(0, testObject.local.length); @@ -416,8 +422,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); const target = testObject.local[0]; testObject.uninstall(target); - uninstallEvent.fire({ identifier: local.identifier }); - didUninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); + didUninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(!(await testObject.canInstall(target))); }); @@ -455,11 +461,11 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extension = page.firstPage[0]; assert.strictEqual(ExtensionState.Uninstalled, extension.state); - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); const promise = Event.toPromise(testObject.onChange); // Installed - didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension(gallery.name, gallery, gallery) }]); + didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension(gallery.name, gallery, gallery), profileLocation: null! }]); await promise; }); @@ -477,7 +483,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { disposableStore.add(testObject.onChange(target)); // Installing - installEvent.fire({ identifier: gallery.identifier, source: gallery }); + installEvent.fire({ identifier: gallery.identifier, source: gallery, profileLocation: null! }); assert.ok(target.calledOnce); }); @@ -491,7 +497,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject.uninstall(testObject.local[0]); disposableStore.add(testObject.onChange(target)); - uninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(target.calledOnce); }); @@ -503,9 +509,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { const target = sinon.spy(); testObject.uninstall(testObject.local[0]); - uninstallEvent.fire({ identifier: local.identifier }); + uninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); disposableStore.add(testObject.onChange(target)); - didUninstallEvent.fire({ identifier: local.identifier }); + didUninstallEvent.fire({ identifier: local.identifier, profileLocation: null! }); assert.ok(target.calledOnce); }); @@ -719,10 +725,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionB = aLocalExtension('b'); const extensionC = aLocalExtension('c'); - instantiationService.stub(INotificationService, { - prompt(severity, message, choices, options) { - choices[0].run(); - return null!; + instantiationService.stub(IDialogService, { + prompt() { + return Promise.resolve({ result: true }); } }); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally) @@ -1018,7 +1023,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); const local = aLocalExtension('pub.a'); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - didInstallEvent.fire([{ local, identifier: local.identifier, operation: InstallOperation.Update }]); + didInstallEvent.fire([{ local, identifier: local.identifier, operation: InstallOperation.Update, profileLocation: null! }]); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const actual = await testObject.queryLocal(); assert.strictEqual(actual[0].enablementState, EnablementState.DisabledGlobally); @@ -1028,7 +1033,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); const local = aLocalExtension('pub.a'); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledWorkspace); - didInstallEvent.fire([{ local, identifier: local.identifier, operation: InstallOperation.Update }]); + didInstallEvent.fire([{ local, identifier: local.identifier, operation: InstallOperation.Update, profileLocation: null! }]); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const actual = await testObject.queryLocal(); assert.strictEqual(actual[0].enablementState, EnablementState.DisabledWorkspace); @@ -1427,10 +1432,35 @@ suite('ExtensionsWorkbenchServiceTest', () => { await testObject.updateAutoUpdateEnablementFor(testObject.local[0], false); - assert.strictEqual(testObject.local[0].local?.pinned, true); + assert.strictEqual(testObject.local[0].local?.pinned, undefined); + assert.strictEqual(testObject.local[1].local?.pinned, undefined); + + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), []); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), ['pub.a']); + }); + + test('Test disable autoupdate for extension when auto update is enabled for enabled extensions', async () => { + stubConfiguration('onlyEnabledExtensions'); + + const extension1 = aLocalExtension('a'); + const extension2 = aLocalExtension('b'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2]); + instantiationService.stub(IExtensionManagementService, 'updateMetadata', (local: Mutable, metadata: Partial) => { + local.pinned = !!metadata.pinned; + return local; + }); + testObject = await aWorkbenchService(); + + assert.strictEqual(testObject.local[0].local?.pinned, undefined); + assert.strictEqual(testObject.local[1].local?.pinned, undefined); + + await testObject.updateAutoUpdateEnablementFor(testObject.local[0], false); + + assert.strictEqual(testObject.local[0].local?.pinned, undefined); assert.strictEqual(testObject.local[1].local?.pinned, undefined); - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), []); + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), []); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), ['pub.a']); }); test('Test enable autoupdate for extension when auto update is enabled for all', async () => { @@ -1449,10 +1479,33 @@ suite('ExtensionsWorkbenchServiceTest', () => { await testObject.updateAutoUpdateEnablementFor(testObject.local[0], false); await testObject.updateAutoUpdateEnablementFor(testObject.local[0], true); + assert.strictEqual(testObject.local[0].local?.pinned, undefined); + assert.strictEqual(testObject.local[1].local?.pinned, undefined); + + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), []); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), []); + }); + + test('Test enable autoupdate for pinned extension when auto update is enabled', async () => { + const extension1 = aLocalExtension('a', undefined, { pinned: true }); + const extension2 = aLocalExtension('b'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2]); + instantiationService.stub(IExtensionManagementService, 'updateMetadata', (local: Mutable, metadata: Partial) => { + local.pinned = !!metadata.pinned; + return local; + }); + testObject = await aWorkbenchService(); + + assert.strictEqual(testObject.local[0].local?.pinned, true); + assert.strictEqual(testObject.local[1].local?.pinned, undefined); + + await testObject.updateAutoUpdateEnablementFor(testObject.local[0], true); + assert.strictEqual(testObject.local[0].local?.pinned, false); assert.strictEqual(testObject.local[1].local?.pinned, undefined); - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), []); + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), []); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), []); }); test('Test updateAutoUpdateEnablementFor throws error when auto update is disabled', async () => { @@ -1485,46 +1538,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { } }); - test('Test updateAutoUpdateEnablementFor throws error for extension id when auto update mode is onlySelectedExtensions', async () => { - stubConfiguration('onlySelectedExtensions'); - - const extension1 = aLocalExtension('a'); - const extension2 = aLocalExtension('b'); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2]); - testObject = await aWorkbenchService(); - - try { - await testObject.updateAutoUpdateEnablementFor(testObject.local[0].identifier.id, true); - assert.fail('error expected'); - } catch (error) { - // expected - } - }); - - test('Test enable autoupdate for extension when auto update is set to onlySelectedExtensions', async () => { - stubConfiguration('onlySelectedExtensions'); - - const extension1 = aLocalExtension('a', undefined, { pinned: true }); - const extension2 = aLocalExtension('b', undefined, { pinned: true }); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2]); - instantiationService.stub(IExtensionManagementService, 'updateMetadata', (local: Mutable, metadata: Partial) => { - local.pinned = !!metadata.pinned; - return local; - }); - testObject = await aWorkbenchService(); - - assert.strictEqual(testObject.local[0].local?.pinned, true); - assert.strictEqual(testObject.local[1].local?.pinned, true); - - await testObject.updateAutoUpdateEnablementFor(testObject.local[0], true); - - assert.strictEqual(testObject.local[0].local?.pinned, false); - assert.strictEqual(testObject.local[1].local?.pinned, true); - - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), ['pub.a']); - assert.equal(instantiationService.get(IConfigurationService).getValue(AutoUpdateConfigurationKey), 'onlySelectedExtensions'); - }); - test('Test enable autoupdate for extension when auto update is disabled', async () => { stubConfiguration(false); @@ -1542,15 +1555,17 @@ suite('ExtensionsWorkbenchServiceTest', () => { await testObject.updateAutoUpdateEnablementFor(testObject.local[0], true); - assert.strictEqual(testObject.local[0].local?.pinned, false); + assert.strictEqual(testObject.local[0].local?.pinned, true); assert.strictEqual(testObject.local[1].local?.pinned, true); - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), ['pub.a']); - assert.equal(instantiationService.get(IConfigurationService).getValue(AutoUpdateConfigurationKey), 'onlySelectedExtensions'); + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), ['pub.a']); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), []); }); - test('Test disable autoupdate for extension when auto update is set to onlySelectedExtensions', async () => { - stubConfiguration('onlySelectedExtensions'); + test('Test reset autoupdate extensions state when auto update is disabled', async () => { + instantiationService.stub(IDialogService, { + confirm: () => Promise.resolve({ confirmed: true }) + }); const extension1 = aLocalExtension('a', undefined, { pinned: true }); const extension2 = aLocalExtension('b', undefined, { pinned: true }); @@ -1561,101 +1576,41 @@ suite('ExtensionsWorkbenchServiceTest', () => { }); testObject = await aWorkbenchService(); - await testObject.updateAutoUpdateEnablementFor(testObject.local[0], true); await testObject.updateAutoUpdateEnablementFor(testObject.local[0], false); - assert.strictEqual(testObject.local[0].local?.pinned, true); - assert.strictEqual(testObject.local[1].local?.pinned, true); - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), []); - assert.equal(instantiationService.get(IConfigurationService).getValue(AutoUpdateConfigurationKey), false); - }); + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), []); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), ['pub.a']); - test('Test enable auto update for publisher when auto update mode is onlySelectedExtensions', async () => { - stubConfiguration('onlySelectedExtensions'); + await testObject.updateAutoUpdateValue(false); - const extension1 = aLocalExtension('a', undefined, { pinned: true }); - const extension2 = aLocalExtension('b', undefined, { pinned: true }); - const extension3 = aLocalExtension('a', { publisher: 'pub2' }, { pinned: true }); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2, extension3]); - instantiationService.stub(IExtensionManagementService, 'updateMetadata', (local: ILocalExtension, metadata: Partial) => { - local.pinned = !!metadata.pinned; - return local; - }); - testObject = await aWorkbenchService(); - - await testObject.updateAutoUpdateEnablementFor(testObject.local[0].publisher, true); - - assert.strictEqual(testObject.local[0].local?.pinned, false); - assert.strictEqual(testObject.local[1].local?.pinned, false); - assert.strictEqual(testObject.local[2].local?.pinned, true); - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), ['pub']); + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), []); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), []); }); - test('Test disable auto update for publisher when auto update mode is onlySelectedExtensions', async () => { - stubConfiguration('onlySelectedExtensions'); - - const extension1 = aLocalExtension('a', undefined, { pinned: true }); - const extension2 = aLocalExtension('b', undefined, { pinned: true }); - const extension3 = aLocalExtension('a', { publisher: 'pub2' }, { pinned: true }); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2, extension3]); - instantiationService.stub(IExtensionManagementService, 'updateMetadata', (local: ILocalExtension, metadata: Partial) => { - local.pinned = !!metadata.pinned; - return local; + test('Test reset autoupdate extensions state when auto update is enabled', async () => { + stubConfiguration(false); + instantiationService.stub(IDialogService, { + confirm: () => Promise.resolve({ confirmed: true }) }); - testObject = await aWorkbenchService(); - - await testObject.updateAutoUpdateEnablementFor(testObject.local[0].publisher, true); - await testObject.updateAutoUpdateEnablementFor(testObject.local[0].publisher, false); - - assert.strictEqual(testObject.local[0].local?.pinned, true); - assert.strictEqual(testObject.local[1].local?.pinned, true); - assert.strictEqual(testObject.local[2].local?.pinned, true); - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), []); - }); - - test('Test disable auto update for an extension when auto update for publisher is enabled and update mode is onlySelectedExtensions', async () => { - stubConfiguration('onlySelectedExtensions'); const extension1 = aLocalExtension('a', undefined, { pinned: true }); const extension2 = aLocalExtension('b', undefined, { pinned: true }); - const extension3 = aLocalExtension('a', { publisher: 'pub2' }, { pinned: true }); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2, extension3]); - instantiationService.stub(IExtensionManagementService, 'updateMetadata', (local: ILocalExtension, metadata: Partial) => { + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2]); + instantiationService.stub(IExtensionManagementService, 'updateMetadata', (local: Mutable, metadata: Partial) => { local.pinned = !!metadata.pinned; return local; }); testObject = await aWorkbenchService(); - await testObject.updateAutoUpdateEnablementFor(testObject.local[0].publisher, true); - await testObject.updateAutoUpdateEnablementFor(testObject.local[0], false); + await testObject.updateAutoUpdateEnablementFor(testObject.local[0], true); - assert.strictEqual(testObject.local[0].local?.pinned, true); - assert.strictEqual(testObject.local[1].local?.pinned, false); - assert.strictEqual(testObject.local[2].local?.pinned, true); - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), ['pub', '-pub.a']); - }); + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), ['pub.a']); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), []); - test('Test enable auto update for an extension when auto updates is enabled for publisher and disabled for extension and update mode is onlySelectedExtensions', async () => { - stubConfiguration('onlySelectedExtensions'); + await testObject.updateAutoUpdateValue(true); - const extension1 = aLocalExtension('a', undefined, { pinned: true }); - const extension2 = aLocalExtension('b', undefined, { pinned: true }); - const extension3 = aLocalExtension('a', { publisher: 'pub2' }, { pinned: true }); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extension1, extension2, extension3]); - instantiationService.stub(IExtensionManagementService, 'updateMetadata', (local: ILocalExtension, metadata: Partial) => { - local.pinned = !!metadata.pinned; - return local; - }); - testObject = await aWorkbenchService(); - - await testObject.updateAutoUpdateEnablementFor(testObject.local[0].publisher, true); - await testObject.updateAutoUpdateEnablementFor(testObject.local[0], false); - await testObject.updateAutoUpdateEnablementFor(testObject.local[0], true); - - assert.strictEqual(testObject.local[0].local?.pinned, false); - assert.strictEqual(testObject.local[1].local?.pinned, false); - assert.strictEqual(testObject.local[2].local?.pinned, true); - assert.deepStrictEqual(testObject.getSelectedExtensionsToAutoUpdate(), ['pub']); + assert.deepStrictEqual(testObject.getEnabledAutoUpdateExtensions(), []); + assert.deepStrictEqual(testObject.getDisabledAutoUpdateExtensions(), []); }); async function aWorkbenchService(): Promise { @@ -1669,13 +1624,22 @@ suite('ExtensionsWorkbenchServiceTest', () => { [AutoUpdateConfigurationKey]: autoUpdateValue ?? true, [AutoCheckUpdatesConfigurationKey]: autoCheckUpdatesValue ?? true }; + const emitter = disposableStore.add(new Emitter()); instantiationService.stub(IConfigurationService, { - onDidChangeConfiguration: () => { return undefined!; }, + onDidChangeConfiguration: emitter.event, getValue: (key?: any) => { return key ? values[key] : undefined; }, updateValue: async (key: string, value: any) => { values[key] = value; + emitter.fire({ + affectedKeys: new Set([key]), + source: ConfigurationTarget.USER, + change: { keys: [], overrides: [] }, + affectsConfiguration(configuration, overrides) { + return true; + }, + }); } }); } @@ -1740,7 +1704,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { onDidUpdateExtensionMetadata: Event.None, getInstalled: () => Promise.resolve(installed), installFromGallery: (extension: IGalleryExtension) => Promise.reject(new Error('not supported')), - updateMetadata: async (local: Mutable, metadata: Partial) => { + updateMetadata: async (local: Mutable, metadata: Partial, profileLocation: URI) => { local.identifier.uuid = metadata.id; local.publisherDisplayName = metadata.publisherDisplayName!; local.publisherId = metadata.publisherId!; @@ -1748,6 +1712,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { }, getTargetPlatform: async () => getTargetPlatform(platform, arch), async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; }, + async resetPinnedStateForAllUserExtensions(pinned: boolean) { } }; } }); diff --git a/patched-vscode/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/patched-vscode/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index 6ca5f7d4..535fbe2b 100644 --- a/patched-vscode/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -10,12 +10,10 @@ import { MenuId, MenuRegistry, IMenuItem } from 'vs/platform/actions/common/acti import { ITerminalGroupService, ITerminalService as IIntegratedTerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; import { IFileService } from 'vs/platform/files/common/files'; -import { IListService } from 'vs/platform/list/browser/listService'; import { getMultiSelectedResources, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; import { distinct } from 'vs/base/common/arrays'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -26,6 +24,8 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Registry } from 'vs/platform/registry/common/platform'; import { IExternalTerminalConfiguration, IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; const OPEN_IN_TERMINAL_COMMAND_ID = 'openInTerminal'; @@ -37,7 +37,6 @@ function registerOpenTerminalCommand(id: string, explorerKind: 'integrated' | 'e handler: async (accessor, resource: URI) => { const configurationService = accessor.get(IConfigurationService); - const editorService = accessor.get(IEditorService); const fileService = accessor.get(IFileService); const integratedTerminalService = accessor.get(IIntegratedTerminalService); const remoteAgentService = accessor.get(IRemoteAgentService); @@ -45,10 +44,9 @@ function registerOpenTerminalCommand(id: string, explorerKind: 'integrated' | 'e let externalTerminalService: IExternalTerminalService | undefined = undefined; try { externalTerminalService = accessor.get(IExternalTerminalService); - } catch { - } + } catch { } - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IEditorGroupsService), accessor.get(IExplorerService)); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService)); return fileService.resolveAll(resources.map(r => ({ resource: r }))).then(async stats => { // Always use integrated terminal when using a remote const config = configurationService.getValue(); @@ -138,11 +136,11 @@ export class ExternalTerminalContribution extends Disposable implements IWorkben MenuRegistry.appendMenuItem(MenuId.ExplorerContext, this._openInTerminalMenuItem); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, this._openInIntegratedTerminalMenuItem); - this._configurationService.onDidChangeConfiguration(e => { + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('terminal.explorerKind') || e.affectsConfiguration('terminal.external')) { this._refreshOpenInTerminalMenuItemTitle(); } - }); + })); this._refreshOpenInTerminalMenuItemTitle(); } diff --git a/patched-vscode/src/vs/workbench/contrib/externalUriOpener/test/common/externalUriOpenerService.test.ts b/patched-vscode/src/vs/workbench/contrib/externalUriOpener/test/common/externalUriOpenerService.test.ts index 842599bb..5be58d57 100644 --- a/patched-vscode/src/vs/workbench/contrib/externalUriOpener/test/common/externalUriOpenerService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/externalUriOpener/test/common/externalUriOpenerService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 066e0372..a598f8b7 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -158,8 +158,8 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa // Save As primaryActions.push(this.instantiationService.createInstance(SaveModelAsAction, model)); - // Discard - primaryActions.push(this.instantiationService.createInstance(DiscardModelAction, model)); + // Revert + primaryActions.push(this.instantiationService.createInstance(RevertModelAction, model)); // Message if (isWriteLocked) { @@ -306,12 +306,12 @@ class RetrySaveModelAction extends Action { } } -class DiscardModelAction extends Action { +class RevertModelAction extends Action { constructor( private model: ITextFileEditorModel ) { - super('workbench.files.action.discardModel', localize('discard', "Discard")); + super('workbench.files.action.revertModel', localize('revert', "Revert")); } override async run(): Promise { diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/explorerService.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/explorerService.ts index 1c67fc0f..4bc6f717 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/explorerService.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/explorerService.ts @@ -152,6 +152,7 @@ export class ExplorerService implements IExplorerService { return { sortOrder: this.config.sortOrder, lexicographicOptions: this.config.sortOrderLexicographicOptions, + reverse: this.config.sortOrderReverse, }; } @@ -521,6 +522,11 @@ export class ExplorerService implements IExplorerService { if (this.config.sortOrderLexicographicOptions !== configLexicographicOptions) { shouldRefresh = shouldRefresh || this.config.sortOrderLexicographicOptions !== undefined; } + const sortOrderReverse = configuration?.explorer?.sortOrderReverse || false; + + if (this.config.sortOrderReverse !== sortOrderReverse) { + shouldRefresh = shouldRefresh || this.config.sortOrderReverse !== undefined; + } this.config = configuration.explorer; diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 963e5c56..ca094233 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -10,7 +10,7 @@ import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/commo import { ICommandAction } from 'vs/platform/action/common/action'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { openWindowCommand, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; -import { COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, OpenEditorsDirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, OpenEditorsReadonlyEditorContext, OPEN_WITH_EXPLORER_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL, SAVE_ALL_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; +import { COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, OpenEditorsDirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, OpenEditorsReadonlyEditorContext, OPEN_WITH_EXPLORER_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL, SAVE_ALL_COMMAND_ID, OpenEditorsSelectedFileOrUntitledContext } from 'vs/workbench/contrib/files/browser/fileConstants'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -20,7 +20,7 @@ import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOS import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { Schemas } from 'vs/base/common/network'; -import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext } from 'vs/workbench/common/contextkeys'; +import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext, SelectedEditorsInGroupFileOrUntitledResourceContextKey } from 'vs/workbench/common/contextkeys'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -300,7 +300,12 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { id: REOPEN_WITH_COMMAND_ID, title: nls.localize('reopenWith', "Reopen Editor With...") }, - when: ActiveEditorAvailableEditorIdsContext + when: ContextKeyExpr.and( + // Editors with Available Choices to Open With + ActiveEditorAvailableEditorIdsContext, + // Not: editor groups + OpenEditorsGroupContext.toNegated() + ) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -413,14 +418,14 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '3_compare', order: 30, command: compareSelectedCommand, - when: ContextKeyExpr.and(ResourceContextKey.HasResource, WorkbenchListDoubleSelection, isFileOrUntitledResourceContextKey) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, WorkbenchListDoubleSelection, OpenEditorsSelectedFileOrUntitledContext) }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { - group: '3_compare', + group: '1_compare', order: 30, command: compareSelectedCommand, - when: ContextKeyExpr.and(ResourceContextKey.HasResource, TwoEditorsSelectedInGroupContext, isFileOrUntitledResourceContextKey) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, TwoEditorsSelectedInGroupContext, SelectedEditorsInGroupFileOrUntitledResourceContextKey) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/fileActions.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/fileActions.ts index 3e98384f..58a3df13 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -1110,18 +1110,32 @@ export const pasteFileHandler = async (accessor: ServicesAccessor, fileList?: Fi const configurationService = accessor.get(IConfigurationService); const uriIdentityService = accessor.get(IUriIdentityService); const dialogService = accessor.get(IDialogService); + const hostService = accessor.get(IHostService); const context = explorerService.getContext(false); const hasNativeFilesToPaste = fileList && fileList.length > 0; const confirmPasteNative = hasNativeFilesToPaste && configurationService.getValue('explorer.confirmPasteNative'); - const toPaste = await getFilesToPaste(fileList, clipboardService); + const toPaste = await getFilesToPaste(fileList, clipboardService, hostService); if (confirmPasteNative && toPaste.files.length >= 1) { const message = toPaste.files.length > 1 ? nls.localize('confirmMultiPasteNative', "Are you sure you want to paste the following {0} items?", toPaste.files.length) : nls.localize('confirmPasteNative', "Are you sure you want to paste '{0}'?", basename(toPaste.type === 'paths' ? toPaste.files[0].fsPath : toPaste.files[0].name)); - const detail = toPaste.files.length > 1 ? getFileNamesMessage(toPaste.files.map(item => toPaste.type === 'paths' ? item.path : (item as File).name)) : undefined; + const detail = toPaste.files.length > 1 ? getFileNamesMessage(toPaste.files.map(item => { + if (URI.isUri(item)) { + return item.fsPath; + } + + if (toPaste.type === 'paths') { + const path = hostService.getPathForFile(item); + if (path) { + return path; + } + } + + return item.name; + })) : undefined; const confirmation = await dialogService.confirm({ message, detail, @@ -1270,16 +1284,16 @@ type FilesToPaste = | { type: 'paths'; files: URI[] } | { type: 'data'; files: File[] }; -async function getFilesToPaste(fileList: FileList | undefined, clipboardService: IClipboardService): Promise { +async function getFilesToPaste(fileList: FileList | undefined, clipboardService: IClipboardService, hostService: IHostService): Promise { if (fileList && fileList.length > 0) { // with a `fileList` we support natively pasting file from disk from clipboard - const resources = [...fileList].filter(file => !!file.path && isAbsolute(file.path)).map(file => URI.file(file.path)); + const resources = [...fileList].map(file => hostService.getPathForFile(file)).filter(filePath => !!filePath && isAbsolute(filePath)).map((filePath) => URI.file(filePath!)); if (resources.length) { return { type: 'paths', files: resources, }; } // Support pasting files that we can't read from disk - return { type: 'data', files: [...fileList].filter(file => !file.path) }; + return { type: 'data', files: [...fileList].filter(file => !hostService.getPathForFile(file)) }; } else { // otherwise we fallback to reading resources from our clipboard service return { type: 'paths', files: resources.distinctParents(await clipboardService.readResources(), resource => resource) }; diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/fileCommands.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/fileCommands.ts index 81bf68c3..c17a5b80 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -15,7 +15,6 @@ import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, ExplorerCo import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { IListService } from 'vs/platform/list/browser/listService'; import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IFileService } from 'vs/platform/files/common/files'; @@ -25,7 +24,7 @@ import { isWeb, isWindows } from 'vs/base/common/platform'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { getResourceForCommand, getMultiSelectedResources, getOpenEditorsViewMultiSelection, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { resolveCommandsContext } from 'vs/workbench/browser/parts/editor/editorCommandsContext'; import { Schemas } from 'vs/base/common/network'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -35,13 +34,12 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { basename, joinPath, isEqual } from 'vs/base/common/resources'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { coalesce } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { isCancellationError } from 'vs/base/common/errors'; -import { toAction } from 'vs/base/common/actions'; +import { IAction, toAction } from 'vs/base/common/actions'; import { EditorOpenSource, EditorResolution } from 'vs/platform/editor/common/editor'; import { hash } from 'vs/base/common/hash'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -53,6 +51,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { RemoveRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { OpenEditorsView } from 'vs/workbench/contrib/files/browser/views/openEditorsView'; import { ExplorerView } from 'vs/workbench/contrib/files/browser/views/explorerView'; +import { IListService } from 'vs/platform/list/browser/listService'; export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => { if (Array.isArray(toOpen)) { @@ -90,11 +89,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, id: OPEN_TO_SIDE_COMMAND_ID, handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); - const listService = accessor.get(IListService); const fileService = accessor.get(IFileService); const explorerService = accessor.get(IExplorerService); - const resources = getMultiSelectedResources(resource, listService, editorService, editorGroupService, explorerService); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IEditorGroupsService), explorerService); // Set side input if (resources.length) { @@ -151,6 +148,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const textModelService = accessor.get(ITextModelService); const editorService = accessor.get(IEditorService); const fileService = accessor.get(IFileService); + const listService = accessor.get(IListService); // Register provider at first as needed let registerEditorListener = false; @@ -163,7 +161,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } // Open editor (only resources that can be handled by file service are supported) - const uri = getResourceForCommand(resource, accessor.get(IListService), editorService); + const uri = getResourceForCommand(resource, editorService, listService); if (uri && fileService.hasProvider(uri)) { const name = basename(uri); const editorLabel = nls.localize('modifiedLabel', "{0} (in file) ↔ {1}", name, name); @@ -190,9 +188,7 @@ let resourceSelectedForCompareContext: IContextKey; CommandsRegistry.registerCommand({ id: SELECT_FOR_COMPARE_COMMAND_ID, handler: (accessor, resource: URI | object) => { - const listService = accessor.get(IListService); - - globalResourceToCompare = getResourceForCommand(resource, listService, accessor.get(IEditorService)); + globalResourceToCompare = getResourceForCommand(resource, accessor.get(IEditorService), accessor.get(IListService)); if (!resourceSelectedForCompareContext) { resourceSelectedForCompareContext = ResourceSelectedForCompareContext.bindTo(accessor.get(IContextKeyService)); } @@ -204,9 +200,7 @@ CommandsRegistry.registerCommand({ id: COMPARE_SELECTED_COMMAND_ID, handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); - const explorerService = accessor.get(IExplorerService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, editorGroupService, explorerService); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IEditorGroupsService), accessor.get(IExplorerService)); if (resources.length === 2) { return editorService.openEditor({ @@ -224,9 +218,7 @@ CommandsRegistry.registerCommand({ id: COMPARE_RESOURCE_COMMAND_ID, handler: (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - const listService = accessor.get(IListService); - - const rightResource = getResourceForCommand(resource, listService, editorService); + const rightResource = getResourceForCommand(resource, editorService, accessor.get(IListService)); if (globalResourceToCompare && rightResource) { editorService.openEditor({ original: { resource: globalResourceToCompare }, @@ -328,7 +320,9 @@ CommandsRegistry.registerCommand({ const viewService = accessor.get(IViewsService); const contextService = accessor.get(IWorkspaceContextService); const explorerService = accessor.get(IExplorerService); - const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); + const editorService = accessor.get(IEditorService); + const listService = accessor.get(IListService); + const uri = getResourceForCommand(resource, editorService, listService); if (uri && contextService.isInsideWorkspace(uri)) { const explorerView = await viewService.openView(VIEW_ID, false); @@ -356,8 +350,8 @@ CommandsRegistry.registerCommand({ id: OPEN_WITH_EXPLORER_COMMAND_ID, handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - - const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); + const listService = accessor.get(IListService); + const uri = getResourceForCommand(resource, editorService, listService); if (uri) { return editorService.openEditor({ resource: uri, options: { override: EditorResolution.PICK, source: EditorOpenSource.USER } }); } @@ -369,13 +363,12 @@ CommandsRegistry.registerCommand({ // Save / Save As / Save All / Revert async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise { - const listService = accessor.get(IListService); const editorGroupService = accessor.get(IEditorGroupsService); const codeEditorService = accessor.get(ICodeEditorService); const textFileService = accessor.get(ITextFileService); // Retrieve selected or active editor - let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService); + let editors = getOpenEditorsViewMultiSelection(accessor); if (!editors) { const activeGroup = editorGroupService.activeGroup; if (activeGroup.activeEditor) { @@ -451,16 +444,17 @@ async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentif await editorService.save(editors, options); } catch (error) { if (!isCancellationError(error)) { + const actions: IAction[] = [toAction({ id: 'workbench.action.files.saveEditors', label: nls.localize('retry', "Retry"), run: () => instantiationService.invokeFunction(accessor => doSaveEditors(accessor, editors, options)) })]; + const editorsToRevert = editors.filter(({ editor }) => !editor.hasCapability(EditorInputCapabilities.Untitled) /* all except untitled to prevent unexpected data-loss */); + if (editorsToRevert.length > 0) { + actions.push(toAction({ id: 'workbench.action.files.revertEditors', label: editorsToRevert.length > 1 ? nls.localize('revertAll', "Revert All") : nls.localize('revert', "Revert"), run: () => editorService.revert(editorsToRevert) })); + } + notificationService.notify({ id: editors.map(({ editor }) => hash(editor.resource?.toString())).join(), // ensure unique notification ID per set of editor severity: Severity.Error, message: nls.localize({ key: 'genericSaveError', comment: ['{0} is the resource that failed to save and {1} the error message'] }, "Failed to save '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)), - actions: { - primary: [ - toAction({ id: 'workbench.action.files.saveEditors', label: nls.localize('retry', "Retry"), run: () => instantiationService.invokeFunction(accessor => doSaveEditors(accessor, editors, options)) }), - toAction({ id: 'workbench.action.files.revertEditors', label: nls.localize('discard', "Discard"), run: () => editorService.revert(editors) }) - ] - } + actions: { primary: actions } }); } } @@ -512,15 +506,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ CommandsRegistry.registerCommand({ id: SAVE_ALL_IN_GROUP_COMMAND_ID, handler: (accessor, _: URI | object, editorContext: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); - const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService)); + const resolvedContext = resolveCommandsContext([editorContext], accessor.get(IEditorService), editorGroupsService, accessor.get(IListService)); let groups: readonly IEditorGroup[] | undefined = undefined; - if (!contexts.length) { - groups = editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); + if (!resolvedContext.groupedEditors.length) { + groups = editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); } else { - groups = coalesce(contexts.map(context => editorGroupService.getGroup(context.groupId))); + groups = resolvedContext.groupedEditors.map(({ group }) => group); } return saveDirtyEditorsOfGroups(accessor, groups, { reason: SaveReason.EXPLICIT }); @@ -540,13 +534,11 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: REVERT_FILE_COMMAND_ID, handler: async accessor => { - const notificationService = accessor.get(INotificationService); - const listService = accessor.get(IListService); const editorGroupService = accessor.get(IEditorGroupsService); const editorService = accessor.get(IEditorService); // Retrieve selected or active editor - let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService); + let editors = getOpenEditorsViewMultiSelection(accessor); if (!editors) { const activeGroup = editorGroupService.activeGroup; if (activeGroup.activeEditor) { @@ -561,6 +553,7 @@ CommandsRegistry.registerCommand({ try { await editorService.revert(editors.filter(({ editor }) => !editor.hasCapability(EditorInputCapabilities.Untitled) /* all except untitled */), { force: true }); } catch (error) { + const notificationService = accessor.get(INotificationService); notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false))); } } @@ -569,7 +562,6 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: REMOVE_ROOT_FOLDER_COMMAND_ID, handler: (accessor, resource: URI | object) => { - const workspaceEditingService = accessor.get(IWorkspaceEditingService); const contextService = accessor.get(IWorkspaceContextService); const uriIdentityService = accessor.get(IUriIdentityService); const workspace = contextService.getWorkspace(); @@ -583,6 +575,7 @@ CommandsRegistry.registerCommand({ return commandService.executeCommand(RemoveRootFolderAction.ID); } + const workspaceEditingService = accessor.get(IWorkspaceEditingService); return workspaceEditingService.removeFolders(resources); } }); diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/fileConstants.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/fileConstants.ts index c38f9987..555cff9f 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/fileConstants.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/fileConstants.ts @@ -35,6 +35,7 @@ export const SAVE_FILES_COMMAND_ID = 'workbench.action.files.saveFiles'; export const OpenEditorsGroupContext = new RawContextKey('groupFocusedInOpenEditors', false); export const OpenEditorsDirtyEditorContext = new RawContextKey('dirtyEditorFocusedInOpenEditors', false); export const OpenEditorsReadonlyEditorContext = new RawContextKey('readonlyEditorFocusedInOpenEditors', false); +export const OpenEditorsSelectedFileOrUntitledContext = new RawContextKey('openEditorsSelectedFileOrUntitled', true); export const ResourceSelectedForCompareContext = new RawContextKey('resourceSelectedForCompare', false); export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/fileImportExport.ts index 86f4f228..4f3a858a 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/fileImportExport.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/fileImportExport.ts @@ -564,7 +564,8 @@ export class ExternalFileImport { }); // if we only add one file, just open it directly - if (resourceFileEdits.length === 1) { + const autoOpen = this.configurationService.getValue().explorer.autoOpenDroppedFile; + if (autoOpen && resourceFileEdits.length === 1) { const item = this.explorerService.findClosest(resourceFileEdits[0].newResource!); if (item && !item.isDirectory) { this.editorService.openEditor({ resource: item.resource, options: { pinned: true } }); diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/files.contribution.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/files.contribution.ts index 4af58b0b..09772165 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -23,7 +23,7 @@ import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/ import { ILabelService } from 'vs/platform/label/common/label'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExplorerService, UNDO_REDO_SOURCE } from 'vs/workbench/contrib/files/browser/explorerService'; -import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding'; +import { GUESSABLE_ENCODINGS, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding'; import { Schemas } from 'vs/base/common/network'; import { WorkspaceWatcher } from 'vs/workbench/contrib/files/browser/workspaceWatcher'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; @@ -202,6 +202,17 @@ configurationRegistry.registerConfiguration({ 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only {0} is respected.", '`#files.encoding#`'), 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, + 'files.candidateGuessEncodings': { + 'type': 'array', + 'items': { + 'type': 'string', + 'enum': Object.keys(GUESSABLE_ENCODINGS), + 'enumDescriptions': Object.keys(GUESSABLE_ENCODINGS).map(key => GUESSABLE_ENCODINGS[key].labelLong) + }, + 'default': [], + 'markdownDescription': nls.localize('candidateGuessEncodings', "List of character set encodings that the editor should attempt to guess in the order they are listed. In case it cannot be determined, {0} is respected", '`#files.encoding#`'), + 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE + }, 'files.eol': { 'type': 'string', 'enum': [ @@ -270,13 +281,13 @@ configurationRegistry.registerConfiguration({ 'files.autoSaveWorkspaceFilesOnly': { 'type': 'boolean', 'default': false, - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveWorkspaceFilesOnly' }, "When enabled, will limit [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors to files that are inside the opened workspace. Only applies when `#files.autoSave#` is enabled."), + 'markdownDescription': nls.localize('autoSaveWorkspaceFilesOnly', "When enabled, will limit [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors to files that are inside the opened workspace. Only applies when {0} is enabled.", '`#files.autoSave#`'), scope: ConfigurationScope.LANGUAGE_OVERRIDABLE }, 'files.autoSaveWhenNoErrors': { 'type': 'boolean', 'default': false, - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveWhenNoErrors' }, "When enabled, will limit [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors to files that have no errors reported in them at the time the auto save is triggered. Only applies when `#files.autoSave#` is enabled."), + 'markdownDescription': nls.localize('autoSaveWhenNoErrors', "When enabled, will limit [auto save](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save) of editors to files that have no errors reported in them at the time the auto save is triggered. Only applies when {0} is enabled.", '`#files.autoSave#`'), scope: ConfigurationScope.LANGUAGE_OVERRIDABLE }, 'files.watcherExclude': { @@ -522,6 +533,11 @@ configurationRegistry.registerConfiguration({ ], 'description': nls.localize('sortOrderLexicographicOptions', "Controls the lexicographic sorting of file and folder names in the Explorer.") }, + 'explorer.sortOrderReverse': { + 'type': 'boolean', + 'description': nls.localize('sortOrderReverse', "Controls whether the file and folder sort order, should be reversed."), + 'default': false, + }, 'explorer.decorations.colors': { type: 'boolean', description: nls.localize('explorer.decorations.colors', "Controls whether file decorations should use colors."), @@ -543,6 +559,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('explorer.incrementalNaming', "Controls which naming strategy to use when giving a new name to a duplicated Explorer item on paste."), default: 'simple' }, + 'explorer.autoOpenDroppedFile': { + 'type': 'boolean', + 'description': nls.localize('autoOpenDroppedFile', "Controls whether the Explorer should automatically open a file when it is dropped into the explorer"), + 'default': true + }, 'explorer.compactFolders': { 'type': 'boolean', 'description': nls.localize('compressSingleChildFolders', "Controls whether the Explorer should render folders in a compact form. In such a form, single child folders will be compressed in a combined tree element. Useful for Java package structures, for example."), diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/files.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/files.ts index f1dbd239..2d5683ec 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/files.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/files.ts @@ -14,7 +14,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditableData } from 'vs/workbench/common/views'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; import { ProgressLocation } from 'vs/platform/progress/common/progress'; import { isActiveElement } from 'vs/base/browser/dom'; @@ -90,9 +90,9 @@ function getFocus(listService: IListService): unknown | undefined { // Commands can get executed from a command palette, from a context menu or from some list using a keybinding // To cover all these cases we need to properly compute the resource on which the command is being executed -export function getResourceForCommand(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): URI | undefined { - if (URI.isUri(resource)) { - return resource; +export function getResourceForCommand(commandArg: unknown, editorService: IEditorService, listService: IListService): URI | undefined { + if (URI.isUri(commandArg)) { + return commandArg; } const focus = getFocus(listService); @@ -105,7 +105,7 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe return EditorResourceAccessor.getOriginalUri(editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY }); } -export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService, editorGroupService: IEditorGroupsService, explorerService: IExplorerService): Array { +export function getMultiSelectedResources(commandArg: unknown, listService: IListService, editorSerice: IEditorService, editorGroupService: IEditorGroupsService, explorerService: IExplorerService): Array { const list = listService.lastFocusedList; const element = list?.getHTMLElement(); if (element && isActiveElement(element)) { @@ -124,36 +124,45 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li const focusedElements = list.getFocusedElements(); const focus = focusedElements.length ? focusedElements[0] : undefined; let mainUriStr: string | undefined = undefined; - if (URI.isUri(resource)) { - mainUriStr = resource.toString(); + if (URI.isUri(commandArg)) { + mainUriStr = commandArg.toString(); } else if (focus instanceof OpenEditor) { const focusedResource = focus.getResource(); mainUriStr = focusedResource ? focusedResource.toString() : undefined; } // We only respect the selection if it contains the main element. - if (selection.some(s => s.toString() === mainUriStr)) { + const mainIndex = selection.findIndex(s => s.toString() === mainUriStr); + if (mainIndex !== -1) { + // Move the main resource to the front of the selection. + const mainResource = selection[mainIndex]; + selection.splice(mainIndex, 1); + selection.unshift(mainResource); return selection; } } } - // Check for tabs multiselect. + // Check for tabs multiselect const activeGroup = editorGroupService.activeGroup; const selection = activeGroup.selectedEditors; - if (selection.length > 1 && URI.isUri(resource)) { + if (selection.length > 1 && URI.isUri(commandArg)) { // If the resource is part of the tabs selection, return all selected tabs/resources. // It's possible that multiple tabs are selected but the action was applied to a resource that is not part of the selection. - if (selection.some(e => e.matches({ resource }))) { + const mainEditorSelectionIndex = selection.findIndex(e => e.matches({ resource: commandArg })); + if (mainEditorSelectionIndex !== -1) { + const mainEditor = selection[mainEditorSelectionIndex]; + selection.splice(mainEditorSelectionIndex, 1); + selection.unshift(mainEditor); return selection.map(editor => EditorResourceAccessor.getOriginalUri(editor)).filter(uri => !!uri); } } - const result = getResourceForCommand(resource, listService, editorService); + const result = getResourceForCommand(commandArg, editorSerice, listService); return !!result ? [result] : []; } -export function getOpenEditorsViewMultiSelection(listService: IListService, editorGroupService: IEditorGroupsService): Array | undefined { - const list = listService.lastFocusedList; +export function getOpenEditorsViewMultiSelection(accessor: ServicesAccessor): Array | undefined { + const list = accessor.get(IListService).lastFocusedList; const element = list?.getHTMLElement(); if (element && isActiveElement(element)) { // Open editors view diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/views/explorerView.ts index a9404855..940bfb82 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -660,7 +660,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.setContextKeys(stat); if (stat) { - const enableTrash = this.configurationService.getValue().files.enableTrash; + const enableTrash = Boolean(this.configurationService.getValue().files?.enableTrash); const hasCapability = this.fileService.hasCapability(stat.resource, FileSystemProviderCapabilities.Trash); this.resourceMoveableToTrash.set(enableTrash && hasCapability); } else { diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index bda9eb9b..c568bbdb 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -880,6 +880,10 @@ export class FileSorter implements ITreeSorter { const sortOrder = this.explorerService.sortOrderConfiguration.sortOrder; const lexicographicOptions = this.explorerService.sortOrderConfiguration.lexicographicOptions; + const reverse = this.explorerService.sortOrderConfiguration.reverse; + if (reverse) { + [statA, statB] = [statB, statA]; + } let compareFileNames; let compareFileExtensions; diff --git a/patched-vscode/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/patched-vscode/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 0fd22533..f7560f5a 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -18,7 +18,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration, OpenEditor } from 'vs/workbench/contrib/files/common/files'; import { CloseAllEditorsAction, CloseEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; -import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { asCssVariable, badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; @@ -28,7 +28,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DisposableMap, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { MenuId, Action2, registerAction2, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { OpenEditorsDirtyEditorContext, OpenEditorsGroupContext, OpenEditorsReadonlyEditorContext, SAVE_ALL_LABEL, SAVE_ALL_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; +import { OpenEditorsDirtyEditorContext, OpenEditorsGroupContext, OpenEditorsReadonlyEditorContext, SAVE_ALL_LABEL, SAVE_ALL_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID, OpenEditorsSelectedFileOrUntitledContext } from 'vs/workbench/contrib/files/browser/fileConstants'; import { ResourceContextKey, MultipleEditorGroupsContext } from 'vs/workbench/common/contextkeys'; import { CodeDataTransfers, containsDragType } from 'vs/platform/dnd/browser/dnd'; import { ResourcesDropHandler, fillEditorsDragData } from 'vs/workbench/browser/dnd'; @@ -55,6 +55,7 @@ import { ILocalizedString } from 'vs/platform/action/common/action'; import { mainWindow } from 'vs/base/browser/window'; import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IFileService } from 'vs/platform/files/common/files'; const $ = dom.$; @@ -74,10 +75,6 @@ export class OpenEditorsView extends ViewPane { private needsRefresh = false; private elements: (OpenEditor | IEditorGroup)[] = []; private sortOrder: 'editorOrder' | 'alphabetical' | 'fullPath'; - private resourceContext!: ResourceContextKey; - private groupFocusedContext!: IContextKey; - private dirtyEditorFocusedContext!: IContextKey; - private readonlyEditorFocusedContext!: IContextKey; private blockFocusActiveEditorTracking = false; constructor( @@ -95,6 +92,7 @@ export class OpenEditorsView extends ViewPane { @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IOpenerService openerService: IOpenerService, + @IFileService private readonly fileService: IFileService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); @@ -245,32 +243,8 @@ export class OpenEditorsView extends ViewPane { this.updateSize(); - // Bind context keys - OpenEditorsFocusedContext.bindTo(this.list.contextKeyService); - ExplorerFocusedContext.bindTo(this.list.contextKeyService); - - this.resourceContext = this.instantiationService.createInstance(ResourceContextKey); - this._register(this.resourceContext); - this.groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService); - this.dirtyEditorFocusedContext = OpenEditorsDirtyEditorContext.bindTo(this.contextKeyService); - this.readonlyEditorFocusedContext = OpenEditorsReadonlyEditorContext.bindTo(this.contextKeyService); - + this.handleContextKeys(); this._register(this.list.onContextMenu(e => this.onListContextMenu(e))); - this.list.onDidChangeFocus(e => { - this.resourceContext.reset(); - this.groupFocusedContext.reset(); - this.dirtyEditorFocusedContext.reset(); - this.readonlyEditorFocusedContext.reset(); - const element = e.elements.length ? e.elements[0] : undefined; - if (element instanceof OpenEditor) { - const resource = element.getResource(); - this.dirtyEditorFocusedContext.set(element.editor.isDirty() && !element.editor.isSaving()); - this.readonlyEditorFocusedContext.set(!!element.editor.isReadonly()); - this.resourceContext.set(resource ?? null); - } else if (!!element) { - this.groupFocusedContext.set(true); - } - }); // Open when selecting via keyboard this._register(this.list.onMouseMiddleClick(e => { @@ -318,6 +292,52 @@ export class OpenEditorsView extends ViewPane { })); } + private handleContextKeys() { + if (!this.list) { + return; + } + + // Bind context keys + OpenEditorsFocusedContext.bindTo(this.list.contextKeyService); + ExplorerFocusedContext.bindTo(this.list.contextKeyService); + + const groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService); + const dirtyEditorFocusedContext = OpenEditorsDirtyEditorContext.bindTo(this.contextKeyService); + const readonlyEditorFocusedContext = OpenEditorsReadonlyEditorContext.bindTo(this.contextKeyService); + const openEditorsSelectedFileOrUntitledContext = OpenEditorsSelectedFileOrUntitledContext.bindTo(this.contextKeyService); + + const resourceContext = this.instantiationService.createInstance(ResourceContextKey); + this._register(resourceContext); + + this._register(this.list.onDidChangeFocus(e => { + resourceContext.reset(); + groupFocusedContext.reset(); + dirtyEditorFocusedContext.reset(); + readonlyEditorFocusedContext.reset(); + + const element = e.elements.length ? e.elements[0] : undefined; + if (element instanceof OpenEditor) { + const resource = element.getResource(); + dirtyEditorFocusedContext.set(element.editor.isDirty() && !element.editor.isSaving()); + readonlyEditorFocusedContext.set(!!element.editor.isReadonly()); + resourceContext.set(resource ?? null); + } else if (!!element) { + groupFocusedContext.set(true); + } + })); + + this._register(this.list.onDidChangeSelection(e => { + const selectedAreFileOrUntitled = e.elements.every(e => { + if (e instanceof OpenEditor) { + const resource = e.getResource(); + return resource && (resource.scheme === Schemas.untitled || this.fileService.hasProvider(resource)); + } + return false; + }); + openEditorsSelectedFileOrUntitledContext.set(selectedAreFileOrUntitled); + })); + } + override focus(): void { super.focus(); diff --git a/patched-vscode/src/vs/workbench/contrib/files/common/files.ts b/patched-vscode/src/vs/workbench/contrib/files/common/files.ts index edf32a0e..bf28812f 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/common/files.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/common/files.ts @@ -97,6 +97,7 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb expandSingleFolderWorkspaces: boolean; sortOrder: SortOrder; sortOrderLexicographicOptions: LexicographicOptions; + sortOrderReverse: boolean; decorations: { colors: boolean; badges: boolean; @@ -108,6 +109,7 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb expand: boolean; patterns: { [parent: string]: string }; }; + autoOpenDroppedFile: boolean; }; editor: IEditorOptions; } @@ -142,6 +144,7 @@ export const enum LexicographicOptions { export interface ISortOrderConfiguration { sortOrder: SortOrder; lexicographicOptions: LexicographicOptions; + reverse: boolean; } export class TextFileContentProvider extends Disposable implements ITextModelContentProvider { diff --git a/patched-vscode/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/patched-vscode/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts index 8cb362e4..dc0ae482 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts @@ -14,7 +14,6 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { getMultiSelectedResources, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; -import { IListService } from 'vs/platform/list/browser/listService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { revealResourcesInOS } from 'vs/workbench/contrib/files/electron-sandbox/fileCommands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; @@ -22,6 +21,7 @@ import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; import { appendToCommandPalette, appendEditorTitleContextMenuItem } from 'vs/workbench/contrib/files/browser/fileActions.contribution'; import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IListService } from 'vs/platform/list/browser/listService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/patched-vscode/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index f104ba76..75b57831 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerFileNestingTrie.test.ts b/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerFileNestingTrie.test.ts index 2c08f73b..66481ab3 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerFileNestingTrie.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerFileNestingTrie.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { PreTrie, ExplorerFileNestingTrie, SufTrie } from 'vs/workbench/contrib/files/common/explorerFileNestingTrie'; -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const fakeFilenameAttributes = { dirname: 'mydir', basename: '', extname: '' }; diff --git a/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts b/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts index 938fed84..13870699 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { isLinux, isWindows, OS } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { join } from 'vs/base/common/path'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts b/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts index cea1d9c7..ea30440f 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Emitter } from 'vs/base/common/event'; import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from 'vs/base/test/common/utils'; import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileActions.test.ts b/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileActions.test.ts index e1e8db24..f5070edb 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileActions.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileActions.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { incrementFileName } from 'vs/workbench/contrib/files/browser/fileActions'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index ef6adefc..1675b2d4 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from 'vs/base/test/common/utils'; import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts b/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts index ca865ea0..94f9a716 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/patched-vscode/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts b/patched-vscode/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index 75e6c3b5..f3d6c8fb 100644 --- a/patched-vscode/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 4ab19a40..5e70e522 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { IMenuItem, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; -import { INLINE_CHAT_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, INLINE_CHAT_ID, MENU_INLINE_CHAT_CONTENT_STATUS, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -19,6 +19,12 @@ import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browse import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { InlineChatEnabler, InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { CancelAction, SubmitAction } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; +import { localize } from 'vs/nls'; +import { CONTEXT_CHAT_INPUT_HAS_TEXT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InlineChatAccessibilityHelp } from 'vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp'; +import { InlineChatExansionContextKey, InlineChatExpandLineAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine'; // --- browser @@ -28,6 +34,60 @@ registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, Instant registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors +registerEditorContribution(InlineChatExansionContextKey.Id, InlineChatExansionContextKey, EditorContributionInstantiation.BeforeFirstInteraction); +registerAction2(InlineChatExpandLineAction); + +// --- MENU special --- + +const editActionMenuItem: IMenuItem = { + group: '0_main', + order: 0, + command: { + id: SubmitAction.ID, + title: localize('send.edit', "Edit Code"), + }, + when: ContextKeyExpr.and( + CONTEXT_CHAT_INPUT_HAS_TEXT, + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), + CTX_INLINE_CHAT_EDITING + ), +}; + +const generateActionMenuItem: IMenuItem = { + group: '0_main', + order: 0, + command: { + id: SubmitAction.ID, + title: localize('send.generate', "Generate"), + }, + when: ContextKeyExpr.and( + CONTEXT_CHAT_INPUT_HAS_TEXT, + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), + CTX_INLINE_CHAT_EDITING.toNegated() + ), +}; + +MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_CONTENT_STATUS, editActionMenuItem); +MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_CONTENT_STATUS, generateActionMenuItem); +MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_WIDGET_STATUS, editActionMenuItem); +MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_WIDGET_STATUS, generateActionMenuItem); + +const cancelActionMenuItem: IMenuItem = { + group: '0_main', + order: 0, + command: { + id: CancelAction.ID, + title: localize('cancel', "Stop Request"), + shortTitle: localize('cancelShort', "Stop"), + }, + when: ContextKeyExpr.and( + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, + ), +}; + +MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_WIDGET_STATUS, cancelActionMenuItem); + +// --- actions --- registerAction2(InlineChatActions.StartSessionAction); registerAction2(InlineChatActions.CloseAction); @@ -36,7 +96,6 @@ registerAction2(InlineChatActions.UnstashSessionAction); registerAction2(InlineChatActions.DiscardHunkAction); registerAction2(InlineChatActions.DiscardAction); registerAction2(InlineChatActions.RerunAction); -registerAction2(InlineChatActions.CancelSessionAction); registerAction2(InlineChatActions.MoveToNextHunk); registerAction2(InlineChatActions.MoveToPreviousHunk); @@ -48,11 +107,9 @@ registerAction2(InlineChatActions.ViewInChatAction); registerAction2(InlineChatActions.ToggleDiffForChange); registerAction2(InlineChatActions.AcceptChanges); -registerAction2(InlineChatActions.CopyRecordings); - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatNotebookContribution, LifecyclePhase.Restored); registerWorkbenchContribution2(InlineChatEnabler.Id, InlineChatEnabler, WorkbenchPhase.AfterRestored); - AccessibleViewRegistry.register(new InlineChatAccessibleView()); +AccessibleViewRegistry.register(new InlineChatAccessibilityHelp()); diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts index a2348928..2dc472af 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts @@ -9,13 +9,14 @@ import { AccessibleViewType } from 'vs/platform/accessibility/browser/accessible import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { getChatAccessibilityHelpProvider } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp'; -import { CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CONTEXT_CHAT_INPUT_HAS_FOCUS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; export class InlineChatAccessibilityHelp implements IAccessibleViewImplentation { readonly priority = 106; readonly name = 'inlineChat'; readonly type = AccessibleViewType.Help; - readonly when = ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED); + readonly when = ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CONTEXT_CHAT_INPUT_HAS_FOCUS); getProvider(accessor: ServicesAccessor) { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (!codeEditor) { @@ -23,5 +24,4 @@ export class InlineChatAccessibilityHelp implements IAccessibleViewImplentation } return getChatAccessibilityHelpProvider(accessor, codeEditor, 'inlineChat'); } - dispose() { } } diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts index 91a719e1..964741b6 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts @@ -7,10 +7,12 @@ import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/in import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; export class InlineChatAccessibleView implements IAccessibleViewImplentation { readonly priority = 100; @@ -32,15 +34,12 @@ export class InlineChatAccessibleView implements IAccessibleViewImplentation { if (!responseContent) { return; } - return { - id: AccessibleViewProviderId.InlineChat, - verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, - provideContent(): string { return responseContent; }, - onClose() { - controller.focus(); - }, - options: { type: AccessibleViewType.View } - }; + return new AccessibleContentProvider( + AccessibleViewProviderId.InlineChat, + { type: AccessibleViewType.View }, + () => renderMarkdownAsPlaintext(new MarkdownString(responseContent), true), + () => controller.focus(), + AccessibilityVerbositySettingId.InlineChat + ); } - dispose() { } } diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index b7c33b6d..22d6a83f 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -11,25 +11,22 @@ import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/em import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_HAS_STASHED_SESSION, ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET, ACTION_TOGGLE_DIFF, ACTION_REGENERATE_RESPONSE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, MENU_INLINE_CHAT_CONTENT_STATUS, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options } from 'vs/platform/actions/common/actions'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { fromNow } from 'vs/base/common/date'; -import { IInlineChatSessionService, Recording } from './inlineChatSessionService'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { HunkInformation } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -80,7 +77,7 @@ export class StartSessionAction extends EditorAction2 { let options: InlineChatRunOptions | undefined; const arg = _args[0]; - if (arg && InlineChatRunOptions.isInteractiveEditorOptions(arg)) { + if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) { options = arg; } InlineChatController.get(editor)?.run({ ...options }); @@ -228,27 +225,6 @@ export class FocusInlineChat extends EditorAction2 { } } -export class DiscardHunkAction extends AbstractInlineChatAction { - - constructor() { - super({ - id: 'inlineChat.discardHunkChange', - title: localize('discard', 'Discard'), - icon: Codicon.clearAll, - precondition: CTX_INLINE_CHAT_VISIBLE, - menu: { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty), CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live)), - group: '0_main', - order: 3 - } - }); - } - - async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { - return ctrl.discardHunk(); - } -} export class DiscardAction extends AbstractInlineChatAction { @@ -271,33 +247,6 @@ export class DiscardAction extends AbstractInlineChatAction { } } -export class ToggleDiffForChange extends AbstractInlineChatAction { - - constructor() { - super({ - id: ACTION_TOGGLE_DIFF, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF), - title: localize2('showChanges', 'Toggle Changes'), - icon: Codicon.diffSingle, - toggled: { - condition: CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, - }, - menu: [ - { - id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: '1_main', - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF), - order: 10, - } - ] - }); - } - - override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController): void { - ctrl.toggleDiff(); - } -} - export class AcceptChanges extends AbstractInlineChatAction { constructor() { @@ -312,47 +261,101 @@ export class AcceptChanges extends AbstractInlineChatAction { weight: KeybindingWeight.WorkbenchContrib + 10, primary: KeyMod.CtrlCmd | KeyCode.Enter, }], - menu: { - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), + menu: [{ id: MENU_INLINE_CHAT_WIDGET_STATUS, group: '0_main', - order: 0 - } + order: 1, + when: ContextKeyExpr.and( + CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), + CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits) + ), + }, { + id: MENU_INLINE_CHAT_ZONE, + group: 'navigation', + order: 1, + }] }); } - override async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController): Promise { - ctrl.acceptHunk(); + override async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise { + ctrl.acceptHunk(hunk); } } -export class CancelSessionAction extends AbstractInlineChatAction { +export class DiscardHunkAction extends AbstractInlineChatAction { constructor() { super({ - id: 'inlineChat.cancel', - title: localize('cancel', 'Cancel'), - icon: Codicon.clearAll, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Preview)), + id: ACTION_DISCARD_CHANGES, + title: localize('discard', 'Discard'), + icon: Codicon.chromeClose, + precondition: CTX_INLINE_CHAT_VISIBLE, + menu: [{ + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: '0_main', + order: 2, + when: ContextKeyExpr.and( + CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), + CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits), + CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live) + ), + }, { + id: MENU_INLINE_CHAT_ZONE, + group: 'navigation', + order: 2 + }], keybinding: { - weight: KeybindingWeight.EditorContrib - 1, - primary: KeyCode.Escape - }, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.Escape, + when: CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits) + } + }); + } + + async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise { + return ctrl.discardHunk(hunk); + } +} + +export class RerunAction extends AbstractInlineChatAction { + constructor() { + super({ + id: ACTION_REGENERATE_RESPONSE, + title: localize2('chat.rerun.label', "Rerun Request"), + shortTitle: localize('rerun', 'Rerun'), + f1: false, + icon: Codicon.refresh, + precondition: CTX_INLINE_CHAT_VISIBLE, menu: { id: MENU_INLINE_CHAT_WIDGET_STATUS, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Preview), CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), group: '0_main', - order: 3 + order: 5, + when: ContextKeyExpr.and( + CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), + CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.None) + ) + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyR } }); } - async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { - ctrl.cancelSession(); + override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { + const chatService = accessor.get(IChatService); + const model = ctrl.chatWidget.viewModel?.model; + + const lastRequest = model?.getRequests().at(-1); + if (lastRequest) { + await chatService.resendRequest(lastRequest, { noCommandDetection: false, attempt: lastRequest.attempt + 1, location: ctrl.chatWidget.location }); + } } } - export class CloseAction extends AbstractInlineChatAction { constructor() { @@ -362,15 +365,25 @@ export class CloseAction extends AbstractInlineChatAction { icon: Codicon.close, precondition: CTX_INLINE_CHAT_VISIBLE, keybinding: { - weight: KeybindingWeight.EditorContrib - 1, + weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Escape, - when: CTX_INLINE_CHAT_USER_DID_EDIT.negate() }, - menu: { - id: MENU_INLINE_CHAT_WIDGET, - group: 'navigation', + menu: [{ + id: MENU_INLINE_CHAT_CONTENT_STATUS, + group: '0_main', order: 10, - } + }, { + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and( + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), + ContextKeyExpr.or( + CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages), + CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Preview) + ) + ), + }] }); } @@ -387,6 +400,11 @@ export class ConfigureInlineChatAction extends AbstractInlineChatAction { icon: Codicon.settingsGear, precondition: CTX_INLINE_CHAT_VISIBLE, f1: true, + menu: { + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: 'zzz', + order: 5 + } }); } @@ -435,42 +453,6 @@ export class MoveToPreviousHunk extends AbstractInlineChatAction { } } -export class CopyRecordings extends AbstractInlineChatAction { - - constructor() { - super({ - id: 'inlineChat.copyRecordings', - f1: true, - title: localize2('copyRecordings', "(Developer) Write Exchange to Clipboard") - }); - } - - override async runInlineChatCommand(accessor: ServicesAccessor): Promise { - - const clipboardService = accessor.get(IClipboardService); - const quickPickService = accessor.get(IQuickInputService); - const ieSessionService = accessor.get(IInlineChatSessionService); - - const recordings = ieSessionService.recordings().filter(r => r.exchanges.length > 0); - if (recordings.length === 0) { - return; - } - - const picks: (IQuickPickItem & { rec: Recording })[] = recordings.map(rec => { - return { - rec, - label: localize('label', "'{0}' and {1} follow ups ({2})", rec.exchanges[0].prompt, rec.exchanges.length - 1, fromNow(rec.when, true)), - tooltip: rec.exchanges.map(ex => ex.prompt).join('\n'), - }; - }); - - const pick = await quickPickService.pick(picks, { canPickMany: false }); - if (pick) { - clipboardService.writeText(JSON.stringify(pick.rec, undefined, 2)); - } - } -} - export class ViewInChatAction extends AbstractInlineChatAction { constructor() { super({ @@ -478,10 +460,25 @@ export class ViewInChatAction extends AbstractInlineChatAction { title: localize('viewInChat', 'View in Chat'), icon: Codicon.commentDiscussion, precondition: CTX_INLINE_CHAT_VISIBLE, - menu: { - id: MENU_INLINE_CHAT_WIDGET, - group: 'navigation', - order: 5 + menu: [{ + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: 'more', + order: 1, + when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.Messages) + }, { + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and( + CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages), + CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate() + ) + }], + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + when: CONTEXT_IN_CHAT_INPUT } }); } @@ -490,29 +487,32 @@ export class ViewInChatAction extends AbstractInlineChatAction { } } -export class RerunAction extends AbstractInlineChatAction { +export class ToggleDiffForChange extends AbstractInlineChatAction { + constructor() { super({ - id: ACTION_REGENERATE_RESPONSE, - title: localize2('chat.rerun.label', "Rerun Request"), - f1: false, - icon: Codicon.refresh, - precondition: CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), - menu: { + id: ACTION_TOGGLE_DIFF, + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF), + title: localize2('showChanges', 'Toggle Changes'), + icon: Codicon.diffSingle, + toggled: { + condition: CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, + }, + menu: [{ id: MENU_INLINE_CHAT_WIDGET_STATUS, - group: '0_main', - order: 5, - } + group: 'zzz', + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live)), + order: 1, + }, { + id: MENU_INLINE_CHAT_ZONE, + group: 'navigation', + when: CTX_INLINE_CHAT_CHANGE_HAS_DIFF, + order: 2 + }] }); } - override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { - const chatService = accessor.get(IChatService); - const model = ctrl.chatWidget.viewModel?.model; - - const lastRequest = model?.getRequests().at(-1); - if (lastRequest) { - await chatService.resendRequest(lastRequest, { noCommandDetection: false, attempt: lastRequest.attempt + 1, location: ctrl.chatWidget.location }); - } + override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunkInfo: HunkInformation | any): void { + ctrl.toggleDiff(hunkInfo); } } diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts index aa2411d7..7e27344c 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts @@ -10,11 +10,10 @@ import { IDimension } from 'vs/editor/common/core/dimension'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IPosition, Position } from 'vs/editor/common/core/position'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { inlineChatBackground } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { inlineChatBackground, MENU_INLINE_CHAT_CONTENT_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { ChatWidget, IChatWidgetLocationOptions } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -23,6 +22,11 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ScrollType } from 'vs/editor/common/editorCommon'; +import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { TextOnlyMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class InlineChatContentWidget implements IContentWidget { @@ -32,9 +36,9 @@ export class InlineChatContentWidget implements IContentWidget { private readonly _store = new DisposableStore(); private readonly _domNode = document.createElement('div'); private readonly _inputContainer = document.createElement('div'); - private readonly _messageContainer = document.createElement('div'); + private readonly _toolbarContainer = document.createElement('div'); - private _position?: IPosition; + private _position?: IContentWidgetPosition; private readonly _onDidBlur = this._store.add(new Emitter()); readonly onDidBlur: Event = this._onDidBlur.event; @@ -46,10 +50,12 @@ export class InlineChatContentWidget implements IContentWidget { private readonly _widget: ChatWidget; constructor( - location: ChatAgentLocation, + location: IChatWidgetLocationOptions, private readonly _editor: ICodeEditor, @IInstantiationService instaService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + @IQuickInputService quickInputService: IQuickInputService ) { this._defaultChatModel = this._store.add(instaService.createInstance(ChatModel, undefined, ChatAgentLocation.Editor)); @@ -65,16 +71,17 @@ export class InlineChatContentWidget implements IContentWidget { this._widget = scopedInstaService.createInstance( ChatWidget, location, - { resource: true }, + undefined, { defaultElementHeight: 32, editorOverflowWidgetsDomNode: _editor.getOverflowWidgetsDomNode(), - renderStyle: 'compact', + renderStyle: 'minimal', renderInputOnTop: true, renderFollowups: true, - supportsFileReferences: false, + supportsFileReferences: configurationService.getValue(`chat.experimental.variables.${location.location}`) === true, menus: { - telemetrySource: 'inlineChat-content' + telemetrySource: 'inlineChat-content', + executeToolbar: MenuId.ChatExecute, }, filter: _item => false }, @@ -88,22 +95,40 @@ export class InlineChatContentWidget implements IContentWidget { this._store.add(this._widget); this._widget.render(this._inputContainer); this._widget.setModel(this._defaultChatModel, {}); - this._store.add(this._widget.inputEditor.onDidContentSizeChange(() => _editor.layoutContentWidget(this))); + this._store.add(this._widget.onDidChangeContentHeight(() => _editor.layoutContentWidget(this))); this._domNode.tabIndex = -1; this._domNode.className = 'inline-chat-content-widget interactive-session'; this._domNode.appendChild(this._inputContainer); - this._messageContainer.classList.add('hidden', 'message'); - this._domNode.appendChild(this._messageContainer); + this._toolbarContainer.classList.add('toolbar'); + this._domNode.appendChild(this._toolbarContainer); + const toolbar = this._store.add(scopedInstaService.createInstance(MenuWorkbenchToolBar, this._toolbarContainer, MENU_INLINE_CHAT_CONTENT_STATUS, { + actionViewItemProvider: action => action instanceof MenuItemAction ? instaService.createInstance(TextOnlyMenuEntryActionViewItem, action, { conversational: true }) : undefined, + toolbarOptions: { primaryGroup: '0_main' }, + icon: false, + label: true, + })); + + this._store.add(toolbar.onDidChangeMenuItems(() => { + this._domNode.classList.toggle('contents', toolbar.getItemsLength() > 1); + })); + + // note when the widget has been interaced with and disable "close on blur" if so + let widgetHasBeenInteractedWith = false; + this._store.add(this._widget.inputEditor.onDidChangeModelContent(() => { + widgetHasBeenInteractedWith ||= this._widget.inputEditor.getModel()?.getValueLength() !== 0; + })); + this._store.add(this._widget.onDidChangeContext(() => { + widgetHasBeenInteractedWith ||= true; + _editor.layoutContentWidget(this);// https://github.com/microsoft/vscode/issues/221385 + })); const tracker = dom.trackFocus(this._domNode); this._store.add(tracker.onDidBlur(() => { - if (this._visible - // && !"ON" - ) { + if (this._visible && !widgetHasBeenInteractedWith && !quickInputService.currentQuickInput) { this._onDidBlur.fire(); } })); @@ -123,13 +148,7 @@ export class InlineChatContentWidget implements IContentWidget { } getPosition(): IContentWidgetPosition | null { - if (!this._position) { - return null; - } - return { - position: this._position, - preference: [ContentWidgetPositionPreference.ABOVE] - }; + return this._position ?? null; } beforeRender(): IDimension | null { @@ -137,10 +156,11 @@ export class InlineChatContentWidget implements IContentWidget { const maxHeight = this._widget.input.inputEditor.getOption(EditorOption.lineHeight) * 5; const inputEditorHeight = this._widget.contentHeight; - this._widget.layout(Math.min(maxHeight, inputEditorHeight), 360); + const height = Math.min(maxHeight, inputEditorHeight); + const width = 400; + this._widget.layout(height, width); - // const actualHeight = this._widget.inputPartHeight; - // return new dom.Dimension(width, actualHeight); + dom.size(this._domNode, width, null); return null; } @@ -165,18 +185,22 @@ export class InlineChatContentWidget implements IContentWidget { return this._widget.inputEditor.getValue(); } - show(position: IPosition) { + show(position: IPosition, below: boolean) { if (!this._visible) { this._visible = true; this._focusNext = true; this._editor.revealRangeNearTopIfOutsideViewport(Range.fromPositions(position), ScrollType.Immediate); - this._widget.inputEditor.setValue(''); const wordInfo = this._editor.getModel()?.getWordAtPosition(position); - this._position = wordInfo ? new Position(position.lineNumber, wordInfo.startColumn) : position; + this._position = { + position: wordInfo ? new Position(position.lineNumber, wordInfo.startColumn) : position, + preference: [below ? ContentWidgetPositionPreference.BELOW : ContentWidgetPositionPreference.ABOVE] + }; + this._editor.addContentWidget(this); + this._widget.setContext(true); this._widget.setVisible(true); } } @@ -185,6 +209,7 @@ export class InlineChatContentWidget implements IContentWidget { if (this._visible) { this._visible = false; this._editor.removeContentWidget(this); + this._widget.inputEditor.setValue(''); this._widget.saveState(); this._widget.setVisible(false); } @@ -192,16 +217,6 @@ export class InlineChatContentWidget implements IContentWidget { setSession(session: Session): void { this._widget.setModel(session.chatModel, {}); - this._widget.setInputPlaceholder(session.session.placeholder ?? ''); - this._updateMessage(session.session.message ?? ''); - } - - private _updateMessage(message: string) { - if (message) { - const renderedMessage = renderLabelWithIcons(message); - dom.reset(this._messageContainer, ...renderedMessage); - } - this._messageContainer.style.display = message ? 'inherit' : 'none'; - this._editor.layoutContentWidget(this); + this._widget.setInputPlaceholder(session.agent.description ?? ''); } } diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 66ab95ea..233942a2 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -5,58 +5,57 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; import { Barrier, DeferredPromise, Queue } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { MovingAverage } from 'vs/base/common/numbers'; +import { isEqual } from 'vs/base/common/resources'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; -import { CompletionItemKind, CompletionList, TextEdit } from 'vs/editor/common/languages'; +import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { TextEdit } from 'vs/editor/common/languages'; +import { IValidEditOperation } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; +import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; +import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; +import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatWidgetService, showChatView } from 'vs/workbench/contrib/chat/browser/chat'; +import { showChatView } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatWidgetLocationOptions } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { IInlineChatSavingService } from './inlineChatSavingService'; -import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IInlineChatSessionService } from './inlineChatSessionService'; -import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; -import { InlineChatZoneWidget } from './inlineChatZoneWidget'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { StashedSession } from './inlineChatSession'; -import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; -import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; -import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; +import { HunkInformation, HunkState, Session, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { isEqual } from 'vs/base/common/resources'; -import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { EditModeStrategy, HunkAction, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { IInlineChatSavingService } from './inlineChatSavingService'; +import { IInlineChatSessionService } from './inlineChatSessionService'; +import { InlineChatZoneWidget } from './inlineChatZoneWidget'; +import { CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR } from 'vs/workbench/contrib/chat/common/chatContextKeys'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', INIT_UI = 'INIT_UI', WAIT_FOR_INPUT = 'WAIT_FOR_INPUT', SHOW_REQUEST = 'SHOW_REQUEST', - SHOW_RESPONSE = 'SHOW_RESPONSE', PAUSE = 'PAUSE', CANCEL = 'CANCEL', ACCEPT = 'DONE', @@ -81,8 +80,9 @@ export abstract class InlineChatRunOptions { isUnstashed?: boolean; position?: IPosition; withIntentDetection?: boolean; + headless?: boolean; - static isInteractiveEditorOptions(options: any): options is InlineChatRunOptions { + static isInlineChatRunOptions(options: any): options is InlineChatRunOptions { const { initialSelection, initialRange, message, autoSend, position, existingSession } = options; if ( typeof message !== 'undefined' && typeof message !== 'string' @@ -106,16 +106,20 @@ export class InlineChatController implements IEditorContribution { private _isDisposed: boolean = false; private readonly _store = new DisposableStore(); - // private readonly _input: Lazy; - // private readonly _zone: Lazy; private readonly _ui: Lazy<{ content: InlineChatContentWidget; zone: InlineChatZoneWidget }>; private readonly _ctxVisible: IContextKey; - private readonly _ctxResponseTypes: IContextKey; + private readonly _ctxEditing: IContextKey; + private readonly _ctxResponseType: IContextKey; private readonly _ctxUserDidEdit: IContextKey; + private readonly _ctxRequestInProgress: IContextKey; - private _messages = this._store.add(new Emitter()); + private readonly _ctxResponse: IContextKey; + + private readonly _messages = this._store.add(new Emitter()); + protected readonly _onDidEnterState = this._store.add(new Emitter()); + readonly onDidEnterState = this._onDidEnterState.event; private readonly _onWillStartSession = this._store.add(new Emitter()); readonly onWillStartSession = this._onWillStartSession.event; @@ -144,28 +148,47 @@ export class InlineChatController implements IEditorContribution { @IDialogService private readonly _dialogService: IDialogService, @IContextKeyService contextKeyService: IContextKeyService, @IChatService private readonly _chatService: IChatService, - @ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, + @IEditorService private readonly _editorService: IEditorService, @INotebookEditorService notebookEditorService: INotebookEditorService, ) { this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); + this._ctxEditing = CTX_INLINE_CHAT_EDITING.bindTo(contextKeyService); this._ctxUserDidEdit = CTX_INLINE_CHAT_USER_DID_EDIT.bindTo(contextKeyService); - this._ctxResponseTypes = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(contextKeyService); + this._ctxResponseType = CTX_INLINE_CHAT_RESPONSE_TYPE.bindTo(contextKeyService); + this._ctxRequestInProgress = CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); + + this._ctxResponse = CONTEXT_RESPONSE.bindTo(contextKeyService); + CONTEXT_RESPONSE_ERROR.bindTo(contextKeyService); this._ui = new Lazy(() => { - let location = ChatAgentLocation.Editor; + + const location: IChatWidgetLocationOptions = { + location: ChatAgentLocation.Editor, + resolveData: () => { + assertType(this._editor.hasModel()); + assertType(this._session); + return { + type: ChatAgentLocation.Editor, + selection: this._editor.getSelection(), + document: this._session.textModelN.uri, + wholeRange: this._session?.wholeRange.trackedInitialRange, + }; + } + }; // inline chat in notebooks // check if this editor is part of a notebook editor - // and iff so, use the notebook location + // and iff so, use the notebook location but keep the resolveData + // talk about editor data for (const notebookEditor of notebookEditorService.listNotebookEditors()) { for (const [, codeEditor] of notebookEditor.codeEditors) { if (codeEditor === this._editor) { - location = ChatAgentLocation.Notebook; + location.location = ChatAgentLocation.Notebook; break; } } } + const content = this._store.add(_instaService.createInstance(InlineChatContentWidget, location, this._editor)); const zone = this._store.add(_instaService.createInstance(InlineChatZoneWidget, location, this._editor)); return { content, zone }; @@ -202,7 +225,7 @@ export class InlineChatController implements IEditorContribution { } })); - this._log('NEW controller'); + this._log(`NEW controller`); } dispose(): void { @@ -220,7 +243,7 @@ export class InlineChatController implements IEditorContribution { if (message instanceof Error) { this._logService.error(message, ...more); } else { - this._logService.trace(`[IE] (editor:${this._editor.getId()})${message}`, ...more); + this._logService.trace(`[IE] (editor:${this._editor.getId()}) ${message}`, ...more); } } @@ -258,6 +281,7 @@ export class InlineChatController implements IEditorContribution { } catch (error) { // this should not happen but when it does make sure to tear down the UI and everything + this._log('error during run', error); onUnexpectedError(error); if (this._session) { this._inlineChatSessionService.releaseSession(this._session); @@ -275,7 +299,9 @@ export class InlineChatController implements IEditorContribution { let nextState: State | void = state; while (nextState && !this._isDisposed) { this._log('setState to ', nextState); - nextState = await this[nextState](options); + const p: State | Promise | Promise = this[nextState](options); + this._onDidEnterState.fire(nextState); + nextState = await p; } } @@ -285,14 +311,13 @@ export class InlineChatController implements IEditorContribution { let session: Session | undefined = options.existingSession; - let initPosition: Position | undefined; if (options.position) { initPosition = Position.lift(options.position).delta(-1); delete options.position; } - const widgetPosition = this._showWidget(true, initPosition); + const widgetPosition = this._showWidget(options.headless ?? session?.headless, true, initPosition); // this._updatePlaceholder(); let errorMessage = localize('create.fail', "Failed to start editor chat"); @@ -304,7 +329,6 @@ export class InlineChatController implements IEditorContribution { if (m === Message.ACCEPT_INPUT) { // user accepted the input before having a session options.autoSend = true; - this._ui.value.zone.widget.updateProgress(true); this._ui.value.zone.widget.updateInfo(localize('welcome.2', "Getting ready...")); } else { createSessionCts.cancel(); @@ -353,7 +377,7 @@ export class InlineChatController implements IEditorContribution { break; case EditMode.Live: default: - this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._ui.value.zone); + this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._ui.value.zone, session.headless || this._configurationService.getValue(InlineChatConfigKeys.ZoneToolbar)); break; } @@ -361,7 +385,7 @@ export class InlineChatController implements IEditorContribution { return State.INIT_UI; } - private async [State.INIT_UI](options: InlineChatRunOptions): Promise { + private async [State.INIT_UI](options: InlineChatRunOptions): Promise { assertType(this._session); assertType(this._strategy); @@ -371,25 +395,30 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.clear(); const wholeRangeDecoration = this._editor.createDecorationsCollection(); - const updateWholeRangeDecoration = () => { + const handleWholeRangeChange = () => { const newDecorations = this._strategy?.getWholeRangeDecoration() ?? []; wholeRangeDecoration.set(newDecorations); + + this._ctxEditing.set(!this._session?.wholeRange.trackedInitialRange.isEmpty()); }; - this._sessionStore.add(toDisposable(() => wholeRangeDecoration.clear())); - this._sessionStore.add(this._session.wholeRange.onDidChange(updateWholeRangeDecoration)); - updateWholeRangeDecoration(); + this._sessionStore.add(toDisposable(() => { + wholeRangeDecoration.clear(); + this._ctxEditing.reset(); + })); + this._sessionStore.add(this._session.wholeRange.onDidChange(handleWholeRangeChange)); + handleWholeRangeChange(); this._sessionStore.add(this._ui.value.content.onDidBlur(() => this.cancelSession())); this._ui.value.content.setSession(this._session); - // this._ui.value.zone.widget.updateSlashCommands(this._session.session.slashCommands ?? []); + this._ui.value.zone.widget.setChatModel(this._session.chatModel); this._updatePlaceholder(); - const message = this._session.session.message ?? localize('welcome.1', "AI-generated code may be incorrect"); - - this._ui.value.zone.widget.updateInfo(message); - this._showWidget(!this._session.chatModel.hasRequests); + const isModelEmpty = !this._session.chatModel.hasRequests; + this._ui.value.zone.widget.updateToolbar(true); + this._ui.value.zone.widget.toggleStatus(!isModelEmpty); + this._showWidget(this._session.headless, isModelEmpty); this._sessionStore.add(this._editor.onDidChangeModel((e) => { const msg = this._session?.chatModel.hasRequests @@ -428,128 +457,49 @@ export class InlineChatController implements IEditorContribution { })); this._sessionStore.add(this._session.chatModel.onDidChange(async e => { - if (e.kind === 'addRequest' && e.request.response) { - this._ui.value.zone.widget.updateProgress(true); - - const listener = e.request.response.onDidChange(() => { - - if (e.request.response?.isCanceled || e.request.response?.isComplete) { - this._ui.value.zone.widget.updateProgress(false); - listener.dispose(); - } - }); - } else if (e.kind === 'removeRequest') { + if (e.kind === 'removeRequest') { // TODO@jrieken there is still some work left for when a request "in the middle" // is removed. We will undo all changes till that point but not remove those // later request - const exchange = this._session!.exchanges.find(candidate => candidate.prompt.request.id === e.requestId); - if (exchange && this._editor.hasModel()) { - // undo till this point - this._session!.hunkData.ignoreTextModelNChanges = true; - try { - - const model = this._editor.getModel(); - const targetAltVersion = exchange.prompt.modelAltVersionId; - while (targetAltVersion < model.getAlternativeVersionId() && model.canUndo()) { - await model.undo(); - } - } finally { - this._session!.hunkData.ignoreTextModelNChanges = false; - } - } + await this._session!.undoChangesUntil(e.requestId); } })); - // #region DEBT - // DEBT@jrieken - // REMOVE when agents are adopted - this._sessionStore.add(this._languageFeatureService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { - _debugDisplayName: 'inline chat commands', - triggerCharacters: ['/'], - provideCompletionItems: (model, position, context, token) => { - if (position.lineNumber !== 1) { - return undefined; - } - if (!this._session || !this._session.session.slashCommands) { - return undefined; + // apply edits from completed requests that haven't been applied yet + const editState = this._createChatTextEditGroupState(); + let didEdit = false; + for (const request of this._session.chatModel.getRequests()) { + if (!request.response) { + // done when seeing the first request that is still pending (no response). + break; + } + for (const part of request.response.response.value) { + if (part.kind !== 'textEditGroup' || !isEqual(part.uri, this._session.textModelN.uri)) { + continue; } - const widget = this._chatWidgetService.getWidgetByInputUri(model.uri); - if (widget !== this._ui.value.zone.widget.chatWidget && widget !== this._ui.value.content.chatWidget) { - return undefined; + if (part.state?.applied) { + continue; } - - const result: CompletionList = { suggestions: [], incomplete: false }; - for (const command of this._session.session.slashCommands) { - const withSlash = `/${command.name}`; - result.suggestions.push({ - label: { label: withSlash, description: command.description ?? '' }, - kind: CompletionItemKind.Text, - insertText: withSlash, - range: Range.fromPositions(new Position(1, 1), position), - }); + for (const edit of part.edits) { + this._makeChanges(edit, undefined, !didEdit); + didEdit = true; } - - return result; + part.state ??= editState; } - })); - - const updateSlashDecorations = (collection: IEditorDecorationsCollection, model: ITextModel) => { - - const newDecorations: IModelDeltaDecoration[] = []; - for (const command of (this._session?.session.slashCommands ?? []).sort((a, b) => b.name.length - a.name.length)) { - const withSlash = `/${command.name}`; - const firstLine = model.getLineContent(1); - if (firstLine.startsWith(withSlash)) { - newDecorations.push({ - range: new Range(1, 1, 1, withSlash.length + 1), - options: { - description: 'inline-chat-slash-command', - inlineClassName: 'inline-chat-slash-command', - after: { - // Force some space between slash command and placeholder - content: ' ' - } - } - }); - - // inject detail when otherwise empty - if (firstLine.trim() === `/${command.name}`) { - newDecorations.push({ - range: new Range(1, withSlash.length, 1, withSlash.length), - options: { - description: 'inline-chat-slash-command-detail', - after: { - content: `${command.description}`, - inlineClassName: 'inline-chat-slash-command-detail' - } - } - }); - } - break; - } - } - collection.set(newDecorations); - }; - const inputInputEditor = this._ui.value.content.chatWidget.inputEditor; - const zoneInputEditor = this._ui.value.zone.widget.chatWidget.inputEditor; - const inputDecorations = inputInputEditor.createDecorationsCollection(); - const zoneDecorations = zoneInputEditor.createDecorationsCollection(); - this._sessionStore.add(inputInputEditor.onDidChangeModelContent(() => updateSlashDecorations(inputDecorations, inputInputEditor.getModel()!))); - this._sessionStore.add(zoneInputEditor.onDidChangeModelContent(() => updateSlashDecorations(zoneDecorations, zoneInputEditor.getModel()!))); - this._sessionStore.add(toDisposable(() => { - inputDecorations.clear(); - zoneDecorations.clear(); - })); + } + if (didEdit) { + const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { computeMoves: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, ignoreTrimWhitespace: false }, 'advanced'); + this._session.wholeRange.fixup(diff?.changes ?? []); + await this._session.hunkData.recompute(editState, diff); - //#endregion ------- DEBT + this._updateCtxResponseType(); + } + options.position = await this._strategy.renderChanges(); - if (!this._session.chatModel.hasRequests) { - return State.WAIT_FOR_INPUT; - } else if (options.isUnstashed) { - delete options.isUnstashed; - return State.SHOW_RESPONSE; + if (this._session.chatModel.requestInProgress) { + return State.SHOW_REQUEST; } else { - return State.SHOW_RESPONSE; + return State.WAIT_FOR_INPUT; } } @@ -563,7 +513,7 @@ export class InlineChatController implements IEditorContribution { this.updateInput(options.message); aria.alert(options.message); delete options.message; - this._showWidget(false); + this._showWidget(this._session.headless, false); } let message = Message.NONE; @@ -588,7 +538,7 @@ export class InlineChatController implements IEditorContribution { if (options.autoSend) { delete options.autoSend; - this._showWidget(false); + this._showWidget(this._session.headless, false); this._ui.value.zone.widget.chatWidget.acceptInput(); } @@ -613,29 +563,28 @@ export class InlineChatController implements IEditorContribution { return State.WAIT_FOR_INPUT; } - const input = request.message.text; - this._ui.value.zone.widget.value = input; - - this._session.addInput(new SessionPrompt(request, this._editor.getModel()!.getAlternativeVersionId())); return State.SHOW_REQUEST; } - private async [State.SHOW_REQUEST](): Promise { + private async [State.SHOW_REQUEST](options: InlineChatRunOptions): Promise { assertType(this._session); + assertType(this._strategy); assertType(this._session.chatModel.requestInProgress); + this._ctxRequestInProgress.set(true); + const { chatModel } = this._session; - const request: IChatRequestModel | undefined = chatModel.getRequests().at(-1); + const request = chatModel.lastRequest; assertType(request); assertType(request.response); - this._showWidget(false); - this._ui.value.zone.widget.value = request.message.text; + this._showWidget(this._session.headless, false); this._ui.value.zone.widget.selectAll(false); this._ui.value.zone.widget.updateInfo(''); + this._ui.value.zone.widget.toggleStatus(true); const { response } = request; const responsePromise = new DeferredPromise(); @@ -647,7 +596,8 @@ export class InlineChatController implements IEditorContribution { const progressiveEditsClock = StopWatch.create(); const progressiveEditsQueue = new Queue(); - let next: State.SHOW_RESPONSE | State.SHOW_REQUEST | State.CANCEL | State.PAUSE | State.ACCEPT | State.WAIT_FOR_INPUT = State.SHOW_RESPONSE; + let next: State.WAIT_FOR_INPUT | State.SHOW_REQUEST | State.CANCEL | State.PAUSE | State.ACCEPT = State.WAIT_FOR_INPUT; + store.add(Event.once(this._messages.event)(message => { this._log('state=_makeRequest) message received', message); this._chatService.cancelCurrentRequestForSession(chatModel.sessionId); @@ -660,7 +610,7 @@ export class InlineChatController implements IEditorContribution { } })); - store.add(chatModel.onDidChange(e => { + store.add(chatModel.onDidChange(async e => { if (e.kind === 'removeRequest' && e.requestId === request.id) { progressiveEditsCts.cancel(); responsePromise.complete(); @@ -669,6 +619,50 @@ export class InlineChatController implements IEditorContribution { } else { next = State.CANCEL; } + return; + } + if (e.kind === 'move') { + assertType(this._session); + const log: typeof this._log = (msg: string, ...args: any[]) => this._log('state=_showRequest) moving inline chat', msg, ...args); + + log('move was requested', e.target, e.range); + + // if there's already a tab open for targetUri, show it and move inline chat to that tab + // otherwise, open the tab to the side + const initialSelection = Selection.fromRange(Range.lift(e.range), SelectionDirection.LTR); + const editorPane = await this._editorService.openEditor({ resource: e.target, options: { selection: initialSelection } }, SIDE_GROUP); + + if (!editorPane) { + log('opening editor failed'); + return; + } + + const newEditor = editorPane.getControl(); + if (!isCodeEditor(newEditor) || !newEditor.hasModel()) { + log('new editor is either missing or not a code editor or does not have a model'); + return; + } + + if (this._inlineChatSessionService.getSession(newEditor, e.target)) { + log('new editor ALREADY has a session'); + return; + } + + const newSession = await this._inlineChatSessionService.createSession( + newEditor, + { + editMode: this._getMode(), + session: this._session, + }, + CancellationToken.None); // TODO@ulugbekna: add proper cancellation? + + + InlineChatController.get(newEditor)?.run({ existingSession: newSession }); + + next = State.CANCEL; + responsePromise.complete(); + + return; } })); @@ -680,16 +674,14 @@ export class InlineChatController implements IEditorContribution { let lastLength = 0; let isFirstChange = true; - const sha1 = new DefaultModelSHA1Computer(); - const textModel0Sha1 = sha1.canComputeSHA1(this._session.textModel0) - ? sha1.computeSHA1(this._session.textModel0) - : generateUuid(); - const editState: IChatTextEditGroupState = { sha1: textModel0Sha1, applied: 0 }; + const editState = this._createChatTextEditGroupState(); let localEditGroup: IChatTextEditGroup | undefined; // apply edits const handleResponse = () => { + this._updateCtxResponseType(); + if (!localEditGroup) { localEditGroup = response.response.value.find(part => part.kind === 'textEditGroup' && isEqual(part.uri, this._session?.textModelN.uri)); } @@ -701,6 +693,9 @@ export class InlineChatController implements IEditorContribution { const edits = localEditGroup.edits; const newEdits = edits.slice(lastLength); if (newEdits.length > 0) { + + this._log(`${this._session?.textModelN.uri.toString()} received ${newEdits.length} edits`); + // NEW changes lastLength = edits.length; progressiveEditsAvgDuration.update(progressiveEditsClock.elapsed()); @@ -725,7 +720,7 @@ export class InlineChatController implements IEditorContribution { // reshow the widget if the start position changed or shows at the wrong position const startNow = this._session!.wholeRange.value.getStartPosition(); if (!startNow.equals(startThen) || !this._ui.value.zone.position?.equals(startNow)) { - this._showWidget(false, startNow.delta(-1)); + this._showWidget(this._session!.headless, false, startNow.delta(-1)); } }); } @@ -747,66 +742,50 @@ export class InlineChatController implements IEditorContribution { await responsePromise.p; await progressiveEditsQueue.whenIdle(); + if (response.result?.errorDetails) { + await this._session.undoChangesUntil(response.requestId); + } + store.dispose(); - // todo@jrieken we can likely remove 'trackEdit' const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { computeMoves: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, ignoreTrimWhitespace: false }, 'advanced'); this._session.wholeRange.fixup(diff?.changes ?? []); + await this._session.hunkData.recompute(editState, diff); - await this._session.hunkData.recompute(editState); - - this._ui.value.zone.widget.updateToolbar(true); - this._ui.value.zone.widget.updateProgress(false); - - return next; - } - - private async[State.SHOW_RESPONSE](): Promise { - assertType(this._session); - assertType(this._strategy); + this._ctxRequestInProgress.set(false); - const { response } = this._session.lastExchange!; - - let responseTypes: InlineChatResponseTypes | undefined; - for (const request of this._session.chatModel.getRequests()) { - if (!request.response) { - continue; - } - const thisType = asInlineChatResponseType(request.response.response); - if (responseTypes === undefined) { - responseTypes = thisType; - } else if (responseTypes !== thisType) { - responseTypes = InlineChatResponseTypes.Mixed; - break; - } - } - this._ctxResponseTypes.set(responseTypes); let newPosition: Position | undefined; - if (response instanceof EmptyResponse) { - // show status message + if (response.result?.errorDetails) { + // error -> no message, errors are shown with the request + + } else if (response.response.value.length === 0) { + // empty -> show message const status = localize('empty', "No results, please refine your input and try again"); this._ui.value.zone.widget.updateStatus(status, { classes: ['warn'] }); - return State.WAIT_FOR_INPUT; - } else if (response instanceof ErrorResponse) { - // show error - if (!response.isCancellation) { - this._ui.value.zone.widget.updateStatus(response.message, { classes: ['error'] }); - this._strategy?.cancel(); - } - - } else if (response instanceof ReplyResponse) { - // real response -> complex... + } else { + // real response -> no message this._ui.value.zone.widget.updateStatus(''); - this._ui.value.zone.widget.updateToolbar(true); + } - newPosition = await this._strategy.renderChanges(response); + const position = await this._strategy.renderChanges(); + if (position) { + // if the selection doesn't start far off we keep the widget at its current position + // because it makes reading this nicer + const selection = this._editor.getSelection(); + if (selection?.containsPosition(position)) { + if (position.lineNumber - selection.startLineNumber > 8) { + newPosition = position; + } + } else { + newPosition = position; + } } - this._showWidget(false, newPosition); + this._showWidget(this._session.headless, false, newPosition); - return State.WAIT_FOR_INPUT; + return next; } private async[State.PAUSE]() { @@ -846,7 +825,7 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.clear(); // only stash sessions that were not unstashed, not "empty", and not interacted with - const shouldStash = !this._session.isUnstashed && !!this._session.lastExchange && this._session.hunkData.size === this._session.hunkData.pending; + const shouldStash = !this._session.isUnstashed && this._session.chatModel.hasRequests && this._session.hunkData.size === this._session.hunkData.pending; let undoCancelEdits: IValidEditOperation[] = []; try { undoCancelEdits = this._strategy.cancel(); @@ -873,8 +852,9 @@ export class InlineChatController implements IEditorContribution { // ---- - private _showWidget(initialRender: boolean = false, position?: Position) { + private _showWidget(headless: boolean = false, initialRender: boolean = false, position?: Position) { assertType(this._editor.hasModel()); + this._ctxVisible.set(true); let widgetPosition: Position; if (position) { @@ -892,38 +872,26 @@ export class InlineChatController implements IEditorContribution { widgetPosition = this._editor.getSelection().getStartPosition().delta(-1); } - if (this._session && !position && (this._session.hasChangedText || this._session.lastExchange)) { - widgetPosition = this._session.wholeRange.value.getStartPosition().delta(-1); + if (this._session && !position && (this._session.hasChangedText || this._session.chatModel.hasRequests)) { + widgetPosition = this._session.wholeRange.trackedInitialRange.getStartPosition().delta(-1); } - if (this._ui.rawValue?.zone?.position) { - this._ui.value.zone.updatePositionAndHeight(widgetPosition); + if (!headless) { - } else if (initialRender) { - const selection = this._editor.getSelection(); - widgetPosition = selection.getStartPosition(); - // TODO@jrieken we are not ready for this - // widgetPosition = selection.getEndPosition(); - // if (Range.spansMultipleLines(selection) && widgetPosition.column === 1) { - // // selection ends on "nothing" -> move up to match the - // // rendered/visible part of the selection - // widgetPosition = this._editor.getModel().validatePosition(widgetPosition.delta(-1, Number.MAX_SAFE_INTEGER)); - // } - this._ui.value.content.show(widgetPosition); + if (this._ui.rawValue?.zone?.position) { + this._ui.value.zone.updatePositionAndHeight(widgetPosition); - } else { - this._ui.value.content.hide(); - this._ui.value.zone.show(widgetPosition); - if (this._session) { - this._ui.value.zone.widget.setChatModel(this._session.chatModel); - } - } + } else if (initialRender && this._configurationService.getValue(InlineChatConfigKeys.StartWithOverlayWidget)) { + const selection = this._editor.getSelection(); + widgetPosition = selection.getStartPosition(); + this._ui.value.content.show(widgetPosition, selection.isEmpty()); - if (this._session && this._ui.rawValue?.zone) { - this._ui.rawValue?.zone.updateBackgroundColor(widgetPosition, this._session.wholeRange.value); + } else { + this._ui.value.content.hide(); + this._ui.value.zone.show(widgetPosition); + } } - this._ctxVisible.set(true); return widgetPosition; } @@ -941,6 +909,46 @@ export class InlineChatController implements IEditorContribution { } } + private _updateCtxResponseType(): void { + + if (!this._session) { + this._ctxResponseType.set(InlineChatResponseType.None); + return; + } + + const hasLocalEdit = (response: IResponse): boolean => { + return response.value.some(part => part.kind === 'textEditGroup' && isEqual(part.uri, this._session?.textModelN.uri)); + }; + + let responseType = InlineChatResponseType.None; + for (const request of this._session.chatModel.getRequests()) { + if (!request.response || request.response.isCanceled) { + continue; + } + responseType = InlineChatResponseType.Messages; + if (hasLocalEdit(request.response.response)) { + responseType = InlineChatResponseType.MessagesAndEdits; + break; // no need to check further + } + } + this._ctxResponseType.set(responseType); + this._ctxResponse.set(responseType !== InlineChatResponseType.None); + } + + private _createChatTextEditGroupState(): IChatTextEditGroupState { + assertType(this._session); + + const sha1 = new DefaultModelSHA1Computer(); + const textModel0Sha1 = sha1.canComputeSHA1(this._session.textModel0) + ? sha1.computeSHA1(this._session.textModel0) + : generateUuid(); + + return { + sha1: textModel0Sha1, + applied: 0 + }; + } + private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined, undoStopBefore: boolean) { assertType(this._session); assertType(this._strategy); @@ -962,7 +970,6 @@ export class InlineChatController implements IEditorContribution { }; this._inlineChatSavingService.markChanged(this._session); - this._session.wholeRange.trackEdits(editOperations); if (opts) { await this._strategy.makeProgressiveChanges(editOperations, editsObserver, opts, undoStopBefore); } else { @@ -977,14 +984,27 @@ export class InlineChatController implements IEditorContribution { } private _getPlaceholderText(): string { - return this._forcedPlaceholder ?? this._session?.session.placeholder ?? ''; + return this._forcedPlaceholder ?? this._session?.agent.description ?? ''; } // ---- controller API showSaveHint(): void { - const status = localize('savehint', "Accept or discard changes to continue saving"); + if (!this._session) { + return; + } + + const status = localize('savehint', "Accept or discard changes to continue saving."); this._ui.value.zone.widget.updateStatus(status, { classes: ['warn'] }); + + if (this._ui.value.zone.position) { + this._editor.revealLineInCenterIfOutsideViewport(this._ui.value.zone.position.lineNumber); + } else { + const hunk = this._session.hunkData.getInfo().find(info => info.getState() === HunkState.Pending); + if (hunk) { + this._editor.revealLineInCenterIfOutsideViewport(hunk.getRangesN()[0].startLineNumber); + } + } } acceptInput() { @@ -1024,10 +1044,6 @@ export class InlineChatController implements IEditorContribution { return this._ui.value.zone.widget.hasFocus(); } - moveHunk(next: boolean) { - this.focus(); - this._strategy?.move?.(next); - } async viewInChat() { if (!this._strategy || !this._session) { @@ -1057,6 +1073,7 @@ export class InlineChatController implements IEditorContribution { if (someApplied) { assertType(lastEdit); lastEdit.edits = [doEdits]; + lastEdit.state!.applied = 0; } await this._instaService.invokeFunction(moveToPanelChat, this._session?.chatModel); @@ -1064,17 +1081,14 @@ export class InlineChatController implements IEditorContribution { this.cancelSession(); } - toggleDiff() { - this._strategy?.toggleDiff?.(); - } - acceptSession(): void { - if (this._session?.lastExchange?.response instanceof ReplyResponse && this._session?.lastExchange?.response.chatResponse) { - const response = this._session?.lastExchange?.response.chatResponse; + const response = this._session?.chatModel.getRequests().at(-1)?.response; + if (response) { this._chatService.notifyUserAction({ - sessionId: this._session.chatModel.sessionId, + sessionId: response.session.sessionId, requestId: response.requestId, agentId: response.agent?.id, + command: response.slashCommand?.name, result: response.result, action: { kind: 'inlineChat', @@ -1085,22 +1099,31 @@ export class InlineChatController implements IEditorContribution { this._messages.fire(Message.ACCEPT_SESSION); } - acceptHunk() { - return this._strategy?.acceptHunk(); + acceptHunk(hunkInfo?: HunkInformation) { + return this._strategy?.performHunkAction(hunkInfo, HunkAction.Accept); } - discardHunk() { - return this._strategy?.discardHunk(); + discardHunk(hunkInfo?: HunkInformation) { + return this._strategy?.performHunkAction(hunkInfo, HunkAction.Discard); } - async cancelSession() { + toggleDiff(hunkInfo?: HunkInformation) { + return this._strategy?.performHunkAction(hunkInfo, HunkAction.ToggleDiff); + } - if (this._session?.lastExchange?.response instanceof ReplyResponse && this._session?.lastExchange?.response.chatResponse) { - const response = this._session?.lastExchange?.response.chatResponse; + moveHunk(next: boolean) { + this.focus(); + this._strategy?.performHunkAction(undefined, next ? HunkAction.MoveNext : HunkAction.MovePrev); + } + + async cancelSession() { + const response = this._session?.chatModel.lastRequest?.response; + if (response) { this._chatService.notifyUserAction({ - sessionId: this._session.chatModel.sessionId, + sessionId: response.session.sessionId, requestId: response.requestId, agentId: response.agent?.id, + command: response.slashCommand?.name, result: response.result, action: { kind: 'inlineChat', @@ -1124,6 +1147,20 @@ export class InlineChatController implements IEditorContribution { } } + reportIssue() { + const response = this._session?.chatModel.lastRequest?.response; + if (response) { + this._chatService.notifyUserAction({ + sessionId: response.session.sessionId, + requestId: response.requestId, + agentId: response.agent?.id, + command: response.slashCommand?.name, + result: response.result, + action: { kind: 'bug' } + }); + } + } + unstashLastSession(): Session | undefined { const result = this._stashedSession.value?.unstash(); if (result) { @@ -1135,6 +1172,37 @@ export class InlineChatController implements IEditorContribution { joinCurrentRun(): Promise | undefined { return this._currentRun; } + + async reviewEdits(anchor: IRange, stream: AsyncIterable, token: CancellationToken) { + if (!this._editor.hasModel()) { + return false; + } + + const session = await this._inlineChatSessionService.createSession(this._editor, { editMode: EditMode.Live, wholeRange: anchor, headless: true }, token); + if (!session) { + return false; + } + + const request = session.chatModel.addRequest({ text: 'DUMMY', parts: [] }, { variables: [] }, 0); + const run = this.run({ + existingSession: session, + headless: true + }); + + await Event.toPromise(Event.filter(this._onDidEnterState.event, candidate => candidate === State.SHOW_REQUEST)); + + for await (const chunk of stream) { + session.chatModel.acceptResponseProgress(request, { kind: 'textEdit', uri: this._editor.getModel()!.uri, edits: [chunk] }); + } + + if (token.isCancellationRequested) { + session.chatModel.cancelRequest(request); + } else { + session.chatModel.completeResponse(request); + } + await run; + return true; + } } async function moveToPanelChat(accessor: ServicesAccessor, model: ChatModel | undefined) { @@ -1151,25 +1219,3 @@ async function moveToPanelChat(accessor: ServicesAccessor, model: ChatModel | un widget.focusLastMessage(); } } - -function asInlineChatResponseType(response: IResponse): InlineChatResponseTypes { - let result: InlineChatResponseTypes | undefined; - for (const item of response.value) { - let thisType: InlineChatResponseTypes; - switch (item.kind) { - case 'textEditGroup': - thisType = InlineChatResponseTypes.OnlyEdits; - break; - case 'markdownContent': - default: - thisType = InlineChatResponseTypes.OnlyMessages; - break; - } - if (result === undefined) { - result = thisType; - } else if (result !== thisType) { - return InlineChatResponseTypes.Mixed; - } - } - return result ?? InlineChatResponseTypes.Empty; -} diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts new file mode 100644 index 00000000..d898993c --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -0,0 +1,166 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { localize, localize2 } from 'vs/nls'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { InlineChatController, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Range } from 'vs/editor/common/core/range'; +import { Position } from 'vs/editor/common/core/position'; +import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { IValidEditOperation } from 'vs/editor/common/model'; + + +export const CTX_INLINE_CHAT_EXPANSION = new RawContextKey('inlineChatExpansion', false, localize('inlineChatExpansion', "Whether the inline chat expansion is enabled when at the end of a just-typed line")); + +export class InlineChatExansionContextKey implements IEditorContribution { + + static Id = 'editor.inlineChatExpansion'; + + private readonly _store = new DisposableStore(); + private readonly _editorListener = this._store.add(new MutableDisposable()); + + private readonly _ctxInlineChatExpansion: IContextKey; + + constructor( + editor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService, + @IChatAgentService chatAgentService: IChatAgentService + ) { + this._ctxInlineChatExpansion = CTX_INLINE_CHAT_EXPANSION.bindTo(contextKeyService); + + const update = () => { + if (editor.hasModel() && chatAgentService.getAgents().length > 0) { + this._install(editor); + } else { + this._uninstall(); + } + }; + this._store.add(chatAgentService.onDidChangeAgents(update)); + this._store.add(editor.onDidChangeModel(update)); + update(); + } + + dispose(): void { + this._ctxInlineChatExpansion.reset(); + this._store.dispose(); + } + + private _install(editor: IActiveCodeEditor): void { + + const store = new DisposableStore(); + this._editorListener.value = store; + + const model = editor.getModel(); + const lastChangeEnds: number[] = []; + + store.add(editor.onDidChangeCursorPosition(e => { + + let enabled = false; + + if (e.reason === CursorChangeReason.NotSet) { + + const position = editor.getPosition(); + const positionOffset = model.getOffsetAt(position); + + const lineLength = model.getLineLength(position.lineNumber); + const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(position.lineNumber); + + if (firstNonWhitespace !== 0 && position.column > lineLength && lastChangeEnds.includes(positionOffset)) { + enabled = true; + } + } + + lastChangeEnds.length = 0; + this._ctxInlineChatExpansion.set(enabled); + + })); + + store.add(editor.onDidChangeModelContent(e => { + lastChangeEnds.length = 0; + for (const change of e.changes) { + const changeEnd = change.rangeOffset + change.text.length; + lastChangeEnds.push(changeEnd); + } + queueMicrotask(() => { + if (lastChangeEnds.length > 0) { + // this is a signal that onDidChangeCursorPosition didn't run which means some outside change + // which means we should disable the context key + this._ctxInlineChatExpansion.set(false); + } + }); + })); + } + + private _uninstall(): void { + this._editorListener.clear(); + } +} + +export class InlineChatExpandLineAction extends EditorAction2 { + + constructor() { + super({ + id: 'inlineChat.startWithCurrentLine', + category: AbstractInlineChatAction.category, + title: localize2('startWithCurrentLine', "Start in Editor with Current Line"), + f1: true, + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_HAS_AGENT, EditorContextKeys.writable), + // keybinding: { + // when: CTX_INLINE_CHAT_EXPANSION, + // weight: KeybindingWeight.EditorContrib, + // primary: KeyCode.Tab + // } + }); + } + + override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { + const ctrl = InlineChatController.get(editor); + if (!ctrl || !editor.hasModel()) { + return; + } + + const model = editor.getModel(); + const lineNumber = editor.getSelection().positionLineNumber; + const lineContent = model.getLineContent(lineNumber); + + const startColumn = model.getLineFirstNonWhitespaceColumn(lineNumber); + const endColumn = model.getLineMaxColumn(lineNumber); + + // clear the line + let undoEdits: IValidEditOperation[] = []; + model.pushEditOperations(null, [EditOperation.replace(new Range(lineNumber, startColumn, lineNumber, endColumn), '')], (edits) => { + undoEdits = edits; + return null; + }); + + let lastState: State | undefined; + const d = ctrl.onDidEnterState(e => lastState = e); + + try { + // trigger chat + await ctrl.run({ + autoSend: true, + message: lineContent.trim(), + position: new Position(lineNumber, startColumn) + }); + + } finally { + d.dispose(); + } + + if (lastState === State.CANCEL) { + model.pushEditOperations(null, undoEdits, () => null); + } + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts deleted file mode 100644 index eca335cb..00000000 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts +++ /dev/null @@ -1,256 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dimension, h } from 'vs/base/browser/dom'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { Range } from 'vs/editor/common/core/range'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; -import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { INLINE_CHAT_ID, inlineChatRegionHighlight } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { Position } from 'vs/editor/common/core/position'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { ResourceLabel } from 'vs/workbench/browser/labels'; -import { FileKind } from 'vs/platform/files/common/files'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IAction, toAction } from 'vs/base/common/actions'; -import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { Codicon } from 'vs/base/common/codicons'; -import { TAB_ACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; -import { localize } from 'vs/nls'; -import { Event } from 'vs/base/common/event'; - -export class InlineChatFileCreatePreviewWidget extends ZoneWidget { - - private static TitleHeight = 35; - - private readonly _elements = h('div.inline-chat-newfile-widget@domNode', [ - h('div.title@title', [ - h('span.name.show-file-icons@name'), - h('span.detail@detail'), - ]), - h('div.editor@editor'), - ]); - - private readonly _name: ResourceLabel; - private readonly _previewEditor: ICodeEditor; - private readonly _previewStore = new MutableDisposable(); - private readonly _buttonBar: ButtonBarWidget; - private _dim: Dimension | undefined; - - constructor( - parentEditor: ICodeEditor, - @IInstantiationService instaService: IInstantiationService, - @IThemeService themeService: IThemeService, - @ITextModelService private readonly _textModelResolverService: ITextModelService, - @IEditorService private readonly _editorService: IEditorService, - ) { - super(parentEditor, { - showArrow: false, - showFrame: true, - frameColor: colorRegistry.asCssVariable(TAB_ACTIVE_MODIFIED_BORDER), - frameWidth: 1, - isResizeable: true, - isAccessible: true, - showInHiddenAreas: true, - ordinal: 10000 + 2 - }); - super.create(); - - this._name = instaService.createInstance(ResourceLabel, this._elements.name, { supportIcons: true }); - this._elements.detail.appendChild(renderIcon(Codicon.circleFilled)); - - const contributions = EditorExtensionsRegistry - .getEditorContributions() - .filter(c => c.id !== INLINE_CHAT_ID); - - this._previewEditor = instaService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, { - scrollBeyondLastLine: false, - stickyScroll: { enabled: false }, - minimap: { enabled: false }, - scrollbar: { alwaysConsumeMouseWheel: false, useShadows: true, ignoreHorizontalScrollbarInContentHeight: true, }, - }, { isSimpleWidget: true, contributions }, parentEditor); - - const doStyle = () => { - const theme = themeService.getColorTheme(); - const overrides: [target: string, source: string][] = [ - [colorRegistry.editorBackground, inlineChatRegionHighlight], - [editorColorRegistry.editorGutter, inlineChatRegionHighlight], - ]; - - for (const [target, source] of overrides) { - const value = theme.getColor(source); - if (value) { - this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value)); - } - } - }; - doStyle(); - this._disposables.add(themeService.onDidColorThemeChange(doStyle)); - - this._buttonBar = instaService.createInstance(ButtonBarWidget); - this._elements.title.appendChild(this._buttonBar.domNode); - } - - override dispose(): void { - this._name.dispose(); - this._buttonBar.dispose(); - this._previewEditor.dispose(); - this._previewStore.dispose(); - super.dispose(); - } - - protected override _fillContainer(container: HTMLElement): void { - container.appendChild(this._elements.domNode); - } - - override show(): void { - throw new Error('Use showFileCreation'); - } - - async showCreation(where: Position, untitledTextModel: IUntitledTextEditorModel): Promise { - - const store = new DisposableStore(); - this._previewStore.value = store; - - this._name.element.setFile(untitledTextModel.resource, { - fileKind: FileKind.FILE, - fileDecorations: { badges: true, colors: true } - }); - - const actionSave = toAction({ - id: '1', - label: localize('save', "Create"), - run: () => untitledTextModel.save({ reason: SaveReason.EXPLICIT }) - }); - const actionSaveAs = toAction({ - id: '2', - label: localize('saveAs', "Create As"), - run: async () => { - const ids = this._editorService.findEditors(untitledTextModel.resource, { supportSideBySide: SideBySideEditor.ANY }); - await this._editorService.save(ids.slice(), { saveAs: true, reason: SaveReason.EXPLICIT }); - } - }); - - this._buttonBar.update([ - [actionSave, actionSaveAs], - [(toAction({ id: '3', label: localize('discard', "Discard"), run: () => untitledTextModel.revert() }))] - ]); - - store.add(Event.any( - untitledTextModel.onDidRevert, - untitledTextModel.onDidSave, - untitledTextModel.onDidChangeDirty, - untitledTextModel.onWillDispose - )(() => this.hide())); - - await untitledTextModel.resolve(); - - const ref = await this._textModelResolverService.createModelReference(untitledTextModel.resource); - store.add(ref); - - const model = ref.object.textEditorModel; - this._previewEditor.setModel(model); - - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - - this._elements.title.style.height = `${InlineChatFileCreatePreviewWidget.TitleHeight}px`; - const titleHightInLines = InlineChatFileCreatePreviewWidget.TitleHeight / lineHeight; - - const maxLines = Math.max(4, Math.floor((this.editor.getLayoutInfo().height / lineHeight) * .33)); - const lines = Math.min(maxLines, model.getLineCount()); - - super.show(where, titleHightInLines + lines); - } - - override hide(): void { - this._previewStore.clear(); - super.hide(); - } - - // --- layout - - protected override revealRange(range: Range, isLastLine: boolean): void { - // ignore - } - - protected override _onWidth(widthInPixel: number): void { - if (this._dim) { - this._doLayout(this._dim.height, widthInPixel); - } - } - - protected override _doLayout(heightInPixel: number, widthInPixel: number): void { - - const { lineNumbersLeft } = this.editor.getLayoutInfo(); - this._elements.title.style.marginLeft = `${lineNumbersLeft}px`; - - const newDim = new Dimension(widthInPixel, heightInPixel); - if (!Dimension.equals(this._dim, newDim)) { - this._dim = newDim; - this._previewEditor.layout(this._dim.with(undefined, this._dim.height - InlineChatFileCreatePreviewWidget.TitleHeight)); - } - } -} - - -class ButtonBarWidget { - - private readonly _domNode = h('div.buttonbar-widget'); - private readonly _buttonBar: ButtonBar; - private readonly _store = new DisposableStore(); - - constructor( - @IContextMenuService private _contextMenuService: IContextMenuService, - ) { - this._buttonBar = new ButtonBar(this.domNode); - - } - - update(allActions: IAction[][]): void { - this._buttonBar.clear(); - let secondary = false; - for (const actions of allActions) { - let btn: IButton; - const [first, ...rest] = actions; - if (!first) { - continue; - } else if (rest.length === 0) { - // single action - btn = this._buttonBar.addButton({ ...defaultButtonStyles, secondary }); - } else { - btn = this._buttonBar.addButtonWithDropdown({ - ...defaultButtonStyles, - addPrimaryActionToDropdown: false, - actions: rest, - contextMenuProvider: this._contextMenuService - }); - } - btn.label = first.label; - this._store.add(btn.onDidClick(() => first.run())); - secondary = true; - } - } - - dispose(): void { - this._buttonBar.dispose(); - this._store.dispose(); - } - - get domNode() { - return this._domNode.root; - } -} diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts index e074c182..64a25c3c 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts @@ -12,6 +12,9 @@ import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/in import { IInlineChatSessionService } from './inlineChatSessionService'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor'; +import { NotebookMultiTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor'; export class InlineChatNotebookContribution { @@ -19,6 +22,7 @@ export class InlineChatNotebookContribution { constructor( @IInlineChatSessionService sessionService: IInlineChatSessionService, + @IEditorService editorService: IEditorService, @INotebookEditorService notebookEditorService: INotebookEditorService, ) { @@ -58,6 +62,11 @@ export class InlineChatNotebookContribution { return fallback; } + const activeEditor = editorService.activeEditorPane; + if (activeEditor && (activeEditor.getId() === NotebookTextDiffEditor.ID || activeEditor.getId() === NotebookMultiTextDiffEditor.ID)) { + return `${editor.getId()}#${uri}`; + } + throw illegalState('Expected notebook editor'); } })); diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index b360c83a..f5b111e5 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -5,23 +5,13 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; -import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; -import { TextEdit } from 'vs/editor/common/languages'; import { IIdentifiedSingleEditOperation, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { EditMode, IInlineChatSession, CTX_INLINE_CHAT_HAS_STASHED_SESSION, IInlineChatResponse } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditMode, CTX_INLINE_CHAT_HAS_STASHED_SESSION } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { isCancellationError } from 'vs/base/common/errors'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { DetailedLineRangeMapping, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; -import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ResourceMap } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; -import { isEqual } from 'vs/base/common/resources'; -import { IInlineChatSessionService, Recording } from './inlineChatSessionService'; +import { IInlineChatSessionService } from './inlineChatSessionService'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { coalesceInPlace } from 'vs/base/common/arrays'; @@ -31,9 +21,10 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ILogService } from 'vs/platform/log/common/log'; -import { ChatModel, IChatRequestModel, IChatResponseModel, IChatTextEditGroupState } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, IChatRequestModel, IChatTextEditGroupState } from 'vs/workbench/contrib/chat/common/chatModel'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IChatAgent } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IDocumentDiff } from 'vs/editor/common/diff/documentDiffProvider'; export type TelemetryData = { @@ -89,15 +80,6 @@ export class SessionWholeRange { } } - trackEdits(edits: ISingleEditOperation[]): void { - const newDeco: IModelDeltaDecoration[] = []; - for (const edit of edits) { - newDeco.push({ range: edit.range, options: SessionWholeRange._options }); - } - this._decorationIds.push(...this._textModel.deltaDecorations([], newDeco)); - this._onDidChange.fire(this); - } - fixup(changes: readonly DetailedLineRangeMapping[]): void { const newDeco: IModelDeltaDecoration[] = []; @@ -137,16 +119,15 @@ export class SessionWholeRange { export class Session { - private _lastInput: SessionPrompt | undefined; private _isUnstashed: boolean = false; - private readonly _exchange: SessionExchange[] = []; private readonly _startTime = new Date(); private readonly _teldata: TelemetryData; - readonly textModelNAltVersion: number; + private readonly _versionByRequest = new Map(); constructor( readonly editMode: EditMode, + readonly headless: boolean, /** * The URI of the document which is being EditorEdit */ @@ -156,16 +137,16 @@ export class Session { */ readonly textModel0: ITextModel, /** - * The document into which AI edits went, when live this is `targetUri` otherwise it is a temporary document + * The model of the editor */ readonly textModelN: ITextModel, readonly agent: IChatAgent, - readonly session: IInlineChatSession, readonly wholeRange: SessionWholeRange, readonly hunkData: HunkData, readonly chatModel: ChatModel, + versionsByRequest?: [string, number][], // DEBT? this is needed when a chat model is "reused" for a new chat session ) { - this.textModelNAltVersion = textModelN.getAlternativeVersionId(); + this._teldata = { extension: ExtensionIdentifier.toKey(agent.extensionId), startTime: this._startTime.toISOString(), @@ -180,14 +161,9 @@ export class Session { discardedHunks: 0, responseTypes: '' }; - } - - addInput(input: SessionPrompt): void { - this._lastInput = input; - } - - get lastInput() { - return this._lastInput; + if (versionsByRequest) { + this._versionByRequest = new Map(versionsByRequest); + } } get isUnstashed(): boolean { @@ -199,19 +175,30 @@ export class Session { this._isUnstashed = true; } - addExchange(exchange: SessionExchange): void { - this._isUnstashed = false; - const newLen = this._exchange.push(exchange); - this._teldata.rounds += `${newLen}|`; - // this._teldata.responseTypes += `${exchange.response instanceof ReplyResponse ? exchange.response.responseType : InlineChatResponseTypes.Empty}|`; + markModelVersion(request: IChatRequestModel) { + this._versionByRequest.set(request.id, this.textModelN.getAlternativeVersionId()); } - get exchanges(): readonly SessionExchange[] { - return this._exchange; + get versionsByRequest() { + return Array.from(this._versionByRequest); } - get lastExchange(): SessionExchange | undefined { - return this._exchange[this._exchange.length - 1]; + async undoChangesUntil(requestId: string): Promise { + + const targetAltVersion = this._versionByRequest.get(requestId); + if (targetAltVersion === undefined) { + return false; + } + // undo till this point + this.hunkData.ignoreTextModelNChanges = true; + try { + while (targetAltVersion < this.textModelN.getAlternativeVersionId() && this.textModelN.canUndo()) { + await this.textModelN.undo(); + } + } finally { + this.hunkData.ignoreTextModelNChanges = false; + } + return true; } get hasChangedText(): boolean { @@ -254,119 +241,8 @@ export class Session { this._teldata.endTime = new Date().toISOString(); return this._teldata; } - - asRecording(): Recording { - const result: Recording = { - session: this.session, - when: this._startTime, - exchanges: [] - }; - for (const exchange of this._exchange) { - const response = exchange.response; - if (response instanceof ReplyResponse) { - result.exchanges.push({ prompt: exchange.prompt.value, res: response.raw }); - } - } - return result; - } -} - - -export class SessionPrompt { - - readonly value: string; - - constructor( - readonly request: IChatRequestModel, - readonly modelAltVersionId: number, - ) { - this.value = request.message.text; - } -} - -export class SessionExchange { - - constructor( - readonly prompt: SessionPrompt, - readonly response: ReplyResponse | EmptyResponse | ErrorResponse - ) { } -} - -export class EmptyResponse { - -} - -export class ErrorResponse { - - readonly message: string; - readonly isCancellation: boolean; - - constructor( - readonly error: any - ) { - this.message = toErrorMessage(error, false); - this.isCancellation = isCancellationError(error); - } } -export class ReplyResponse { - - readonly untitledTextModel: IUntitledTextEditorModel | undefined; - - constructor( - readonly raw: IInlineChatResponse, - localUri: URI, - readonly modelAltVersionId: number, - readonly chatRequest: IChatRequestModel, - readonly chatResponse: IChatResponseModel, - @ITextFileService private readonly _textFileService: ITextFileService, - @ILanguageService private readonly _languageService: ILanguageService, - ) { - - const editsMap = new ResourceMap(); - const edits = ResourceEdit.convert(raw.edits); - - for (const edit of edits) { - if (edit instanceof ResourceFileEdit) { - if (edit.newResource && !edit.oldResource) { - editsMap.set(edit.newResource, []); - if (edit.options.contents) { - console.warn('CONTENT not supported'); - } - } - } else if (edit instanceof ResourceTextEdit) { - // - const array = editsMap.get(edit.resource); - if (array) { - array.push([edit.textEdit]); - } else { - editsMap.set(edit.resource, [[edit.textEdit]]); - } - } - } - - - for (const [uri, edits] of editsMap) { - - const flatEdits = edits.flat(); - if (flatEdits.length === 0) { - editsMap.delete(uri); - continue; - } - - const isLocalUri = isEqual(uri, localUri); - if (uri.scheme === Schemas.untitled && !isLocalUri && !this.untitledTextModel) { //TODO@jrieken the first untitled model WINS - const langSelection = this._languageService.createByFilepathOrFirstLine(uri, undefined); - const untitledTextModel = this._textFileService.untitled.create({ - associatedResource: uri, - languageId: langSelection.languageId - }); - this.untitledTextModel = untitledTextModel; - untitledTextModel.resolve(); - } - } - } -} export class StashedSession { @@ -387,7 +263,7 @@ export class StashedSession { // keep session for a little bit, only release when user continues to work (type, move cursor, etc.) this._session = session; this._ctxHasStashedSession.set(true); - this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { + this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel, editor.onDidBlurEditorWidget))(() => { this._session = undefined; this._sessionService.releaseSession(session); this._ctxHasStashedSession.reset(); @@ -484,27 +360,30 @@ export class HunkData { // mirror textModelN changes to textModel0 execept for those that // overlap with a hunk - type HunkRangePair = { rangeN: Range; range0: Range }; + type HunkRangePair = { rangeN: Range; range0: Range; markAccepted: () => void }; const hunkRanges: HunkRangePair[] = []; const ranges0: Range[] = []; - for (const { textModelNDecorations, textModel0Decorations, state } of this._data.values()) { + for (const entry of this._data.values()) { - if (state === HunkState.Pending) { + if (entry.state === HunkState.Pending) { // pending means the hunk's changes aren't "sync'd" yet - for (let i = 1; i < textModelNDecorations.length; i++) { - const rangeN = this._textModelN.getDecorationRange(textModelNDecorations[i]); - const range0 = this._textModel0.getDecorationRange(textModel0Decorations[i]); + for (let i = 1; i < entry.textModelNDecorations.length; i++) { + const rangeN = this._textModelN.getDecorationRange(entry.textModelNDecorations[i]); + const range0 = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]); if (rangeN && range0) { - hunkRanges.push({ rangeN, range0 }); + hunkRanges.push({ + rangeN, range0, + markAccepted: () => entry.state = HunkState.Accepted + }); } } - } else if (state === HunkState.Accepted) { + } else if (entry.state === HunkState.Accepted) { // accepted means the hunk's changes are also in textModel0 - for (let i = 1; i < textModel0Decorations.length; i++) { - const range = this._textModel0.getDecorationRange(textModel0Decorations[i]); + for (let i = 1; i < entry.textModel0Decorations.length; i++) { + const range = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]); if (range) { ranges0.push(range); } @@ -523,16 +402,20 @@ export class HunkData { let pendingChangesLen = 0; - for (const { rangeN, range0 } of hunkRanges) { - if (rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { + for (const entry of hunkRanges) { + if (entry.rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { // pending hunk _before_ this change. When projecting into textModel0 we need to // subtract that. Because diffing is relaxed it might include changes that are not // actual insertions/deletions. Therefore we need to take the length of the original // range into account. - pendingChangesLen += this._textModelN.getValueLengthInRange(rangeN); - pendingChangesLen -= this._textModel0.getValueLengthInRange(range0); - - } else if (Range.areIntersectingOrTouching(rangeN, change.range)) { + pendingChangesLen += this._textModelN.getValueLengthInRange(entry.rangeN); + pendingChangesLen -= this._textModel0.getValueLengthInRange(entry.range0); + + } else if (Range.areIntersectingOrTouching(entry.rangeN, change.range)) { + // an edit overlaps with a (pending) hunk. We take this as a signal + // to mark the hunk as accepted and to ignore the edit. The range of the hunk + // will be up-to-date because of decorations created for them + entry.markAccepted(); isOverlapping = true; break; @@ -567,33 +450,34 @@ export class HunkData { this._textModel0.pushEditOperations(null, edits, () => null); } - async recompute(editState: IChatTextEditGroupState) { + async recompute(editState: IChatTextEditGroupState, diff?: IDocumentDiff | null) { - const diff = await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced'); + diff ??= await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced'); - if (!diff || diff.changes.length === 0) { - // return new HunkData([], session); - return; - } + let mergedChanges: DetailedLineRangeMapping[] = []; - // merge changes neighboring changes - const mergedChanges = [diff.changes[0]]; - for (let i = 1; i < diff.changes.length; i++) { - const lastChange = mergedChanges[mergedChanges.length - 1]; - const thisChange = diff.changes[i]; - if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { - mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( - lastChange.original.join(thisChange.original), - lastChange.modified.join(thisChange.modified), - (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) - ); - } else { - mergedChanges.push(thisChange); + if (diff && diff.changes.length > 0) { + // merge changes neighboring changes + mergedChanges = [diff.changes[0]]; + for (let i = 1; i < diff.changes.length; i++) { + const lastChange = mergedChanges[mergedChanges.length - 1]; + const thisChange = diff.changes[i]; + if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { + mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( + lastChange.original.join(thisChange.original), + lastChange.modified.join(thisChange.modified), + (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) + ); + } else { + mergedChanges.push(thisChange); + } } } const hunks = mergedChanges.map(change => new RawHunk(change.original, change.modified, change.innerChanges ?? [])); + editState.applied = hunks.length; + this._textModelN.changeDecorations(accessorN => { this._textModel0.changeDecorations(accessor0 => { @@ -696,6 +580,9 @@ export class HunkData { const edits = this._discardEdits(item); this._textModelN.pushEditOperations(null, edits, () => null); data.state = HunkState.Rejected; + if (data.editState.applied > 0) { + data.editState.applied -= 1; + } } }, acceptChanges: () => { @@ -712,7 +599,6 @@ export class HunkData { } this._textModel0.pushEditOperations(null, edits, () => null); data.state = HunkState.Accepted; - data.editState.applied += 1; } } }; diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts index 638f0858..221addc3 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionService.ts @@ -2,23 +2,16 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; -import { EditMode, IInlineChatSession, IInlineChatResponse } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IRange } from 'vs/editor/common/core/range'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IRange } from 'vs/editor/common/core/range'; +import { IValidEditOperation } from 'vs/editor/common/model'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { EditMode } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Session, StashedSession } from './inlineChatSession'; -import { IValidEditOperation } from 'vs/editor/common/model'; - - -export type Recording = { - when: Date; - session: IInlineChatSession; - exchanges: { prompt: string; res: IInlineChatResponse }[]; -}; export interface ISessionKeyComputer { getComparisonKey(editor: ICodeEditor, uri: URI): string; @@ -43,7 +36,7 @@ export interface IInlineChatSessionService { onDidStashSession: Event; onDidEndSession: Event; - createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange }, token: CancellationToken): Promise; + createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: IRange; session?: Session; headless?: boolean }, token: CancellationToken): Promise; moveSession(session: Session, newEditor: ICodeEditor): void; @@ -57,8 +50,5 @@ export interface IInlineChatSessionService { registerSessionKeyComputer(scheme: string, value: ISessionKeyComputer): IDisposable; - // - recordings(): readonly Recording[]; - dispose(): void; } diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 5a890022..484681d7 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -3,33 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { CancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IRange, Range } from 'vs/editor/common/core/range'; +import { Range } from 'vs/editor/common/core/range'; import { IValidEditOperation } from 'vs/editor/common/model'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { CTX_INLINE_CHAT_HAS_AGENT, EditMode, IInlineChatResponse, IInlineChatSession } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_HAS_AGENT, EditMode } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; -import { IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer, Recording } from './inlineChatSessionService'; -import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { ISelection } from 'vs/editor/common/core/selection'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { HunkData, Session, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; +import { IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer } from './inlineChatSessionService'; +import { isEqual } from 'vs/base/common/resources'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; type SessionData = { @@ -46,19 +46,6 @@ export class InlineChatError extends Error { } } -const _inlineChatContext = '_inlineChatContext'; -const _inlineChatDocument = '_inlineChatDocument'; - -class InlineChatContext { - - static readonly variableName = '_inlineChatContext'; - - constructor( - readonly uri: URI, - readonly selection: ISelection, - readonly wholeRange: IRange, - ) { } -} export class InlineChatSessionServiceImpl implements IInlineChatSessionService { @@ -80,8 +67,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { private readonly _sessions = new Map(); private readonly _keyComputers = new Map(); - private _recordings: Recording[] = []; - constructor( @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -91,38 +76,11 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { @ILogService private readonly _logService: ILogService, @IInstantiationService private readonly _instaService: IInstantiationService, @IEditorService private readonly _editorService: IEditorService, + @ITextFileService private readonly _textFileService: ITextFileService, + @ILanguageService private readonly _languageService: ILanguageService, @IChatService private readonly _chatService: IChatService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService, - @IChatVariablesService chatVariableService: IChatVariablesService, - ) { - - - // MARK: implicit variable for editor selection and (tracked) whole range - - this._store.add(chatVariableService.registerVariable( - { id: _inlineChatContext, name: _inlineChatContext, description: '', hidden: true }, - async (_message, _arg, model) => { - for (const [, data] of this._sessions) { - if (data.session.chatModel === model) { - return JSON.stringify(new InlineChatContext(data.session.textModelN.uri, data.editor.getSelection()!, data.session.wholeRange.trackedInitialRange)); - } - } - return undefined; - } - )); - this._store.add(chatVariableService.registerVariable( - { id: _inlineChatDocument, name: _inlineChatDocument, description: '', hidden: true }, - async (_message, _arg, model) => { - for (const [, data] of this._sessions) { - if (data.session.chatModel === model) { - return data.session.textModelN.uri; - } - } - return undefined; - } - )); - - } + @IChatAgentService private readonly _chatAgentService: IChatAgentService + ) { } dispose() { this._store.dispose(); @@ -130,7 +88,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._sessions.clear(); } - async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise { + async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; headless?: boolean; wholeRange?: Range; session?: Session }, token: CancellationToken): Promise { const agent = this._chatAgentService.getDefaultAgent(ChatAgentLocation.Editor); @@ -139,31 +97,27 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { return undefined; } - this._onWillStartSession.fire(editor); const textModel = editor.getModel(); const selection = editor.getSelection(); - const rawSession: IInlineChatSession = { - id: Math.random(), - wholeRange: new Range(selection.selectionStartLineNumber, selection.selectionStartColumn, selection.positionLineNumber, selection.positionColumn), - placeholder: agent.description, - slashCommands: agent.slashCommands - }; - const store = new DisposableStore(); this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${agent.extensionId}`); - const chatModel = this._chatService.startSession(ChatAgentLocation.Editor, token); + const chatModel = options.session?.chatModel ?? this._chatService.startSession(ChatAgentLocation.Editor, token); if (!chatModel) { this._logService.trace('[IE] NO chatModel found'); return undefined; } store.add(toDisposable(() => { - this._chatService.clearSession(chatModel.sessionId); - chatModel.dispose(); + const doesOtherSessionUseChatModel = [...this._sessions.values()].some(data => data.session !== session && data.session.chatModel === chatModel); + + if (!doesOtherSessionUseChatModel) { + this._chatService.clearSession(chatModel.sessionId); + chatModel.dispose(); + } })); const lastResponseListener = store.add(new MutableDisposable()); @@ -172,10 +126,9 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { return; } - const modelAltVersionIdNow = textModel.getAlternativeVersionId(); - const { response } = e.request; + session.markModelVersion(e.request); lastResponseListener.value = response.onDidChange(() => { if (!response.isComplete) { @@ -184,54 +137,22 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { lastResponseListener.clear(); // ONCE - let inlineResponse: ErrorResponse | EmptyResponse | ReplyResponse; - - // make an response from the ChatResponseModel - if (response.isCanceled) { - // error: cancelled - inlineResponse = new ErrorResponse(new CancellationError()); - } else if (response.result?.errorDetails) { - // error: "real" error - inlineResponse = new ErrorResponse(new Error(response.result.errorDetails.message)); - } else if (response.response.value.length === 0) { - // epmty response - inlineResponse = new EmptyResponse(); - } else { - // replay response - const raw: IInlineChatResponse = { - edits: { edits: [] }, - }; - for (const item of response.response.value) { - if (item.kind === 'textEditGroup') { - for (const group of item.edits) { - for (const edit of group) { - raw.edits.edits.push({ - resource: item.uri, - textEdit: edit, - versionId: undefined - }); - } - } - } + // special handling for untitled files + for (const part of response.response.value) { + if (part.kind !== 'textEditGroup' || part.uri.scheme !== Schemas.untitled || isEqual(part.uri, session.textModelN.uri)) { + continue; } - - inlineResponse = this._instaService.createInstance( - ReplyResponse, - raw, - session.textModelN.uri, - modelAltVersionIdNow, - e.request, - response - ); - } - - session.addExchange(new SessionExchange(session.lastInput!, inlineResponse)); - - if (inlineResponse instanceof ReplyResponse && inlineResponse.untitledTextModel) { - this._textModelService.createModelReference(inlineResponse.untitledTextModel.resource).then(ref => { + const langSelection = this._languageService.createByFilepathOrFirstLine(part.uri, undefined); + const untitledTextModel = this._textFileService.untitled.create({ + associatedResource: part.uri, + languageId: langSelection.languageId + }); + untitledTextModel.resolve(); + this._textModelService.createModelReference(part.uri).then(ref => { store.add(ref); }); } + }); })); @@ -267,7 +188,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { let wholeRange = options.wholeRange; if (!wholeRange) { - wholeRange = rawSession.wholeRange ? Range.lift(rawSession.wholeRange) : editor.getSelection(); + wholeRange = new Range(selection.selectionStartLineNumber, selection.selectionStartColumn, selection.positionLineNumber, selection.positionColumn); } if (token.isCancellationRequested) { @@ -277,14 +198,15 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const session = new Session( options.editMode, + options.headless ?? false, targetUri, textModel0, textModelN, agent, - rawSession, store.add(new SessionWholeRange(textModelN, wholeRange)), store.add(new HunkData(this._editorWorkerService, textModel0, textModelN)), - chatModel + chatModel, + options.session?.versionsByRequest, ); // store: key -> session @@ -347,7 +269,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { return; } - this._keepRecording(session); this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); const [key, value] = tuple; @@ -359,7 +280,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { } stashSession(session: Session, editor: ICodeEditor, undoCancelEdits: IValidEditOperation[]): StashedSession { - this._keepRecording(session); const result = this._instaService.createInstance(StashedSession, editor, session, undoCancelEdits); this._onDidStashSession.fire({ editor, session }); this._logService.trace(`[IE] did STASH session for ${editor.getId()}, ${session.agent.extensionId}`); @@ -392,19 +312,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._keyComputers.set(scheme, value); return toDisposable(() => this._keyComputers.delete(scheme)); } - - // --- debug - - private _keepRecording(session: Session) { - const newLen = this._recordings.unshift(session.asRecording()); - if (newLen > 5) { - this._recordings.pop(); - } - } - - recordings(): readonly Recording[] { - return this._recordings; - } } export class InlineChatEnabler { @@ -413,18 +320,21 @@ export class InlineChatEnabler { private readonly _ctxHasProvider: IContextKey; + private readonly _store = new DisposableStore(); + constructor( @IContextKeyService contextKeyService: IContextKeyService, @IChatAgentService chatAgentService: IChatAgentService ) { this._ctxHasProvider = CTX_INLINE_CHAT_HAS_AGENT.bindTo(contextKeyService); - chatAgentService.onDidChangeAgents(() => { + this._store.add(chatAgentService.onDidChangeAgents(() => { const hasEditorAgent = Boolean(chatAgentService.getDefaultAgent(ChatAgentLocation.Editor)); this._ctxHasProvider.set(hasEditorAgent); - }); + })); } dispose() { this._ctxHasProvider.reset(); + this._store.dispose(); } } diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index c98fcfa3..01df414e 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WindowIntervalTimer } from 'vs/base/browser/dom'; +import { getTotalWidth, WindowIntervalTimer } from 'vs/base/browser/dom'; import { coalesceInPlace } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { themeColorFromId } from 'vs/base/common/themables'; -import { ICodeEditor, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; +import { themeColorFromId, ThemeIcon } from 'vs/base/common/themables'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { LineSource, RenderOptions, renderLines } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; @@ -21,15 +21,13 @@ import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, IValidEditOpera import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel'; -import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Progress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; -import { HunkInformation, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { HunkInformation, Session, HunkState } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from './inlineChatZoneWidget'; -import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, minimapInlineChatDiffInserted, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { HunkState } from './inlineChatSession'; +import { ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, MENU_INLINE_CHAT_ZONE, minimapInlineChatDiffInserted, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; @@ -41,19 +39,33 @@ import { Schemas } from 'vs/base/common/network'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DefaultChatTextEditor } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { isEqual } from 'vs/base/common/resources'; +import { generateUuid } from 'vs/base/common/uuid'; +import { MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Iterable } from 'vs/base/common/iterator'; +import { ConflictActionsFactory, IContentWidgetAction } from 'vs/workbench/contrib/mergeEditor/browser/view/conflictActions'; +import { observableValue } from 'vs/base/common/observable'; +import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; export interface IEditObserver { start(): void; stop(): void; } +export const enum HunkAction { + Accept, + Discard, + MoveNext, + MovePrev, + ToggleDiff +} + export abstract class EditModeStrategy { protected static _decoBlock = ModelDecorationOptions.register({ description: 'inline-chat', showIfCollapsed: false, isWholeLine: true, - className: 'inline-chat-block-selection', }); protected readonly _store = new DisposableStore(); @@ -64,20 +76,26 @@ export abstract class EditModeStrategy { readonly onDidAccept: Event = this._onDidAccept.event; readonly onDidDiscard: Event = this._onDidDiscard.event; - toggleDiff?: () => any; - constructor( protected readonly _session: Session, protected readonly _editor: ICodeEditor, protected readonly _zone: InlineChatZoneWidget, @ITextFileService private readonly _textFileService: ITextFileService, - @IInstantiationService private readonly _instaService: IInstantiationService, + @IInstantiationService protected readonly _instaService: IInstantiationService, ) { } dispose(): void { this._store.dispose(); } + performHunkAction(_hunk: HunkInformation | undefined, action: HunkAction) { + if (action === HunkAction.Accept) { + this._onDidAccept.fire(); + } else if (action === HunkAction.Discard) { + this._onDidDiscard.fire(); + } + } + protected async _doApplyChanges(ignoreLocal: boolean): Promise { const untitledModels: IUntitledTextEditorModel[] = []; @@ -124,21 +142,13 @@ export abstract class EditModeStrategy { return this._session.hunkData.discardAll(); } - async acceptHunk(): Promise { - this._onDidAccept.fire(); - } - async discardHunk(): Promise { - this._onDidDiscard.fire(); - } abstract makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, timings: ProgressingEditsOptions, undoStopBefore: boolean): Promise; abstract makeChanges(edits: ISingleEditOperation[], obs: IEditObserver, undoStopBefore: boolean): Promise; - abstract renderChanges(response: ReplyResponse): Promise; - - move?(next: boolean): void; + abstract renderChanges(): Promise; abstract hasFocus(): boolean; @@ -190,7 +200,7 @@ export class PreviewStrategy extends EditModeStrategy { override async makeProgressiveChanges(): Promise { } - override async renderChanges(response: ReplyResponse): Promise { } + override async renderChanges(): Promise { } hasFocus(): boolean { return this._zone.widget.hasFocus(); @@ -209,8 +219,10 @@ type HunkDisplayData = { decorationIds: string[]; - viewZoneId: string | undefined; - viewZone: IViewZone; + diffViewZoneId: string | undefined; + diffViewZone: IViewZone; + + lensActionsViewZoneIds?: string[]; distance: number; position: Position; @@ -250,19 +262,20 @@ export class LiveStrategy extends EditModeStrategy { private readonly _ctxCurrentChangeShowsDiff: IContextKey; private readonly _progressiveEditingDecorations: IEditorDecorationsCollection; + private readonly _lensActionsFactory: ConflictActionsFactory; private _editCount: number = 0; - override acceptHunk: () => Promise = () => super.acceptHunk(); - override discardHunk: () => Promise = () => super.discardHunk(); - constructor( session: Session, editor: ICodeEditor, zone: InlineChatZoneWidget, + private readonly _showOverlayToolbar: boolean, @IContextKeyService contextKeyService: IContextKeyService, @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configService: IConfigurationService, + @IMenuService private readonly _menuService: IMenuService, + @IContextKeyService private readonly _contextService: IContextKeyService, @ITextFileService textFileService: ITextFileService, @IInstantiationService instaService: IInstantiationService ) { @@ -271,6 +284,7 @@ export class LiveStrategy extends EditModeStrategy { this._ctxCurrentChangeShowsDiff = CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF.bindTo(contextKeyService); this._progressiveEditingDecorations = this._editor.createDecorationsCollection(); + this._lensActionsFactory = this._store.add(new ConflictActionsFactory(this._editor)); } @@ -362,9 +376,70 @@ export class LiveStrategy extends EditModeStrategy { } } + override performHunkAction(hunk: HunkInformation | undefined, action: HunkAction) { + const displayData = this._findDisplayData(hunk); + + if (!displayData) { + // no hunks (left or not yet) found, make sure to + // finish the sessions + if (action === HunkAction.Accept) { + this._onDidAccept.fire(); + } else if (action === HunkAction.Discard) { + this._onDidDiscard.fire(); + } + return; + } + + if (action === HunkAction.Accept) { + displayData.acceptHunk(); + } else if (action === HunkAction.Discard) { + displayData.discardHunk(); + } else if (action === HunkAction.MoveNext) { + displayData.move(true); + } else if (action === HunkAction.MovePrev) { + displayData.move(false); + } else if (action === HunkAction.ToggleDiff) { + displayData.toggleDiff?.(); + } + } + + private _findDisplayData(hunkInfo?: HunkInformation) { + let result: HunkDisplayData | undefined; + if (hunkInfo) { + // use context hunk (from tool/buttonbar) + result = this._hunkDisplayData.get(hunkInfo); + } + + if (!result && this._zone.position) { + // find nearest from zone position + const zoneLine = this._zone.position.lineNumber; + let distance: number = Number.MAX_SAFE_INTEGER; + for (const candidate of this._hunkDisplayData.values()) { + if (candidate.hunk.getState() !== HunkState.Pending) { + continue; + } + const hunkRanges = candidate.hunk.getRangesN(); + const myDistance = zoneLine <= hunkRanges[0].startLineNumber + ? hunkRanges[0].startLineNumber - zoneLine + : zoneLine - hunkRanges[0].endLineNumber; + + if (myDistance < distance) { + distance = myDistance; + result = candidate; + } + } + } + + if (!result) { + // fallback: first hunk that is pending + result = Iterable.first(Iterable.filter(this._hunkDisplayData.values(), candidate => candidate.hunk.getState() === HunkState.Pending)); + } + return result; + } + private readonly _hunkDisplayData = new Map(); - override async renderChanges(response: ReplyResponse) { + override async renderChanges() { this._progressiveEditingDecorations.clear(); @@ -421,67 +496,110 @@ export class LiveStrategy extends EditModeStrategy { afterLineNumber: -1, heightInLines: result.heightInLines, domNode, + ordinal: 50000 + 2 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 }; const toggleDiff = () => { const scrollState = StableEditorScrollState.capture(this._editor); changeDecorationsAndViewZones(this._editor, (_decorationsAccessor, viewZoneAccessor) => { assertType(data); - if (!data.viewZoneId) { + if (!data.diffViewZoneId) { const [hunkRange] = hunkData.getRangesN(); viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1; - data.viewZoneId = viewZoneAccessor.addZone(viewZoneData); + data.diffViewZoneId = viewZoneAccessor.addZone(viewZoneData); + overlay?.updateExtraTop(result.heightInLines); } else { - viewZoneAccessor.removeZone(data.viewZoneId!); - data.viewZoneId = undefined; + viewZoneAccessor.removeZone(data.diffViewZoneId!); + overlay?.updateExtraTop(0); + data.diffViewZoneId = undefined; } }); - this._ctxCurrentChangeShowsDiff.set(typeof data?.viewZoneId === 'string'); + this._ctxCurrentChangeShowsDiff.set(typeof data?.diffViewZoneId === 'string'); scrollState.restore(this._editor); }; + const overlay = this._showOverlayToolbar && false + ? this._instaService.createInstance(InlineChangeOverlay, this._editor, hunkData) + : undefined; + + + let lensActions: DisposableStore | undefined; + const lensActionsViewZoneIds: string[] = []; + + if (this._showOverlayToolbar && hunkData.getState() === HunkState.Pending) { + + lensActions = new DisposableStore(); + + const menu = this._menuService.createMenu(MENU_INLINE_CHAT_ZONE, this._contextService); + const makeActions = () => { + const actions: IContentWidgetAction[] = []; + const tuples = menu.getActions(); + for (const [, group] of tuples) { + for (const item of group) { + if (item instanceof MenuItemAction) { + + let text = item.label; + + if (item.id === ACTION_TOGGLE_DIFF) { + text = item.checked ? 'Hide Changes' : 'Show Changes'; + } else if (ThemeIcon.isThemeIcon(item.item.icon)) { + text = `$(${item.item.icon.id}) ${text}`; + } + + actions.push({ + text, + tooltip: item.tooltip, + action: async () => item.run(), + }); + } + } + } + return actions; + }; + + const obs = observableValue(this, makeActions()); + lensActions.add(menu.onDidChange(() => obs.set(makeActions(), undefined))); + lensActions.add(menu); + + lensActions.add(this._lensActionsFactory.createWidget(viewZoneAccessor, + hunkRanges[0].startLineNumber - 1, + obs, + lensActionsViewZoneIds + )); + } + const remove = () => { changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { assertType(data); for (const decorationId of data.decorationIds) { decorationsAccessor.removeDecoration(decorationId); } - if (data.viewZoneId) { - viewZoneAccessor.removeZone(data.viewZoneId); + if (data.diffViewZoneId) { + viewZoneAccessor.removeZone(data.diffViewZoneId); } data.decorationIds = []; - data.viewZoneId = undefined; + data.diffViewZoneId = undefined; + + data.lensActionsViewZoneIds?.forEach(viewZoneAccessor.removeZone); + data.lensActionsViewZoneIds = undefined; }); + + lensActions?.dispose(); + overlay?.dispose(); }; const move = (next: boolean) => { - assertType(widgetData); - - const candidates: Position[] = []; - for (const item of this._session.hunkData.getInfo()) { - if (item.getState() === HunkState.Pending) { - candidates.push(item.getRangesN()[0].getStartPosition().delta(-1)); - } - } - if (candidates.length < 2) { - return; - } - for (let i = 0; i < candidates.length; i++) { - if (candidates[i].equals(widgetData.position)) { - let newPos: Position; - if (next) { - newPos = candidates[(i + 1) % candidates.length]; - } else { - newPos = candidates[(i + candidates.length - 1) % candidates.length]; - } - this._zone.updatePositionAndHeight(newPos); - renderHunks(); - break; - } + const keys = Array.from(this._hunkDisplayData.keys()); + const idx = keys.indexOf(hunkData); + const nextIdx = (idx + (next ? 1 : -1) + keys.length) % keys.length; + if (nextIdx !== idx) { + const nextData = this._hunkDisplayData.get(keys[nextIdx])!; + this._zone.updatePositionAndHeight(nextData?.position); + renderHunks(); } }; - const zoneLineNumber = this._zone.position!.lineNumber; + const zoneLineNumber = this._zone.position?.lineNumber ?? this._editor.getPosition()!.lineNumber; const myDistance = zoneLineNumber <= hunkRanges[0].startLineNumber ? hunkRanges[0].startLineNumber - zoneLineNumber : zoneLineNumber - hunkRanges[0].endLineNumber; @@ -489,8 +607,9 @@ export class LiveStrategy extends EditModeStrategy { data = { hunk: hunkData, decorationIds, - viewZoneId: '', - viewZone: viewZoneData, + diffViewZoneId: '', + diffViewZone: viewZoneData, + lensActionsViewZoneIds, distance: myDistance, position: hunkRanges[0].getStartPosition().delta(-1), acceptHunk, @@ -507,7 +626,7 @@ export class LiveStrategy extends EditModeStrategy { } else { // update distance and position based on modifiedRange-decoration - const zoneLineNumber = this._zone.position!.lineNumber; + const zoneLineNumber = this._zone.position?.lineNumber ?? this._editor.getPosition()!.lineNumber; const modifiedRangeNow = hunkRanges[0]; data.position = modifiedRangeNow.getStartPosition().delta(-1); data.distance = zoneLineNumber <= modifiedRangeNow.startLineNumber @@ -531,10 +650,6 @@ export class LiveStrategy extends EditModeStrategy { if (widgetData) { this._zone.updatePositionAndHeight(widgetData.position); - this._editor.revealPositionInCenterIfOutsideViewport(widgetData.position); - - const remainingHunks = this._session.hunkData.pending; - this._updateSummaryMessage(remainingHunks, this._session.hunkData.size); const mode = this._configService.getValue<'on' | 'off' | 'auto'>(InlineChatConfigKeys.AccessibleDiffView); @@ -543,10 +658,6 @@ export class LiveStrategy extends EditModeStrategy { } this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff)); - this.toggleDiff = widgetData.toggleDiff; - this.acceptHunk = async () => widgetData!.acceptHunk(); - this.discardHunk = async () => widgetData!.discardHunk(); - this.move = next => widgetData!.move(next); } else if (this._hunkDisplayData.size > 0) { // everything accepted or rejected @@ -570,30 +681,6 @@ export class LiveStrategy extends EditModeStrategy { return renderHunks()?.position; } - private _updateSummaryMessage(remaining: number, total: number) { - - const needsReview = this._configService.getValue(InlineChatConfigKeys.AcceptedOrDiscardBeforeSave); - let message: string; - if (total === 0) { - message = localize('change.0', "Nothing changed."); - } else if (remaining === 1) { - message = needsReview - ? localize('review.1', "$(info) Accept or discard 1 change") - : localize('change.1', "1 change"); - } else { - message = needsReview - ? localize('review.N', "$(info) Accept or Discard {0} changes", remaining) - : localize('change.N', "{0} changes", total); - } - - let title: string | undefined; - if (needsReview) { - title = localize('review', "Review (accept or discard) all changes before continuing"); - } - - this._zone.widget.updateStatus(message, { title }); - } - hasFocus(): boolean { return this._zone.widget.hasFocus(); } @@ -611,3 +698,78 @@ function changeDecorationsAndViewZones(editor: ICodeEditor, callback: (accessor: }); }); } + + +class InlineChangeOverlay implements IOverlayWidget { + + readonly allowEditorOverflow: boolean = false; + + private readonly _id: string = `inline-chat-diff-overlay-` + generateUuid(); + private readonly _domNode: HTMLElement = document.createElement('div'); + private readonly _store: DisposableStore = new DisposableStore(); + + private _extraTopLines: number = 0; + + constructor( + private readonly _editor: ICodeEditor, + private readonly _hunkInfo: HunkInformation, + @IInstantiationService private readonly _instaService: IInstantiationService, + ) { + + this._domNode.classList.add('inline-chat-diff-overlay'); + + if (_hunkInfo.getState() === HunkState.Pending) { + + const menuBar = this._store.add(this._instaService.createInstance(MenuWorkbenchButtonBar, this._domNode, MENU_INLINE_CHAT_ZONE, { + menuOptions: { arg: _hunkInfo }, + telemetrySource: 'inlineChat-changesZone', + buttonConfigProvider: (_action, idx) => { + return { + isSecondary: idx > 0, + showIcon: true, + showLabel: false + }; + }, + })); + + this._store.add(menuBar.onDidChange(() => this._editor.layoutOverlayWidget(this))); + } + + this._editor.addOverlayWidget(this); + this._store.add(Event.any(this._editor.onDidLayoutChange, this._editor.onDidScrollChange)(() => this._editor.layoutOverlayWidget(this))); + queueMicrotask(() => this._editor.layoutOverlayWidget(this)); // FUNKY but needed to get the initial layout right + } + + dispose(): void { + this._editor.removeOverlayWidget(this); + this._store.dispose(); + } + + getId(): string { + return this._id; + } + + getDomNode(): HTMLElement { + return this._domNode; + } + + getPosition(): IOverlayWidgetPosition | null { + + const line = this._hunkInfo.getRangesN()[0].startLineNumber; + const info = this._editor.getLayoutInfo(); + const top = this._editor.getTopForLineNumber(line) - this._editor.getScrollTop(); + const left = info.contentLeft + info.contentWidth - info.verticalScrollbarWidth; + + const extraTop = this._editor.getOption(EditorOption.lineHeight) * this._extraTopLines; + const width = getTotalWidth(this._domNode); + + return { preference: { top: top - extraTop, left: left - width } }; + } + + updateExtraTop(value: number) { + if (this._extraTopLines !== value) { + this._extraTopLines = value; + this._editor.layoutOverlayWidget(this); + } + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index f7149145..e0423850 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { Dimension, getActiveElement, getTotalHeight, h, reset, trackFocus } from 'vs/base/browser/dom'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { IAction } from 'vs/base/common/actions'; +import { isNonEmptyArray, tail } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { ISettableObservable, constObservable, derived, observableValue } from 'vs/base/common/observable'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { constObservable, derived, ISettableObservable, observableValue } from 'vs/base/common/observable'; import 'vs/css!./media/inlineChat'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; @@ -17,37 +20,39 @@ import { EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/ed import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; +import { IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { MenuId } from 'vs/platform/actions/common/actions'; +import { createActionViewItem, IMenuEntryActionViewItemOptions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { asCssVariable, asCssVariableName, editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; +import { asCssVariable, asCssVariableName, editorBackground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; +import { MarkUnhelpfulActionId } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions'; +import { IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatVoteDownButton } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { ChatWidget, IChatWidgetLocationOptions } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { chatRequestBackground } from 'vs/workbench/contrib/chat/common/chatColors'; +import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { ChatAgentVoteDirection, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; -import { chatRequestBackground } from 'vs/workbench/contrib/chat/common/chatColors'; -import { Selection } from 'vs/editor/common/core/selection'; -import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { isNonEmptyArray, tail } from 'vs/base/common/arrays'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; -import { IChatListItemRendererOptions } from 'vs/workbench/contrib/chat/browser/chat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground, inlineChatForeground } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; export interface InlineChatWidgetViewState { @@ -57,30 +62,18 @@ export interface InlineChatWidgetViewState { } export interface IInlineChatWidgetConstructionOptions { - /** - * The telemetry source for all commands of this widget - */ - telemetrySource: string; - /** - * The menu that is inside the input editor, use for send, dictation - */ - inputMenuId: MenuId; - /** - * The menu that next to the input editor, use for close, config etc - */ - widgetMenuId: MenuId; + /** * The menu that rendered as button bar, use for accept, discard etc */ statusMenuId: MenuId | { menu: MenuId; options: IWorkbenchButtonBarOptions }; - /** - * The men that rendered in the lower right corner, use for feedback - */ - feedbackMenuId?: MenuId; - editorOverflowWidgetsDomNode?: HTMLElement; + secondaryMenuId?: MenuId; - rendererOptions?: IChatListItemRendererOptions; + /** + * The options for the chat widget + */ + chatWidgetViewOptions?: IChatWidgetViewOptions; } export interface IInlineChatMessage { @@ -100,15 +93,12 @@ export class InlineChatWidget { 'div.inline-chat@root', [ h('div.chat-widget@chatWidget'), - h('div.progress@progress'), - h('div.followUps.hidden@followUps'), - h('div.previewDiff.hidden@previewDiff'), h('div.accessibleViewer@accessibleViewer'), h('div.status@status', [ h('div.label.info.hidden@infoLabel'), - h('div.actions.hidden@statusToolbar'), + h('div.actions.hidden@toolbar1'), h('div.label.status.hidden@statusLabel'), - h('div.actions.hidden@feedbackToolbar'), + h('div.actions.secondary.hidden@toolbar2'), ]), ] ); @@ -119,7 +109,6 @@ export class InlineChatWidget { private readonly _ctxInputEditorFocused: IContextKey; private readonly _ctxResponseFocused: IContextKey; - private readonly _progressBar: ProgressBar; private readonly _chatWidget: ChatWidget; protected readonly _onDidChangeHeight = this._store.add(new Emitter()); @@ -133,7 +122,7 @@ export class InlineChatWidget { readonly scopedContextKeyService: IContextKeyService; constructor( - location: ChatAgentLocation, + location: IChatWidgetLocationOptions, options: IInlineChatWidgetConstructionOptions, @IInstantiationService protected readonly _instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -145,12 +134,6 @@ export class InlineChatWidget { @IChatService private readonly _chatService: IChatService, @IHoverService private readonly _hoverService: IHoverService, ) { - // toolbars - this._progressBar = new ProgressBar(this._elements.progress); - this._store.add(this._progressBar); - - let allowRequests = false; - this.scopedContextKeyService = this._store.add(_contextKeyService.createScoped(this._elements.chatWidget)); const scopedInstaService = _instantiationService.createChild( new ServiceCollection([ @@ -163,32 +146,36 @@ export class InlineChatWidget { this._chatWidget = scopedInstaService.createInstance( ChatWidget, location, - { resource: true }, + undefined, { defaultElementHeight: 32, - renderStyle: 'compact', - renderInputOnTop: true, + renderStyle: 'minimal', + renderInputOnTop: false, renderFollowups: true, - supportsFileReferences: true, - editorOverflowWidgetsDomNode: options.editorOverflowWidgetsDomNode, - rendererOptions: options.rendererOptions, - menus: { - executeToolbar: options.inputMenuId, - inputSideToolbar: options.widgetMenuId, - telemetrySource: options.telemetrySource - }, + supportsFileReferences: _configurationService.getValue(`chat.experimental.variables.${location.location}`) === true, filter: item => { if (isWelcomeVM(item)) { + // filter welcome messages return false; } - if (isRequestVM(item)) { - return allowRequests; + if (isResponseVM(item) && item.isComplete && !item.errorDetails) { + // filter responses that + // - are just text edits(prevents the "Made Edits") + // - are all empty + if (item.response.value.length > 0 && item.response.value.every(item => item.kind === 'textEditGroup' && options.chatWidgetViewOptions?.rendererOptions?.renderTextEditsAsSummary?.(item.uri))) { + return false; + } + if (item.response.value.length === 0) { + return false; + } + return true; } return true; }, + ...options.chatWidgetViewOptions }, { - listForeground: editorForeground, + listForeground: inlineChatForeground, listBackground: inlineChatBackground, inputEditorBackground: inputBackground, resultEditorBackground: editorBackground @@ -199,41 +186,43 @@ export class InlineChatWidget { this._chatWidget.setVisible(true); this._store.add(this._chatWidget); - const viewModelListener = this._store.add(new MutableDisposable()); - this._store.add(this._chatWidget.onDidChangeViewModel(() => { - const model = this._chatWidget.viewModel; - - if (!model) { - allowRequests = false; - viewModelListener.clear(); - return; - } - - const updateAllowRequestsFilter = () => { - let requestCount = 0; - for (const item of model.getItems()) { - if (isRequestVM(item)) { - if (++requestCount >= 2) { - break; - } - } - } - const newAllowRequest = requestCount >= 2; - if (newAllowRequest !== allowRequests) { - allowRequests = newAllowRequest; - this._chatWidget.refilter(); - } - }; - viewModelListener.value = model.onDidChange(updateAllowRequestsFilter); - })); + const ctxResponse = CONTEXT_RESPONSE.bindTo(this.scopedContextKeyService); + const ctxResponseVote = CONTEXT_RESPONSE_VOTE.bindTo(this.scopedContextKeyService); + const ctxResponseSupportIssues = CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING.bindTo(this.scopedContextKeyService); + const ctxResponseError = CONTEXT_RESPONSE_ERROR.bindTo(this.scopedContextKeyService); + const ctxResponseErrorFiltered = CONTEXT_RESPONSE_FILTERED.bindTo(this.scopedContextKeyService); const viewModelStore = this._store.add(new DisposableStore()); this._store.add(this._chatWidget.onDidChangeViewModel(() => { viewModelStore.clear(); + const viewModel = this._chatWidget.viewModel; - if (viewModel) { - viewModelStore.add(viewModel.onDidChange(() => this._onDidChangeHeight.fire())); + if (!viewModel) { + return; } + + viewModelStore.add(toDisposable(() => { + toolbar2.context = undefined; + ctxResponse.reset(); + ctxResponseVote.reset(); + ctxResponseError.reset(); + ctxResponseErrorFiltered.reset(); + ctxResponseSupportIssues.reset(); + })); + + viewModelStore.add(viewModel.onDidChange(() => { + + const last = viewModel.getItems().at(-1); + toolbar2.context = last; + + ctxResponse.set(isResponseVM(last)); + ctxResponseVote.set(isResponseVM(last) ? last.vote === ChatAgentVoteDirection.Down ? 'down' : last.vote === ChatAgentVoteDirection.Up ? 'up' : '' : ''); + ctxResponseError.set(isResponseVM(last) && last.errorDetails !== undefined); + ctxResponseErrorFiltered.set((!!(isResponseVM(last) && last.errorDetails?.responseIsFiltered))); + ctxResponseSupportIssues.set(isResponseVM(last) && (last.agent?.metadata.supportIssueReporting ?? false)); + + this._onDidChangeHeight.fire(); + })); this._onDidChangeHeight.fire(); })); @@ -252,26 +241,32 @@ export class InlineChatWidget { this._store.add(this._chatWidget.inputEditor.onDidBlurEditorWidget(() => this._ctxInputEditorFocused.set(false))); const statusMenuId = options.statusMenuId instanceof MenuId ? options.statusMenuId : options.statusMenuId.menu; - const statusMenuOptions = options.statusMenuId instanceof MenuId ? undefined : options.statusMenuId.options; - const statusButtonBar = this._instantiationService.createInstance(MenuWorkbenchButtonBar, this._elements.statusToolbar, statusMenuId, statusMenuOptions); + // BUTTON bar + const statusMenuOptions = options.statusMenuId instanceof MenuId ? undefined : options.statusMenuId.options; + const statusButtonBar = scopedInstaService.createInstance(MenuWorkbenchButtonBar, this._elements.toolbar1, statusMenuId, { + toolbarOptions: { primaryGroup: '0_main' }, + telemetrySource: options.chatWidgetViewOptions?.menus?.telemetrySource, + menuOptions: { renderShortTitle: true }, + ...statusMenuOptions, + }); this._store.add(statusButtonBar.onDidChange(() => this._onDidChangeHeight.fire())); this._store.add(statusButtonBar); - - const workbenchToolbarOptions = { - hiddenItemStrategy: HiddenItemStrategy.NoHide, - toolbarOptions: { - primaryGroup: () => true, - useSeparatorsInPrimaryActions: true + // secondary toolbar + const toolbar2 = scopedInstaService.createInstance(MenuWorkbenchToolBar, this._elements.toolbar2, options.secondaryMenuId ?? MenuId.for(''), { + telemetrySource: options.chatWidgetViewOptions?.menus?.telemetrySource, + menuOptions: { renderShortTitle: true, shouldForwardArgs: true }, + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { + if (action instanceof MenuItemAction && action.item.id === MarkUnhelpfulActionId) { + return scopedInstaService.createInstance(ChatVoteDownButton, action, options as IMenuEntryActionViewItemOptions); + } + return createActionViewItem(scopedInstaService, action, options); } - }; + }); + this._store.add(toolbar2.onDidChangeMenuItems(() => this._onDidChangeHeight.fire())); + this._store.add(toolbar2); - if (options.feedbackMenuId) { - const feedbackToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.feedbackToolbar, options.feedbackMenuId, { ...workbenchToolbarOptions, hiddenItemStrategy: HiddenItemStrategy.Ignore }); - this._store.add(feedbackToolbar.onDidChangeMenuItems(() => this._onDidChangeHeight.fire())); - this._store.add(feedbackToolbar); - } this._store.add(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) { @@ -280,12 +275,11 @@ export class InlineChatWidget { })); this._elements.root.tabIndex = 0; - this._elements.followUps.tabIndex = 0; this._elements.statusLabel.tabIndex = 0; this._updateAriaLabel(); // this._elements.status - this._store.add(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('element'), this._elements.statusLabel, () => { + this._store.add(this._hoverService.setupManagedHover(getDefaultHoverDelegate('element'), this._elements.statusLabel, () => { return this._elements.statusLabel.dataset['title']; })); @@ -346,18 +340,15 @@ export class InlineChatWidget { protected _doLayout(dimension: Dimension): void { const extraHeight = this._getExtraHeight(); - const progressHeight = getTotalHeight(this._elements.progress); - const followUpsHeight = getTotalHeight(this._elements.followUps); const statusHeight = getTotalHeight(this._elements.status); // console.log('ZONE#Widget#layout', { height: dimension.height, extraHeight, progressHeight, followUpsHeight, statusHeight, LIST: dimension.height - progressHeight - followUpsHeight - statusHeight - extraHeight }); this._elements.root.style.height = `${dimension.height - extraHeight}px`; this._elements.root.style.width = `${dimension.width}px`; - this._elements.progress.style.width = `${dimension.width}px`; this._chatWidget.layout( - dimension.height - progressHeight - followUpsHeight - statusHeight - extraHeight, + dimension.height - statusHeight - extraHeight, dimension.width ); } @@ -367,13 +358,11 @@ export class InlineChatWidget { */ get contentHeight(): number { const data = { - followUpsHeight: getTotalHeight(this._elements.followUps), chatWidgetContentHeight: this._chatWidget.contentHeight, - progressHeight: getTotalHeight(this._elements.progress), statusHeight: getTotalHeight(this._elements.status), extraHeight: this._getExtraHeight() }; - const result = data.progressHeight + data.chatWidgetContentHeight + data.followUpsHeight + data.statusHeight + data.extraHeight; + const result = data.chatWidgetContentHeight + data.statusHeight + data.extraHeight; return result; } @@ -396,17 +385,7 @@ export class InlineChatWidget { } protected _getExtraHeight(): number { - return 12 /* padding */ + 2 /*border*/ + 12 /*shadow*/; - } - - updateProgress(show: boolean) { - if (show) { - this._progressBar.show(); - this._progressBar.infinite(); - } else { - this._progressBar.stop(); - this._progressBar.hide(); - } + return 2 /*border*/ + 4 /*shadow*/; } get value(): string { @@ -435,9 +414,18 @@ export class InlineChatWidget { this._chatWidget.setInputPlaceholder(value); } + toggleStatus(show: boolean) { + this._elements.toolbar1.classList.toggle('hidden', !show); + this._elements.toolbar2.classList.toggle('hidden', !show); + this._elements.status.classList.toggle('hidden', !show); + this._elements.infoLabel.classList.toggle('hidden', !show); + this._onDidChangeHeight.fire(); + } + updateToolbar(show: boolean) { - this._elements.statusToolbar.classList.toggle('hidden', !show); - this._elements.feedbackToolbar.classList.toggle('hidden', !show); + this._elements.root.classList.toggle('toolbar', show); + this._elements.toolbar1.classList.toggle('hidden', !show); + this._elements.toolbar2.classList.toggle('hidden', !show); this._elements.status.classList.toggle('actions', show); this._elements.infoLabel.classList.toggle('hidden', show); this._onDidChangeHeight.fire(); @@ -448,12 +436,12 @@ export class InlineChatWidget { if (!viewModel) { return undefined; } - for (const item of viewModel.getItems()) { - if (isResponseVM(item)) { - return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex)?.model; - } + const items = viewModel.getItems().filter(i => isResponseVM(i)); + if (!items.length) { + return; } - return undefined; + const item = items[items.length - 1]; + return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex)?.model; } get responseContent(): string | undefined { @@ -461,12 +449,9 @@ export class InlineChatWidget { if (!isNonEmptyArray(requests)) { return undefined; } - return tail(requests)?.response?.response.asString(); + return tail(requests)?.response?.response.toString(); } - get usesDefaultChatModel(): boolean { - return this.getChatModel() === this._defaultChatModel; - } getChatModel(): IChatModel { return this._chatWidget.viewModel?.model ?? this._defaultChatModel; @@ -476,16 +461,6 @@ export class InlineChatWidget { this._chatWidget.setModel(chatModel, { inputValue: undefined }); } - - /** - * @deprecated use `setChatModel` instead - */ - addToHistory(input: string) { - if (this._chatWidget.viewModel?.model === this._defaultChatModel) { - this._chatWidget.input.acceptInput(input); - } - } - /** * @deprecated use `setChatModel` instead */ @@ -565,15 +540,18 @@ export class InlineChatWidget { } reset() { + this._chatWidget.setContext(true); this._chatWidget.saveState(); this.updateChatMessage(undefined); reset(this._elements.statusLabel); this._elements.statusLabel.classList.toggle('hidden', true); - this._elements.statusToolbar.classList.add('hidden'); - this._elements.feedbackToolbar.classList.add('hidden'); + this._elements.toolbar1.classList.add('hidden'); + this._elements.toolbar2.classList.add('hidden'); this.updateInfo(''); + this.chatWidget.setModel(this._defaultChatModel, {}); + this._elements.accessibleViewer.classList.toggle('hidden', true); this._onDidChangeHeight.fire(); } @@ -595,7 +573,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { private readonly _accessibleViewer = this._store.add(new MutableDisposable()); constructor( - location: ChatAgentLocation, + location: IChatWidgetLocationOptions, private readonly _parentEditor: ICodeEditor, options: IInlineChatWidgetConstructionOptions, @IContextKeyService contextKeyService: IContextKeyService, @@ -608,7 +586,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { @IChatService chatService: IChatService, @IHoverService hoverService: IHoverService, ) { - super(location, { ...options, editorOverflowWidgetsDomNode: _parentEditor.getOverflowWidgetsDomNode() }, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, textModelResolverService, chatService, hoverService); + super(location, { ...options, chatWidgetViewOptions: { ...options.chatWidgetViewOptions, editorOverflowWidgetsDomNode: _parentEditor.getOverflowWidgetsDomNode() } }, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, textModelResolverService, chatService, hoverService); } // --- layout @@ -617,7 +595,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { let result = super.contentHeight; if (this._accessibleViewer.value) { - result += this._accessibleViewer.value.height; + result += this._accessibleViewer.value.height + 8 /* padding */; } return result; @@ -629,7 +607,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget { if (this._accessibleViewer.value) { this._accessibleViewer.value.width = dimension.width - 12; - newHeight -= this._accessibleViewer.value.height; + newHeight -= this._accessibleViewer.value.height + 8; } super._doLayout(dimension.with(undefined, newHeight)); diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index 88c16b80..fe7f9881 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -4,41 +4,44 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener, Dimension } from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { toDisposable } from 'vs/base/common/lifecycle'; +import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; -import { IRange, Range } from 'vs/editor/common/core/range'; +import { Range } from 'vs/editor/common/core/range'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, EditMode, InlineChatConfigKeys, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ACTION_REGENERATE_RESPONSE, ACTION_REPORT_ISSUE, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, EditMode, InlineChatConfigKeys, MENU_INLINE_CHAT_WIDGET_SECONDARY, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { EditorBasedInlineChatWidget } from './inlineChatWidget'; -import { MenuId } from 'vs/platform/actions/common/actions'; import { isEqual } from 'vs/base/common/resources'; import { StableEditorBottomScrollState } from 'vs/editor/browser/stableEditorScroll'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IChatWidgetLocationOptions } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; export class InlineChatZoneWidget extends ZoneWidget { readonly widget: EditorBasedInlineChatWidget; + private readonly _scrollUp = this._disposables.add(new ScrollUpState(this.editor)); private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>; private _dimension?: Dimension; - private _indentationWidth: number | undefined; constructor( - location: ChatAgentLocation, + location: IChatWidgetLocationOptions, editor: ICodeEditor, @IInstantiationService private readonly _instaService: IInstantiationService, + @ILogService private _logService: ILogService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, ) { - super(editor, { showFrame: false, showArrow: false, isAccessible: true, className: 'inline-chat-widget', keepEditorSelection: true, showInHiddenAreas: true, ordinal: 10000 }); + super(editor, { showFrame: false, showArrow: false, isAccessible: true, className: 'inline-chat-widget', keepEditorSelection: true, showInHiddenAreas: true, ordinal: 50000 }); this._ctxCursorPosition = CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.bindTo(contextKeyService); @@ -47,39 +50,54 @@ export class InlineChatZoneWidget extends ZoneWidget { })); this.widget = this._instaService.createInstance(EditorBasedInlineChatWidget, location, this.editor, { - telemetrySource: 'interactiveEditorWidget-toolbar', - inputMenuId: MenuId.ChatExecute, - widgetMenuId: MENU_INLINE_CHAT_WIDGET, statusMenuId: { menu: MENU_INLINE_CHAT_WIDGET_STATUS, options: { - buttonConfigProvider: action => { - if (new Set([ACTION_REGENERATE_RESPONSE, ACTION_TOGGLE_DIFF]).has(action.id)) { - return { isSecondary: true, showIcon: true, showLabel: false }; - } else if (action.id === ACTION_ACCEPT_CHANGES) { - return { isSecondary: false }; + buttonConfigProvider: (action, index) => { + const isSecondary = index > 0; + if (new Set([ACTION_REGENERATE_RESPONSE, ACTION_TOGGLE_DIFF, ACTION_REPORT_ISSUE]).has(action.id)) { + return { isSecondary, showIcon: true, showLabel: false }; } else { - return { isSecondary: true }; + return { isSecondary }; } } } }, - rendererOptions: { - renderTextEditsAsSummary: (uri) => { - // render edits as summary only when using Live mode and when - // dealing with the current file in the editor - return isEqual(uri, editor.getModel()?.uri) - && configurationService.getValue(InlineChatConfigKeys.Mode) === EditMode.Live; + secondaryMenuId: MENU_INLINE_CHAT_WIDGET_SECONDARY, + chatWidgetViewOptions: { + menus: { + executeToolbar: MenuId.ChatExecute, + telemetrySource: 'interactiveEditorWidget-toolbar', }, + rendererOptions: { + renderTextEditsAsSummary: (uri) => { + // render edits as summary only when using Live mode and when + // dealing with the current file in the editor + return isEqual(uri, editor.getModel()?.uri) + && configurationService.getValue(InlineChatConfigKeys.Mode) === EditMode.Live; + }, + } } }); + this._disposables.add(this.widget); + + let revealFn: (() => void) | undefined; + this._disposables.add(this.widget.chatWidget.onWillMaybeChangeHeight(() => { + if (this.position) { + revealFn = this._createZoneAndScrollRestoreFn(this.position); + } + })); this._disposables.add(this.widget.onDidChangeHeight(() => { if (this.position) { // only relayout when visible - this._relayout(this._computeHeight().linesValue); + revealFn ??= this._createZoneAndScrollRestoreFn(this.position); + const height = this._computeHeight(); + this._relayout(height.linesValue); + revealFn(); + revealFn = undefined; } })); - this._disposables.add(this.widget); + this.create(); this._disposables.add(addDisposableListener(this.domNode, 'click', e => { @@ -110,16 +128,14 @@ export class InlineChatZoneWidget extends ZoneWidget { container.appendChild(this.widget.domNode); } - protected override _doLayout(heightInPixel: number): void { - const width = Math.min(640, this._availableSpaceGivenIndentation(this._indentationWidth)); - this._dimension = new Dimension(width, heightInPixel); - this.widget.layout(this._dimension); - } - private _availableSpaceGivenIndentation(indentationWidth: number | undefined): number { const info = this.editor.getLayoutInfo(); - return info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth + (indentationWidth ?? 0)); + let width = info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth); + width = Math.min(640, width); + + this._dimension = new Dimension(width, heightInPixel); + this.widget.layout(this._dimension); } private _computeHeight(): { linesValue: number; pixelsValue: number } { @@ -140,93 +156,134 @@ export class InlineChatZoneWidget extends ZoneWidget { override show(position: Position): void { assertType(this.container); - const scrollState = StableEditorBottomScrollState.capture(this.editor); const info = this.editor.getLayoutInfo(); const marginWithoutIndentation = info.glyphMarginWidth + info.decorationsWidth + info.lineNumbersWidth; this.container.style.marginLeft = `${marginWithoutIndentation}px`; - const height = this._computeHeight(); - super.show(position, height.linesValue); - this._setWidgetMargins(position); + const revealZone = this._createZoneAndScrollRestoreFn(position); + super.show(position, this._computeHeight().linesValue); this.widget.chatWidget.setVisible(true); this.widget.focus(); - scrollState.restore(this.editor); - - if (position.lineNumber > 1) { - this.editor.revealRangeNearTopIfOutsideViewport(Range.fromPositions(position.delta(-1)), ScrollType.Immediate); - } else { - // reveal top of zone widget - const lineTop = this.editor.getTopForLineNumber(position.lineNumber); - const zoneTop = lineTop - height.pixelsValue; - const spaceBelowLine = this.editor.getScrollHeight() - this.editor.getBottomForLineNumber(position.lineNumber); - const minTop = this.editor.getScrollTop() - spaceBelowLine; - const newTop = Math.max(zoneTop, minTop); - - if (newTop < this.editor.getScrollTop()) { - this.editor.setScrollTop(newTop, ScrollType.Immediate); - } - } + revealZone(); + this._scrollUp.enable(); } override updatePositionAndHeight(position: Position): void { + const revealZone = this._createZoneAndScrollRestoreFn(position); super.updatePositionAndHeight(position, this._computeHeight().linesValue); - this._setWidgetMargins(position); + revealZone(); } - protected override _getWidth(info: EditorLayoutInfo): number { - return info.width - info.minimap.minimapWidth; - } + private _createZoneAndScrollRestoreFn(position: Position): () => void { - updateBackgroundColor(newPosition: Position, wholeRange: IRange) { - assertType(this.container); - const widgetLineNumber = newPosition.lineNumber; - this.container.classList.toggle('inside-selection', widgetLineNumber > wholeRange.startLineNumber && widgetLineNumber < wholeRange.endLineNumber); - } + const scrollState = StableEditorBottomScrollState.capture(this.editor); - private _calculateIndentationWidth(position: Position): number { - const viewModel = this.editor._getViewModel(); - if (!viewModel) { - return 0; - } + const lineNumber = position.lineNumber <= 1 ? 1 : 1 + position.lineNumber; + const scrollTop = this.editor.getScrollTop(); + const lineTop = this.editor.getTopForLineNumber(lineNumber); + const zoneTop = lineTop - this._computeHeight().pixelsValue; + + const hasResponse = this.widget.chatWidget.viewModel?.getItems().find(candidate => { + return isResponseVM(candidate) && candidate.response.value.length > 0; + }); - const visibleRange = viewModel.getCompletelyVisibleViewRange(); - if (!visibleRange.containsPosition(position)) { - // this is needed because `getOffsetForColumn` won't work when the position - // isn't visible/rendered - return 0; + if (hasResponse && zoneTop < scrollTop || this._scrollUp.didScrollUpOrDown) { + // don't reveal the zone if it is already out of view (unless we are still getting ready) + // or if an outside scroll-up happened (e.g the user scrolled up/down to see the new content) + return this._scrollUp.runIgnored(() => { + scrollState.restore(this.editor); + }); } - let indentationLevel = viewModel.getLineFirstNonWhitespaceColumn(position.lineNumber); - let indentationLineNumber = position.lineNumber; - for (let lineNumber = position.lineNumber; lineNumber >= visibleRange.startLineNumber; lineNumber--) { - const currentIndentationLevel = viewModel.getLineFirstNonWhitespaceColumn(lineNumber); - if (currentIndentationLevel !== 0) { - indentationLineNumber = lineNumber; - indentationLevel = currentIndentationLevel; - break; + return this._scrollUp.runIgnored(() => { + scrollState.restore(this.editor); + + const scrollTop = this.editor.getScrollTop(); + const lineTop = this.editor.getTopForLineNumber(lineNumber); + const zoneTop = lineTop - this._computeHeight().pixelsValue; + const editorHeight = this.editor.getLayoutInfo().height; + const lineBottom = this.editor.getBottomForLineNumber(lineNumber); + + let newScrollTop = zoneTop; + let forceScrollTop = false; + + if (lineBottom >= (scrollTop + editorHeight)) { + // revealing the top of the zone would push out the line we are interested in and + // therefore we keep the line in the viewport + newScrollTop = lineBottom - editorHeight; + forceScrollTop = true; } - } - return Math.max(0, this.editor.getOffsetForColumn(indentationLineNumber, indentationLevel)); // double-guard against invalie getOffsetForColumn-calls + if (newScrollTop < scrollTop || forceScrollTop) { + this._logService.trace('[IE] REVEAL zone', { zoneTop, lineTop, lineBottom, scrollTop, newScrollTop, forceScrollTop }); + this.editor.setScrollTop(newScrollTop, ScrollType.Immediate); + } + }); + } + + protected override revealRange(range: Range, isLastLine: boolean): void { + // noop } - private _setWidgetMargins(position: Position): void { - const indentationWidth = this._calculateIndentationWidth(position); - if (this._indentationWidth === indentationWidth) { - return; - } - this._indentationWidth = this._availableSpaceGivenIndentation(indentationWidth) > 400 ? indentationWidth : 0; - this.widget.domNode.style.marginLeft = `${this._indentationWidth}px`; - this.widget.domNode.style.marginRight = `${this.editor.getLayoutInfo().minimap.minimapWidth}px`; + protected override _getWidth(info: EditorLayoutInfo): number { + return info.width - info.minimap.minimapWidth; } override hide(): void { - this.container!.classList.remove('inside-selection'); + const scrollState = StableEditorBottomScrollState.capture(this.editor); + this._scrollUp.disable(); this._ctxCursorPosition.reset(); this.widget.reset(); this.widget.chatWidget.setVisible(false); super.hide(); aria.status(localize('inlineChatClosed', 'Closed inline chat widget')); + scrollState.restore(this.editor); + } +} + +class ScrollUpState { + + private _didScrollUpOrDown?: boolean; + private _ignoreEvents = false; + + private readonly _listener = new MutableDisposable(); + + constructor(private readonly _editor: ICodeEditor) { } + + dispose(): void { + this._listener.dispose(); + } + + enable(): void { + this._didScrollUpOrDown = undefined; + this._listener.value = this._editor.onDidScrollChange(e => { + if (!e.scrollTopChanged || this._ignoreEvents) { + return; + } + this._listener.clear(); + this._didScrollUpOrDown = true; + }); + } + + disable(): void { + this._listener.clear(); + this._didScrollUpOrDown = undefined; + } + + runIgnored(callback: () => void): () => void { + return () => { + this._ignoreEvents = true; + try { + return callback(); + } finally { + this._ignoreEvents = false; + } + }; + } + + get didScrollUpOrDown(): boolean | undefined { + return this._didScrollUpOrDown; } + } diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index a2d1666e..d8fac566 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -11,20 +11,18 @@ max-width: unset; } -.monaco-workbench .zone-widget-container.inside-selection { - background-color: var(--vscode-inlineChat-regionHighlight); -} - .monaco-workbench .inline-chat { color: inherit; - padding: 0 8px 8px 8px; border-radius: 4px; border: 1px solid var(--vscode-inlineChat-border); box-shadow: 0 2px 4px 0 var(--vscode-widget-shadow); - margin-top: 8px; background: var(--vscode-inlineChat-background); } +.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part { + padding: 2px 6px 0 6px; +} + .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-execute-toolbar { margin-bottom: 1px; } @@ -34,41 +32,59 @@ border-radius: 2px; } + +.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-input-followups .interactive-session-followups { + margin: 2px 0 0 4px; +} + + .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list { padding: 4px 0 0 0; } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list .interactive-item-container.interactive-item-compact { - padding: 6px 4px; gap: 6px; + padding-top: 2px; + padding-right: 20px; + padding-left: 6px; } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list .interactive-item-container.interactive-item-compact .header .avatar { outline-offset: -1px; } -.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list .interactive-request { +.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list .interactive-item-container.interactive-item-compact .chat-notification-widget { + margin-bottom: 0; + padding: 0; border: none; } -/* progress bit */ - -.monaco-workbench .inline-chat .progress { - position: relative; +.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list .interactive-request { + border: none; } -/* UGLY - fighting against workbench styles */ -.monaco-workbench .part.editor > .content .inline-chat .progress .monaco-progress-container { - top: 0; +.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list .interactive-item-container.minimal > .header { + top: 5px; + right: 10px; + display: none; } + /* status */ -.monaco-workbench .inline-chat .status { +.monaco-workbench .inline-chat > .status { display: flex; justify-content: space-between; align-items: center; - margin-top: 3px; /*makes space for action focus borders: https://github.com/microsoft/vscode-copilot/issues/5814 */ + padding-left: 6px; + padding-right: 6px; +} + +.monaco-workbench .inline-chat > .status { + .label, + .actions { + padding-top: 6px; + } } .monaco-workbench .inline-chat .status .actions.hidden { @@ -78,8 +94,9 @@ .monaco-workbench .inline-chat .status .label { overflow: hidden; color: var(--vscode-descriptionForeground); - font-size: 12px; - display: inline-flex; + font-size: 11px; + display: flex; + white-space: nowrap; } .monaco-workbench .inline-chat .status .label.info { @@ -89,9 +106,12 @@ .monaco-workbench .inline-chat .status .label.status { margin-left: auto; + padding-right: 6px; + padding-left: 6px; } -.monaco-workbench .inline-chat .status .label.hidden { +.monaco-workbench .inline-chat .status .label.hidden, +.monaco-workbench .inline-chat .status .label:empty { display: none; } @@ -104,76 +124,89 @@ } .monaco-workbench .inline-chat .status .label > .codicon { - padding: 0 5px; + padding: 0 3px; font-size: 12px; line-height: 18px; } -.monaco-workbench .inline-chat .chatMessage .chatMessageContent .value { - overflow: hidden; - -webkit-user-select: text; - user-select: text; -} -.monaco-workbench .inline-chat .followUps { - padding: 5px 5px; -} +.monaco-workbench .inline-chat .status .actions, +.monaco-workbench .inline-chat-content-widget .toolbar, +.monaco-workbench .inline-chat-diff-overlay { -.monaco-workbench .inline-chat .followUps .interactive-session-followups .monaco-button { - display: block; - color: var(--vscode-textLink-foreground); - font-size: 12px; -} + display: flex; + height: 18px; -.monaco-workbench .inline-chat .followUps.hidden { - display: none; -} + .actions-container { + gap: 3px + } -.monaco-workbench .inline-chat .chatMessage { - padding: 0 3px; -} + .monaco-button-dropdown > .monaco-dropdown-button { + display: flex; + align-items: center; + padding: 0 4px; + } -.monaco-workbench .inline-chat .chatMessage .chatMessageContent { - padding: 2px 2px; -} + .monaco-button.codicon { + display: flex; + } -.monaco-workbench .inline-chat .chatMessage.hidden { - display: none; + .monaco-button.codicon::before { + align-self: center; + color: var(--vscode-button-foreground); + } + + .monaco-button.secondary.codicon::before { + align-self: center; + color: var(--vscode-button-secondaryForeground); + } + + .monaco-text-button { + padding: 0 2px; + white-space: nowrap; + } } .monaco-workbench .inline-chat .status .actions { - display: flex; - padding-top: 3px; + gap: 4px; } -.monaco-workbench .inline-chat .status .actions > .monaco-button, -.monaco-workbench .inline-chat .status .actions > .monaco-button-dropdown { - margin-right: 6px; +.monaco-workbench .inline-chat .status .actions.secondary { + display: none; } -.monaco-workbench .inline-chat .status .actions > .monaco-button-dropdown > .monaco-dropdown-button { - display: flex; - align-items: center; - padding: 0 4px; +.monaco-workbench .inline-chat .status:hover .actions.secondary, +.monaco-workbench .inline-chat:focus .status .actions.secondary, +.monaco-workbench .inline-chat .status:focus-within .actions.secondary { + display: inherit; } -.monaco-workbench .inline-chat .status .actions > .monaco-button.codicon { - display: flex; -} +.monaco-workbench .inline-chat-diff-overlay { -.monaco-workbench .inline-chat .status .actions > .monaco-button.codicon::before { - align-self: center; -} + .monaco-button { + border-radius: 0; + } -.monaco-workbench .inline-chat .status .actions .monaco-text-button { - padding: 2px 4px; - white-space: nowrap; -} + .monaco-button.secondary.checked { + background-color: var(--vscode-button-secondaryHoverBackground); + } + + .monaco-button:first-child { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + } + + .monaco-button:last-child { + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + } -.monaco-workbench .inline-chat .status .monaco-toolbar .action-item { - padding: 0 2px; + .monaco-button:not(:last-child) { + border-right: 1px solid var(--vscode-button-foreground); + } } + /* TODO@jrieken not needed? */ .monaco-workbench .inline-chat .status .monaco-toolbar .action-label.checked { color: var(--vscode-inputOption-activeForeground); @@ -186,45 +219,17 @@ background-color: var(--vscode-button-hoverBackground); } -/* preview */ - -.monaco-workbench .inline-chat .preview { - display: none; -} - -.monaco-workbench .inline-chat .previewDiff, -.monaco-workbench .inline-chat .previewCreate { - display: inherit; - border: 1px solid var(--vscode-inlineChat-border); - border-radius: 2px; - margin: 6px 0px; -} +/* accessible diff viewer */ -.monaco-workbench .inline-chat .previewCreateTitle { - padding-top: 6px; +.monaco-workbench .inline-chat .diff-review { + padding: 4px 6px; + background-color: unset; } -.monaco-workbench .inline-chat .diff-review.hidden, -.monaco-workbench .inline-chat .previewDiff.hidden, -.monaco-workbench .inline-chat .previewCreate.hidden, -.monaco-workbench .inline-chat .previewCreateTitle.hidden { +.monaco-workbench .inline-chat .diff-review.hidden { display: none; } -.monaco-workbench .inline-chat-toolbar { - display: flex; -} - -.monaco-workbench .inline-chat-toolbar > .monaco-button { - margin-right: 6px; -} - -.monaco-workbench .inline-chat-toolbar .action-label.checked { - color: var(--vscode-inputOption-activeForeground); - background-color: var(--vscode-inputOption-activeBackground); - outline: 1px solid var(--vscode-inputOption-activeBorder); -} - /* decoration styles */ .monaco-workbench .inline-chat-inserted-range { @@ -244,56 +249,6 @@ background-color: var(--vscode-diffEditor-insertedTextBackground); } -.monaco-workbench .inline-chat-block-selection { - background-color: var(--vscode-inlineChat-regionHighlight); -} - -.monaco-workbench .interactive-session .interactive-input-and-execute-toolbar .monaco-editor .inline-chat-slash-command { - background-color: var(--vscode-chat-slashCommandBackground); - color: var(--vscode-chat-slashCommandForeground); /* Overrides the foreground color rule in chat.css */ - border-radius: 2px; - padding: 1px; -} - -.monaco-workbench .inline-chat-slash-command-detail { - opacity: 0.5; -} - -/* diff zone */ - -.monaco-workbench .inline-chat-diff-widget .monaco-diff-editor .monaco-editor-background, -.monaco-workbench .inline-chat-diff-widget .monaco-diff-editor .monaco-workbench .margin-view-overlays { - background-color: var(--vscode-inlineChat-regionHighlight); -} - -/* create zone */ - -.monaco-workbench .inline-chat-newfile-widget { - background-color: var(--vscode-inlineChat-regionHighlight); -} - -.monaco-workbench .inline-chat-newfile-widget .title { - display: flex; - align-items: center; - justify-content: space-between; -} - -.monaco-workbench .inline-chat-newfile-widget .title .detail { - margin-left: 4px; -} - -.monaco-workbench .inline-chat-newfile-widget .buttonbar-widget { - display: flex; - margin-left: auto; - margin-right: 8px; -} - -.monaco-workbench .inline-chat-newfile-widget .buttonbar-widget > .monaco-button { - display: inline-flex; - white-space: nowrap; - margin-left: 4px; -} - /* gutter decoration */ .monaco-workbench .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque, diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/media/inlineChatContentWidget.css b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/media/inlineChatContentWidget.css index bc9e18e6..07390880 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/media/inlineChatContentWidget.css +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/browser/media/inlineChatContentWidget.css @@ -5,15 +5,11 @@ .monaco-workbench .inline-chat-content-widget { z-index: 50; - padding: 6px 6px 6px 6px; border-radius: 4px; background-color: var(--vscode-inlineChat-background); box-shadow: 0 4px 8px var(--vscode-inlineChat-shadow); } -.monaco-workbench .inline-chat-content-widget .hidden { - display: none; -} .monaco-workbench .inline-chat-content-widget.interactive-session .interactive-session { max-width: unset; @@ -24,18 +20,14 @@ } .monaco-workbench .inline-chat-content-widget.interactive-session .interactive-input-part.compact { - padding: 0; + padding: 6px; } -.monaco-workbench .inline-chat-content-widget .message { - overflow: hidden; - color: var(--vscode-descriptionForeground); - font-size: 11px; - display: inline-flex; +.monaco-workbench .inline-chat-content-widget.interactive-session .interactive-list { + display: none; } -.monaco-workbench .inline-chat-content-widget .message > .codicon { - padding-right: 5px; - font-size: 12px; - line-height: 18px; +.monaco-workbench .inline-chat-content-widget.interactive-session .toolbar { + display: none; + padding-top: 4px; } diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 00954ac4..f4c87af0 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -3,120 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { IRange } from 'vs/editor/common/core/range'; -import { WorkspaceEdit } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; -import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; -import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; -import { IChatAgentCommand } from 'vs/workbench/contrib/chat/common/chatAgents'; - -export interface IInlineChatSession { - id: number; - placeholder?: string; - input?: string; - message?: string; - slashCommands?: IChatAgentCommand[]; - wholeRange?: IRange; -} - -export const enum InlineChatResponseTypes { - Empty = 'empty', - OnlyEdits = 'onlyEdits', - OnlyMessages = 'onlyMessages', - Mixed = 'mixed' -} - -export interface IInlineChatResponse { - edits: WorkspaceEdit; - message?: IMarkdownString; - placeholder?: string; - wholeRange?: IRange; -} - - -export const INLINE_CHAT_ID = 'interactiveEditor'; -export const INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID = 'interactiveEditorAccessiblityHelp'; - -export const enum EditMode { - Live = 'live', - Preview = 'preview' -} - -export const CTX_INLINE_CHAT_HAS_AGENT = new RawContextKey('inlineChatHasProvider', false, localize('inlineChatHasProvider', "Whether a provider for interactive editors exists")); -export const CTX_INLINE_CHAT_VISIBLE = new RawContextKey('inlineChatVisible', false, localize('inlineChatVisible', "Whether the interactive editor input is visible")); -export const CTX_INLINE_CHAT_FOCUSED = new RawContextKey('inlineChatFocused', false, localize('inlineChatFocused', "Whether the interactive editor input is focused")); -export const CTX_INLINE_CHAT_RESPONSE_FOCUSED = new RawContextKey('inlineChatResponseFocused', false, localize('inlineChatResponseFocused', "Whether the interactive widget's response is focused")); -export const CTX_INLINE_CHAT_EMPTY = new RawContextKey('inlineChatEmpty', false, localize('inlineChatEmpty', "Whether the interactive editor input is empty")); -export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey('inlineChatInnerCursorFirst', false, localize('inlineChatInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line")); -export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line")); -export const CTX_INLINE_CHAT_INNER_CURSOR_START = new RawContextKey('inlineChatInnerCursorStart', false, localize('inlineChatInnerCursorStart', "Whether the cursor of the iteractive editor input is on the start of the input")); -export const CTX_INLINE_CHAT_INNER_CURSOR_END = new RawContextKey('inlineChatInnerCursorEnd', false, localize('inlineChatInnerCursorEnd', "Whether the cursor of the iteractive editor input is on the end of the input")); -export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input")); -export const CTX_INLINE_CHAT_HAS_STASHED_SESSION = new RawContextKey('inlineChatHasStashedSession', false, localize('inlineChatHasStashedSession', "Whether interactive editor has kept a session for quick restore")); -export const CTX_INLINE_CHAT_RESPONSE_TYPES = new RawContextKey('inlineChatResponseTypes', InlineChatResponseTypes.Empty, localize('inlineChatResponseTypes', "What type was the responses have been receieved")); -export const CTX_INLINE_CHAT_USER_DID_EDIT = new RawContextKey('inlineChatUserDidEdit', undefined, localize('inlineChatUserDidEdit', "Whether the user did changes ontop of the inline chat")); -export const CTX_INLINE_CHAT_DOCUMENT_CHANGED = new RawContextKey('inlineChatDocumentChanged', false, localize('inlineChatDocumentChanged', "Whether the document has changed concurrently")); -export const CTX_INLINE_CHAT_CHANGE_HAS_DIFF = new RawContextKey('inlineChatChangeHasDiff', false, localize('inlineChatChangeHasDiff', "Whether the current change supports showing a diff")); -export const CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF = new RawContextKey('inlineChatChangeShowsDiff', false, localize('inlineChatChangeShowsDiff', "Whether the current change showing a diff")); -export const CTX_INLINE_CHAT_EDIT_MODE = new RawContextKey('config.inlineChat.mode', EditMode.Live); - -// --- (select) action identifier - -export const ACTION_ACCEPT_CHANGES = 'inlineChat.acceptChanges'; -export const ACTION_REGENERATE_RESPONSE = 'inlineChat.regenerate'; -export const ACTION_VIEW_IN_CHAT = 'inlineChat.viewInChat'; -export const ACTION_TOGGLE_DIFF = 'inlineChat.toggleDiff'; - -// --- menus - -export const MENU_INLINE_CHAT_WIDGET = MenuId.for('inlineChatWidget'); -export const MENU_INLINE_CHAT_WIDGET_STATUS = MenuId.for('inlineChatWidget.status'); - -// --- colors - - -export const inlineChatBackground = registerColor('inlineChat.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, localize('inlineChat.background', "Background color of the interactive editor widget")); -export const inlineChatBorder = registerColor('inlineChat.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('inlineChat.border', "Border color of the interactive editor widget")); -export const inlineChatShadow = registerColor('inlineChat.shadow', { dark: widgetShadow, light: widgetShadow, hcDark: widgetShadow, hcLight: widgetShadow }, localize('inlineChat.shadow', "Shadow color of the interactive editor widget")); -export const inlineChatRegionHighlight = registerColor('inlineChat.regionHighlight', { dark: editorHoverHighlight, light: editorHoverHighlight, hcDark: editorHoverHighlight, hcLight: editorHoverHighlight }, localize('inlineChat.regionHighlight', "Background highlighting of the current interactive region. Must be transparent."), true); -export const inlineChatInputBorder = registerColor('inlineChatInput.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('inlineChatInput.border', "Border color of the interactive editor input")); -export const inlineChatInputFocusBorder = registerColor('inlineChatInput.focusBorder', { dark: focusBorder, light: focusBorder, hcDark: focusBorder, hcLight: focusBorder }, localize('inlineChatInput.focusBorder', "Border color of the interactive editor input when focused")); -export const inlineChatInputPlaceholderForeground = registerColor('inlineChatInput.placeholderForeground', { dark: inputPlaceholderForeground, light: inputPlaceholderForeground, hcDark: inputPlaceholderForeground, hcLight: inputPlaceholderForeground }, localize('inlineChatInput.placeholderForeground', "Foreground color of the interactive editor input placeholder")); -export const inlineChatInputBackground = registerColor('inlineChatInput.background', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('inlineChatInput.background', "Background color of the interactive editor input")); - -export const inlineChatDiffInserted = registerColor('inlineChatDiff.inserted', { dark: transparent(diffInserted, .5), light: transparent(diffInserted, .5), hcDark: transparent(diffInserted, .5), hcLight: transparent(diffInserted, .5) }, localize('inlineChatDiff.inserted', "Background color of inserted text in the interactive editor input")); -export const overviewRulerInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.')); -export const minimapInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.')); - -export const inlineChatDiffRemoved = registerColor('inlineChatDiff.removed', { dark: transparent(diffRemoved, .5), light: transparent(diffRemoved, .5), hcDark: transparent(diffRemoved, .5), hcLight: transparent(diffRemoved, .5) }, localize('inlineChatDiff.removed', "Background color of removed text in the interactive editor input")); -export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewRuler.inlineChatRemoved', { dark: transparent(diffRemoved, 0.6), light: transparent(diffRemoved, 0.8), hcDark: transparent(diffRemoved, 0.6), hcLight: transparent(diffRemoved, 0.8) }, localize('editorOverviewRuler.inlineChatRemoved', 'Overview ruler marker color for inline chat removed content.')); - +import { diffInserted, diffRemoved, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; // settings - - -Registry.as(ExtensionsMigration.ConfigurationMigration).registerConfigurationMigrations( - [{ - key: 'interactiveEditor.editMode', migrateFn: (value: any) => { - return [['inlineChat.mode', { value: value }]]; - } - }] -); - export const enum InlineChatConfigKeys { Mode = 'inlineChat.mode', FinishOnType = 'inlineChat.finishOnType', AcceptedOrDiscardBeforeSave = 'inlineChat.acceptedOrDiscardBeforeSave', + StartWithOverlayWidget = 'inlineChat.startWithOverlayWidget', + ZoneToolbar = 'inlineChat.experimental.enableZoneToolbar', HoldToSpeech = 'inlineChat.holdToSpeech', AccessibleDiffView = 'inlineChat.accessibleDiffView' } +export const enum EditMode { + Live = 'live', + Preview = 'preview' +} + Registry.as(Extensions.Configuration).registerConfiguration({ id: 'editor', properties: { @@ -156,6 +66,84 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('accessibleDiffView.on', "The accessible diff viewer is always enabled."), localize('accessibleDiffView.off', "The accessible diff viewer is never enabled."), ], - } + }, + [InlineChatConfigKeys.StartWithOverlayWidget]: { + description: localize('onlyZone', "Whether inline chat opens directly as zone widget, between the lines, or as overlay widget which turns into a zone."), + default: false, + type: 'boolean', + }, + [InlineChatConfigKeys.ZoneToolbar]: { + description: localize('zoneToolbar', "Whether to show a toolbar to accept or reject changes in the inline chat changes view."), + default: false, + type: 'boolean', + tags: ['experimental'] + }, } }); + + +export const INLINE_CHAT_ID = 'interactiveEditor'; +export const INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID = 'interactiveEditorAccessiblityHelp'; + +// --- CONTEXT + +export const enum InlineChatResponseType { + None = 'none', + Messages = 'messages', + MessagesAndEdits = 'messagesAndEdits' +} + +export const CTX_INLINE_CHAT_HAS_AGENT = new RawContextKey('inlineChatHasProvider', false, localize('inlineChatHasProvider', "Whether a provider for interactive editors exists")); +export const CTX_INLINE_CHAT_VISIBLE = new RawContextKey('inlineChatVisible', false, localize('inlineChatVisible', "Whether the interactive editor input is visible")); +export const CTX_INLINE_CHAT_FOCUSED = new RawContextKey('inlineChatFocused', false, localize('inlineChatFocused', "Whether the interactive editor input is focused")); +export const CTX_INLINE_CHAT_EDITING = new RawContextKey('inlineChatEditing', true, localize('inlineChatEditing', "Whether the user is currently editing or generating code in the inline chat")); +export const CTX_INLINE_CHAT_RESPONSE_FOCUSED = new RawContextKey('inlineChatResponseFocused', false, localize('inlineChatResponseFocused', "Whether the interactive widget's response is focused")); +export const CTX_INLINE_CHAT_EMPTY = new RawContextKey('inlineChatEmpty', false, localize('inlineChatEmpty', "Whether the interactive editor input is empty")); +export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey('inlineChatInnerCursorFirst', false, localize('inlineChatInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line")); +export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line")); +export const CTX_INLINE_CHAT_INNER_CURSOR_START = new RawContextKey('inlineChatInnerCursorStart', false, localize('inlineChatInnerCursorStart', "Whether the cursor of the iteractive editor input is on the start of the input")); +export const CTX_INLINE_CHAT_INNER_CURSOR_END = new RawContextKey('inlineChatInnerCursorEnd', false, localize('inlineChatInnerCursorEnd', "Whether the cursor of the iteractive editor input is on the end of the input")); +export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input")); +export const CTX_INLINE_CHAT_HAS_STASHED_SESSION = new RawContextKey('inlineChatHasStashedSession', false, localize('inlineChatHasStashedSession', "Whether interactive editor has kept a session for quick restore")); +export const CTX_INLINE_CHAT_USER_DID_EDIT = new RawContextKey('inlineChatUserDidEdit', undefined, localize('inlineChatUserDidEdit', "Whether the user did changes ontop of the inline chat")); +export const CTX_INLINE_CHAT_DOCUMENT_CHANGED = new RawContextKey('inlineChatDocumentChanged', false, localize('inlineChatDocumentChanged', "Whether the document has changed concurrently")); +export const CTX_INLINE_CHAT_CHANGE_HAS_DIFF = new RawContextKey('inlineChatChangeHasDiff', false, localize('inlineChatChangeHasDiff', "Whether the current change supports showing a diff")); +export const CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF = new RawContextKey('inlineChatChangeShowsDiff', false, localize('inlineChatChangeShowsDiff', "Whether the current change showing a diff")); +export const CTX_INLINE_CHAT_EDIT_MODE = new RawContextKey('config.inlineChat.mode', EditMode.Live); +export const CTX_INLINE_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('inlineChatRequestInProgress', false, localize('inlineChatRequestInProgress', "Whether an inline chat request is currently in progress")); +export const CTX_INLINE_CHAT_RESPONSE_TYPE = new RawContextKey('inlineChatResponseType', InlineChatResponseType.None, localize('inlineChatResponseTypes', "What type was the responses have been receieved, nothing yet, just messages, or messaged and local edits")); + +// --- (selected) action identifier + +export const ACTION_ACCEPT_CHANGES = 'inlineChat.acceptChanges'; +export const ACTION_DISCARD_CHANGES = 'inlineChat.discardHunkChange'; +export const ACTION_REGENERATE_RESPONSE = 'inlineChat.regenerate'; +export const ACTION_VIEW_IN_CHAT = 'inlineChat.viewInChat'; +export const ACTION_TOGGLE_DIFF = 'inlineChat.toggleDiff'; +export const ACTION_REPORT_ISSUE = 'inlineChat.reportIssue'; + +// --- menus + +export const MENU_INLINE_CHAT_CONTENT_STATUS = MenuId.for('inlineChat.content.status'); +export const MENU_INLINE_CHAT_WIDGET_STATUS = MenuId.for('inlineChatWidget.status'); +export const MENU_INLINE_CHAT_WIDGET_SECONDARY = MenuId.for('inlineChatWidget.secondary'); +export const MENU_INLINE_CHAT_ZONE = MenuId.for('inlineChatWidget.changesZone'); + +// --- colors + + +export const inlineChatForeground = registerColor('inlineChat.foreground', editorWidgetForeground, localize('inlineChat.foreground', "Foreground color of the interactive editor widget")); +export const inlineChatBackground = registerColor('inlineChat.background', editorWidgetBackground, localize('inlineChat.background', "Background color of the interactive editor widget")); +export const inlineChatBorder = registerColor('inlineChat.border', editorWidgetBorder, localize('inlineChat.border', "Border color of the interactive editor widget")); +export const inlineChatShadow = registerColor('inlineChat.shadow', widgetShadow, localize('inlineChat.shadow', "Shadow color of the interactive editor widget")); +export const inlineChatInputBorder = registerColor('inlineChatInput.border', editorWidgetBorder, localize('inlineChatInput.border', "Border color of the interactive editor input")); +export const inlineChatInputFocusBorder = registerColor('inlineChatInput.focusBorder', focusBorder, localize('inlineChatInput.focusBorder', "Border color of the interactive editor input when focused")); +export const inlineChatInputPlaceholderForeground = registerColor('inlineChatInput.placeholderForeground', inputPlaceholderForeground, localize('inlineChatInput.placeholderForeground', "Foreground color of the interactive editor input placeholder")); +export const inlineChatInputBackground = registerColor('inlineChatInput.background', inputBackground, localize('inlineChatInput.background', "Background color of the interactive editor input")); + +export const inlineChatDiffInserted = registerColor('inlineChatDiff.inserted', transparent(diffInserted, .5), localize('inlineChatDiff.inserted', "Background color of inserted text in the interactive editor input")); +export const overviewRulerInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.')); +export const minimapInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.')); + +export const inlineChatDiffRemoved = registerColor('inlineChatDiff.removed', transparent(diffRemoved, .5), localize('inlineChatDiff.removed', "Background color of removed text in the interactive editor input")); +export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewRuler.inlineChatRemoved', { dark: transparent(diffRemoved, 0.6), light: transparent(diffRemoved, 0.8), hcDark: transparent(diffRemoved, 0.6), hcLight: transparent(diffRemoved, 0.8) }, localize('editorOverviewRuler.inlineChatRemoved', 'Overview ruler marker color for inline chat removed content.')); diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.1.snap b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.1.snap new file mode 100644 index 00000000..a0379e04 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.1.snap @@ -0,0 +1,13 @@ +export function fib(n) { + if (n <= 0) return 0; + if (n === 1) return 0; + if (n === 2) return 1; + + let a = 0, b = 1, c; + for (let i = 3; i <= n; i++) { + c = a + b; + a = b; + b = c; + } + return b; +} \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.2.snap b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.2.snap new file mode 100644 index 00000000..3d44a421 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.2.snap @@ -0,0 +1,6 @@ +export function fib(n) { + if (n <= 0) return 0; + if (n === 1) return 0; + if (n === 2) return 1; + return fib(n - 1) + fib(n - 2); +} \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 536ef768..ca2b5478 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { equals } from 'vs/base/common/arrays'; -import { raceCancellation, timeout } from 'vs/base/common/async'; +import { DeferredPromise, raceCancellation, timeout } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; @@ -31,18 +31,14 @@ import { IView, IViewDescriptorService } from 'vs/workbench/common/views'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentLocation, ChatAgentService, IChatAgentData, IChatAgentNameService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { InlineChatController, InlineChatRunOptions, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { InlineChatController, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { TestViewsService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { IInlineChatSavingService } from '../../browser/inlineChatSavingService'; -import { IInlineChatSessionService } from '../../browser/inlineChatSessionService'; -import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl'; -import { TestWorkerService } from './testWorkerService'; import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -63,6 +59,12 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/se import { RerunAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { assertType } from 'vs/base/common/types'; +import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; +import { NullWorkbenchAssignmentService } from 'vs/workbench/services/assignment/test/common/nullAssignmentService'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; +import { TestWorkerService } from 'vs/workbench/contrib/inlineChat/test/browser/testWorkerService'; suite('InteractiveChatController', function () { @@ -76,51 +78,38 @@ suite('InteractiveChatController', function () { isDefault: true, locations: [ChatAgentLocation.Editor], metadata: {}, - slashCommands: [] + slashCommands: [], + disambiguation: [], }; class TestController extends InlineChatController { static INIT_SEQUENCE: readonly State[] = [State.CREATE_SESSION, State.INIT_UI, State.WAIT_FOR_INPUT]; - static INIT_SEQUENCE_AUTO_SEND: readonly State[] = [...this.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]; + static INIT_SEQUENCE_AUTO_SEND: readonly State[] = [...this.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]; - private readonly _onDidChangeState = new Emitter(); - readonly onDidChangeState: Event = this._onDidChangeState.event; + + readonly onDidChangeState: Event = this._onDidEnterState.event; readonly states: readonly State[] = []; - waitFor(states: readonly State[]): Promise { + awaitStates(states: readonly State[]): Promise { const actual: State[] = []; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const d = this.onDidChangeState(state => { actual.push(state); if (equals(states, actual)) { d.dispose(); - resolve(); + resolve(undefined); } }); setTimeout(() => { d.dispose(); - reject(new Error(`timeout, \nEXPECTED: ${states.join('>')}, \nACTUAL : ${actual.join('>')}`)); + resolve(`[${states.join(',')}] <> [${actual.join(',')}]`); }, 1000); }); } - - protected override async _nextState(state: State, options: InlineChatRunOptions): Promise { - let nextState: State | void = state; - while (nextState) { - this._onDidChangeState.fire(nextState); - (this.states).push(nextState); - nextState = await this[nextState](options); - } - } - - override dispose() { - super.dispose(); - this._onDidChangeState.dispose(); - } } const store = new DisposableStore(); @@ -156,6 +145,11 @@ suite('InteractiveChatController', function () { [IChatWidgetService, new SyncDescriptor(ChatWidgetService)], [IChatSlashCommandService, new SyncDescriptor(ChatSlashCommandService)], [IChatService, new SyncDescriptor(ChatService)], + [IChatAgentNameService, new class extends mock() { + override getAgentNameRestriction(chatAgentData: IChatAgentData): boolean { + return false; + } + }], [IEditorWorkerService, new SyncDescriptor(TestWorkerService)], [IContextKeyService, contextKeyService], [IChatAgentService, new SyncDescriptor(ChatAgentService)], @@ -191,7 +185,8 @@ suite('InteractiveChatController', function () { }], [INotebookEditorService, new class extends mock() { override listNotebookEditors() { return []; } - }] + }], + [IWorkbenchAssignmentService, new NullWorkbenchAssignmentService()] ); instaService = store.add((store.add(workbenchInstantiationService(undefined, store))).createChild(serviceCollection)); @@ -243,9 +238,9 @@ suite('InteractiveChatController', function () { test('run (show/hide)', async function () { ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); + const actualStates = ctrl.awaitStates(TestController.INIT_SEQUENCE_AUTO_SEND); const run = ctrl.run({ message: 'Hello', autoSend: true }); - await p; + assert.strictEqual(await actualStates, undefined); assert.ok(ctrl.getWidgetPosition() !== undefined); await ctrl.cancelSession(); @@ -274,10 +269,10 @@ suite('InteractiveChatController', function () { configurationService.setUserConfiguration(InlineChatConfigKeys.FinishOnType, true); ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); + const actualStates = ctrl.awaitStates(TestController.INIT_SEQUENCE_AUTO_SEND); const r = ctrl.run({ message: 'Hello', autoSend: true }); - await p; + assert.strictEqual(await actualStates, undefined); const session = inlineChatSessionService.getSession(editor, editor.getModel()!.uri); assert.ok(session); @@ -286,7 +281,7 @@ suite('InteractiveChatController', function () { editor.setSelection(new Range(2, 1, 2, 1)); editor.trigger('test', 'type', { text: 'a' }); - await ctrl.waitFor([State.ACCEPT]); + assert.strictEqual(await ctrl.awaitStates([State.ACCEPT]), undefined); await r; }); @@ -313,17 +308,19 @@ suite('InteractiveChatController', function () { })); ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.waitFor(TestController.INIT_SEQUENCE); + const p = ctrl.awaitStates(TestController.INIT_SEQUENCE); const r = ctrl.run({ message: 'GENGEN', autoSend: false }); - await p; + assert.strictEqual(await p, undefined); + const session = inlineChatSessionService.getSession(editor, editor.getModel()!.uri); assert.ok(session); assert.deepStrictEqual(session.wholeRange.value, new Range(3, 1, 3, 3)); // initial + ctrl.chatWidget.setInput('GENGEN'); ctrl.acceptInput(); - await ctrl.waitFor([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + assert.strictEqual(await ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]), undefined); assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 4, 3)); @@ -343,10 +340,11 @@ suite('InteractiveChatController', function () { })); ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]); const r = ctrl.run({ message: 'Hello', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); + ctrl.acceptSession(); await r; @@ -372,9 +370,9 @@ suite('InteractiveChatController', function () { const valueThen = editor.getModel().getValue(); ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); const r = ctrl.run({ message: 'Hello', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); ctrl.acceptSession(); await r; @@ -415,9 +413,9 @@ suite('InteractiveChatController', function () { // store.add(editor.getModel().onDidChangeContent(() => { modelChangeCounter++; })); ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); const r = ctrl.run({ message: 'Hello', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); // assert.ok(modelChangeCounter > 0, modelChangeCounter.toString()); // some changes have been made // const modelChangeCounterNow = modelChangeCounter; @@ -438,9 +436,9 @@ suite('InteractiveChatController', function () { // NO manual edits -> cancel ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); const r = ctrl.run({ message: 'GENERATED', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); assert.ok(model.getValue().includes('GENERATED')); assert.strictEqual(contextKeyService.getContextKeyValue(CTX_INLINE_CHAT_USER_DID_EDIT.key), undefined); @@ -454,9 +452,9 @@ suite('InteractiveChatController', function () { // manual edits -> finish ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); const r = ctrl.run({ message: 'GENERATED', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); assert.ok(model.getValue().includes('GENERATED')); @@ -489,16 +487,17 @@ suite('InteractiveChatController', function () { model.setValue(''); - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); const r = ctrl.run({ message: 'PROMPT_', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); + assert.strictEqual(model.getValue(), 'PROMPT_1'); - const p2 = ctrl.waitFor([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); await instaService.invokeFunction(rerun.runInlineChatCommand, ctrl, editor); - await p2; + assert.strictEqual(await p2, undefined); assert.strictEqual(model.getValue(), 'PROMPT_2'); ctrl.finishExistingSession(); @@ -529,23 +528,24 @@ suite('InteractiveChatController', function () { model.setValue(''); // REQUEST 1 - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); const r = ctrl.run({ message: '1', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); assert.strictEqual(model.getValue(), 'eins-'); // REQUEST 2 - const p2 = ctrl.waitFor([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); + ctrl.chatWidget.setInput('1'); await ctrl.acceptInput(); - await p2; + assert.strictEqual(await p2, undefined); assert.strictEqual(model.getValue(), 'zwei-eins-'); // REQUEST 2 - RERUN - const p3 = ctrl.waitFor([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p3 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); await instaService.invokeFunction(rerun.runInlineChatCommand, ctrl, editor); - await p3; + assert.strictEqual(await p3, undefined); assert.strictEqual(model.getValue(), 'drei-eins-'); @@ -572,10 +572,9 @@ suite('InteractiveChatController', function () { ctrl = instaService.createInstance(TestController, editor); // REQUEST 1 - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); ctrl.run({ message: '1', autoSend: true }); - await p; - + assert.strictEqual(await p, undefined); assert.strictEqual(model.getValue(), 'eins\nHello\nWorld\nHello Again\nHello World\n'); @@ -613,16 +612,17 @@ suite('InteractiveChatController', function () { ctrl = instaService.createInstance(TestController, editor); // REQUEST 1 - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); ctrl.run({ message: '1', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); assert.strictEqual(model.getValue(), 'eins\nHello\nWorld\nHello Again\nHello World\n'); // REQUEST 2 - const p2 = ctrl.waitFor([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); + ctrl.chatWidget.setInput('1'); await ctrl.acceptInput(); - await p2; + assert.strictEqual(await p2, undefined); assert.strictEqual(model.getValue(), 'zwei\neins\nHello\nWorld\nHello Again\nHello World\n'); @@ -651,11 +651,14 @@ suite('InteractiveChatController', function () { let count = 0; const commandDetection: (boolean | undefined)[] = []; + const onDidInvoke = new Emitter(); + store.add(chatAgentService.registerDynamicAgent({ id: 'testEditorAgent2', ...agentData }, { async invoke(request, progress, history, token) { + queueMicrotask(() => onDidInvoke.fire()); commandDetection.push(request.enableCommandDetection); progress({ kind: 'textEdit', uri: model.uri, edits: [{ range: new Range(1, 1, 1, 1), text: request.message + (count++) }] }); @@ -672,17 +675,23 @@ suite('InteractiveChatController', function () { ctrl = instaService.createInstance(TestController, editor); // REQUEST 1 - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]); + // const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]); + const p = Event.toPromise(onDidInvoke.event); ctrl.run({ message: 'Hello-', autoSend: true }); + await p; + // assert.strictEqual(await p, undefined); + // resend pending request without command detection const request = ctrl.chatWidget.viewModel?.model.getRequests().at(-1); assertType(request); - const p2 = ctrl.waitFor([State.SHOW_REQUEST, State.SHOW_RESPONSE]); + const p2 = Event.toPromise(onDidInvoke.event); + const p3 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.Editor }); await p2; + assert.strictEqual(await p3, undefined); assert.deepStrictEqual(commandDetection, [true, false]); assert.strictEqual(model.getValue(), 'Hello-1'); @@ -708,19 +717,196 @@ suite('InteractiveChatController', function () { ctrl = instaService.createInstance(TestController, editor); // REQUEST 1 - const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); ctrl.run({ message: 'Hello-', autoSend: true }); - await p; + assert.strictEqual(await p, undefined); // resend pending request without command detection const request = ctrl.chatWidget.viewModel?.model.getRequests().at(-1); assertType(request); - const p2 = ctrl.waitFor([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); + const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt + 1, location: ChatAgentLocation.Editor }); - await p2; + assert.strictEqual(await p2, undefined); assert.deepStrictEqual(commandDetection, [true, false]); assert.strictEqual(model.getValue(), 'Hello-1'); }); + + + test('Inline: Pressing Rerun request while the response streams breaks the response #5442', async function () { + + model.setValue('two\none\n'); + + const attempts: (number | undefined)[] = []; + + const deferred = new DeferredPromise(); + + store.add(chatAgentService.registerDynamicAgent({ + id: 'testEditorAgent2', + ...agentData + }, { + async invoke(request, progress, history, token) { + + attempts.push(request.attempt); + + progress({ kind: 'textEdit', uri: model.uri, edits: [{ range: new Range(1, 1, 1, 1), text: `TRY:${request.attempt}\n` }] }); + await raceCancellation(deferred.p, token); + deferred.complete(); + await timeout(10); + return {}; + }, + })); + + ctrl = instaService.createInstance(TestController, editor); + + // REQUEST 1 + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]); + ctrl.run({ message: 'Hello-', autoSend: true }); + assert.strictEqual(await p, undefined); + await timeout(10); + assert.deepStrictEqual(attempts, [0]); + + // RERUN (cancel, undo, redo) + const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); + const rerun = new RerunAction(); + await instaService.invokeFunction(rerun.runInlineChatCommand, ctrl, editor); + assert.strictEqual(await p2, undefined); + + assert.deepStrictEqual(attempts, [0, 1]); + + assert.strictEqual(model.getValue(), 'TRY:1\ntwo\none\n'); + + }); + + test('Stopping/cancelling a request should NOT undo its changes', async function () { + + model.setValue('World'); + + const deferred = new DeferredPromise(); + let progress: ((part: IChatProgress) => void) | undefined; + + store.add(chatAgentService.registerDynamicAgent({ + id: 'testEditorAgent2', + ...agentData + }, { + async invoke(request, _progress, history, token) { + + progress = _progress; + await deferred.p; + return {}; + }, + })); + + ctrl = instaService.createInstance(TestController, editor); + + // REQUEST 1 + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]); + ctrl.run({ message: 'Hello', autoSend: true }); + await timeout(10); + assert.strictEqual(await p, undefined); + + assertType(progress); + + const modelChange = new Promise(resolve => model.onDidChangeContent(() => resolve())); + + progress({ kind: 'textEdit', uri: model.uri, edits: [{ range: new Range(1, 1, 1, 1), text: 'Hello-Hello' }] }); + + await modelChange; + assert.strictEqual(model.getValue(), 'HelloWorld'); // first word has been streamed + + const p2 = ctrl.awaitStates([State.WAIT_FOR_INPUT]); + chatService.cancelCurrentRequestForSession(ctrl.chatWidget.viewModel!.model.sessionId); + assert.strictEqual(await p2, undefined); + + assert.strictEqual(model.getValue(), 'HelloWorld'); // CANCEL just stops the request and progressive typing but doesn't undo + + }); + + test('Apply Edits from existing session w/ edits', async function () { + + model.setValue(''); + + const newSession = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(newSession); + + await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.Editor }); + + + assert.strictEqual(newSession.chatModel.requestInProgress, true); + + const response = newSession.chatModel.lastRequest?.response; + assertType(response); + + await new Promise(resolve => { + if (response.isComplete) { + resolve(undefined); + } + const d = response.onDidChange(() => { + if (response.isComplete) { + d.dispose(); + resolve(undefined); + } + }); + }); + + ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE]); + ctrl.run({ existingSession: newSession }); + + assert.strictEqual(await p, undefined); + + assert.strictEqual(model.getValue(), 'Existing'); + + }); + + test('Undo on error (2 rounds)', async function () { + + return runWithFakedTimers({}, async () => { + + + store.add(chatAgentService.registerDynamicAgent({ id: 'testEditorAgent', ...agentData, }, { + async invoke(request, progress, history, token) { + + progress({ + kind: 'textEdit', + uri: model.uri, + edits: [{ + range: new Range(1, 1, 1, 1), + text: request.message + }] + }); + + if (request.message === 'two') { + await timeout(100); // give edit a chance + return { + errorDetails: { message: 'FAILED' } + }; + } + return {}; + }, + })); + + model.setValue(''); + + // ROUND 1 + + ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); + ctrl.run({ autoSend: true, message: 'one' }); + assert.strictEqual(await p, undefined); + assert.strictEqual(model.getValue(), 'one'); + + + // ROUND 2 + + const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); + const values = new Set(); + store.add(model.onDidChangeContent(() => values.add(model.getValue()))); + ctrl.chatWidget.acceptInput('two'); // WILL Trigger a failure + assert.strictEqual(await p2, undefined); + assert.strictEqual(model.getValue(), 'one'); // undone + assert.ok(values.has('twoone')); // we had but the change got undone + }); + }); }); diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index 86a3b614..82734a99 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.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 * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { mock } from 'vs/base/test/common/mock'; @@ -56,6 +56,12 @@ import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVari import { ICommandService } from 'vs/platform/commands/common/commands'; import { TestCommandService } from 'vs/editor/test/browser/editorTestServices'; import { IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; +import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; +import { NullWorkbenchAssignmentService } from 'vs/workbench/services/assignment/test/common/nullAssignmentService'; +import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; +import { MockLanguageModelToolsService } from 'vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService'; +import { IChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { assertSnapshot } from 'vs/base/test/common/snapshot'; suite('InlineChatSession', function () { @@ -89,6 +95,7 @@ suite('InlineChatSession', function () { [IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)], [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionServiceImpl)], [ICommandService, new SyncDescriptor(TestCommandService)], + [ILanguageModelToolsService, new MockLanguageModelToolsService()], [IInlineChatSavingService, new class extends mock() { override markChanged(session: Session): void { // noop @@ -115,7 +122,8 @@ suite('InlineChatSession', function () { [IConfigurationService, new TestConfigurationService()], [IViewDescriptorService, new class extends mock() { override onDidChangeLocation = Event.None; - }] + }], + [IWorkbenchAssignmentService, new NullWorkbenchAssignmentService()] ); @@ -133,7 +141,8 @@ suite('InlineChatSession', function () { isDefault: true, locations: [ChatAgentLocation.Editor], metadata: {}, - slashCommands: [] + slashCommands: [], + disambiguation: [], }, { async invoke() { return {}; @@ -480,4 +489,90 @@ suite('InlineChatSession', function () { inlineChatSessionService.releaseSession(session); }); + + test('Pressing Escape after inline chat errored with "response filtered" leaves document dirty #7764', async function () { + + const origValue = `class Foo { + private onError(error: string): void { + if (/The request timed out|The network connection was lost/i.test(error)) { + return; + } + + error = error.replace(/See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, 'This might mean the application was put on quarantine by macOS. See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information'); + + this.notificationService.notify({ + severity: Severity.Error, + message: error, + source: nls.localize('update service', "Update Service"), + }); + } +}`; + model.setValue(origValue); + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + const fakeRequest = new class extends mock() { + override get id() { return 'one'; } + }; + session.markModelVersion(fakeRequest); + + assert.strictEqual(editor.getModel().getLineCount(), 15); + + await makeEditAsAi([EditOperation.replace(new Range(7, 1, 7, Number.MAX_SAFE_INTEGER), `error = error.replace( + /See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, + 'This might mean the application was put on quarantine by macOS. See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information' + );`)]); + + assert.strictEqual(editor.getModel().getLineCount(), 18); + + // called when a response errors out + await session.undoChangesUntil(fakeRequest.id); + await session.hunkData.recompute({ applied: 0, sha1: 'fakeSha1' }, undefined); + + assert.strictEqual(editor.getModel().getValue(), origValue); + + session.hunkData.discardAll(); // called when dimissing the session + assert.strictEqual(editor.getModel().getValue(), origValue); + }); + + test('Apply Code\'s preview should be easier to undo/esc #7537', async function () { + model.setValue(`export function fib(n) { + if (n <= 0) return 0; + if (n === 1) return 0; + if (n === 2) return 1; + return fib(n - 1) + fib(n - 2); +}`); + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.replace(new Range(5, 1, 6, Number.MAX_SAFE_INTEGER), ` + let a = 0, b = 1, c; + for (let i = 3; i <= n; i++) { + c = a + b; + a = b; + b = c; + } + return b; +}`)]); + + assert.strictEqual(session.hunkData.size, 1); + assert.strictEqual(session.hunkData.pending, 1); + assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Pending)); + + await assertSnapshot(editor.getModel().getValue(), { name: '1' }); + + await model.undo(); + await assertSnapshot(editor.getModel().getValue(), { name: '2' }); + + // overlapping edits (even UNDO) mark edits as accepted + assert.strictEqual(session.hunkData.size, 1); + assert.strictEqual(session.hunkData.pending, 0); + assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Accepted)); + + // no further change when discarding + session.hunkData.discardAll(); // CANCEL + await assertSnapshot(editor.getModel().getValue(), { name: '2' }); + }); + }); diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts index e8d229d8..c6724b09 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatStrategies.test.ts @@ -7,7 +7,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IntervalTimer } from 'vs/base/common/async'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { asProgressiveEdit } from '../../browser/utils'; -import * as assert from 'assert'; +import assert from 'assert'; suite('AsyncEdit', () => { diff --git a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts index d9b64666..409c170c 100644 --- a/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts +++ b/patched-vscode/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts @@ -9,7 +9,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { assertType } from 'vs/base/common/types'; import { DiffAlgorithmName, IEditorWorkerService, ILineChange } from 'vs/editor/common/services/editorWorker'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; -import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { MovedText } from 'vs/editor/common/diff/linesDiffComputer'; import { LineRangeMapping, DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; @@ -18,7 +18,7 @@ import { TextEdit } from 'vs/editor/common/languages'; export class TestWorkerService extends mock() { - private readonly _worker = new EditorSimpleWorker(null!, null); + private readonly _worker = new BaseEditorSimpleWorker(); constructor(@IModelService private readonly _modelService: IModelService) { super(); @@ -36,21 +36,21 @@ export class TestWorkerService extends mock() { assertType(originalModel); assertType(modifiedModel); - this._worker.acceptNewModel({ + this._worker.$acceptNewModel({ url: originalModel.uri.toString(), versionId: originalModel.getVersionId(), lines: originalModel.getLinesContent(), EOL: originalModel.getEOL(), }); - this._worker.acceptNewModel({ + this._worker.$acceptNewModel({ url: modifiedModel.uri.toString(), versionId: modifiedModel.getVersionId(), lines: modifiedModel.getLinesContent(), EOL: modifiedModel.getEOL(), }); - const result = await this._worker.computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm); + const result = await this._worker.$computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm); if (!result) { return result; } diff --git a/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index cc21b037..b69ee2a6 100644 --- a/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -26,7 +26,7 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { EditorActivation, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -95,7 +95,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork providerDisplayName: 'Interactive Notebook', displayName: 'Interactive', filenamePattern: ['*.interactive'], - exclusive: true + priority: RegisteredEditorPriority.builtin })); } @@ -134,17 +134,25 @@ export class InteractiveDocumentContribution extends Disposable implements IWork { createEditorInput: ({ resource, options }) => { const data = CellUri.parse(resource); - let cellOptions: IResourceEditorInput | undefined; - let IwResource = resource; + let cellOptions: ITextResourceEditorInput | undefined; + let iwResource = resource; if (data) { cellOptions = { resource, options }; - IwResource = data.notebook; + iwResource = data.notebook; } - const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions; + const notebookOptions: INotebookEditorOptions | undefined = { + ...options, + cellOptions, + cellRevealType: undefined, + cellSelections: undefined, + isReadOnly: undefined, + viewState: undefined, + indexedCellOptions: undefined + }; - const editorInput = createEditor(IwResource, this.instantiationService); + const editorInput = createEditor(iwResource, this.instantiationService); return { editor: editorInput, options: notebookOptions @@ -155,13 +163,21 @@ export class InteractiveDocumentContribution extends Disposable implements IWork throw new Error('Interactive window editors must have a resource name'); } const data = CellUri.parse(resource); - let cellOptions: IResourceEditorInput | undefined; + let cellOptions: ITextResourceEditorInput | undefined; if (data) { cellOptions = { resource, options }; } - const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions; + const notebookOptions: INotebookEditorOptions = { + ...options, + cellOptions, + cellRevealType: undefined, + cellSelections: undefined, + isReadOnly: undefined, + viewState: undefined, + indexedCellOptions: undefined + }; const editorInput = createEditor(resource, this.instantiationService); return { @@ -411,10 +427,10 @@ registerAction2(class extends Action2 { logService.debug('Open new interactive window:', notebookUri.toString(), inputUri.toString()); if (id) { - const allKernels = kernelService.getMatchingKernel({ uri: notebookUri, viewType: 'interactive' }).all; + const allKernels = kernelService.getMatchingKernel({ uri: notebookUri, notebookType: 'interactive' }).all; const preferredKernel = allKernels.find(kernel => kernel.id === id); if (preferredKernel) { - kernelService.preselectKernelForNotebook(preferredKernel, { uri: notebookUri, viewType: 'interactive' }); + kernelService.preselectKernelForNotebook(preferredKernel, { uri: notebookUri, notebookType: 'interactive' }); } } @@ -435,6 +451,11 @@ registerAction2(class extends Action2 { title: localize2('interactive.execute', 'Execute Code'), category: interactiveWindowCategory, keybinding: [{ + // when: NOTEBOOK_CELL_LIST_FOCUSED, + when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), + primary: KeyMod.CtrlCmd | KeyCode.Enter, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }, { when: ContextKeyExpr.and( ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', true) @@ -448,19 +469,11 @@ registerAction2(class extends Action2 { ), primary: KeyCode.Enter, weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT - }, { - // when: NOTEBOOK_CELL_LIST_FOCUSED, - when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), - primary: KeyMod.WinCtrl | KeyCode.Enter, - win: { - primary: KeyMod.CtrlCmd | KeyCode.Enter - }, - weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT }], menu: [ { id: MenuId.InteractiveInputExecute - } + }, ], icon: icons.executeIcon, f1: false, @@ -485,19 +498,20 @@ registerAction2(class extends Action2 { let editorControl: { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; if (context) { const resourceUri = URI.revive(context); - const editors = editorService.findEditors(resourceUri) - .filter(id => id.editor instanceof InteractiveEditorInput && id.editor.resource?.toString() === resourceUri.toString()); - if (editors.length) { - const editorInput = editors[0].editor as InteractiveEditorInput; - const currentGroup = editors[0].groupId; - const editor = await editorService.openEditor(editorInput, currentGroup); - editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + const editors = editorService.findEditors(resourceUri); + for (const found of editors) { + if (found.editor.typeId === InteractiveEditorInput.ID) { + const editor = await editorService.openEditor(found.editor, found.groupId); + editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + break; + } } } else { editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; } + if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) { const notebookDocument = editorControl.notebookEditor.textModel; const textModel = editorControl.codeEditor.getModel(); @@ -512,6 +526,7 @@ registerAction2(class extends Action2 { return; } + historyService.replaceLast(notebookDocument.uri, value); historyService.addToHistory(notebookDocument.uri, ''); textModel.setValue(''); @@ -590,7 +605,7 @@ registerAction2(class extends Action2 { title: localize2('interactive.history.previous', 'Previous value in history'), category: interactiveWindowCategory, f1: false, - keybinding: { + keybinding: [{ when: ContextKeyExpr.and( ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('bottom'), @@ -599,7 +614,16 @@ registerAction2(class extends Action2 { ), primary: KeyCode.UpArrow, weight: KeybindingWeight.WorkbenchContrib - }, + }, { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('activeEditor', 'workbench.editor.repl'), + INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('bottom'), + INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none'), + SuggestContext.Visible.toNegated() + ), + primary: KeyCode.UpArrow, + weight: KeybindingWeight.WorkbenchContrib + }] }); } @@ -608,6 +632,8 @@ registerAction2(class extends Action2 { const historyService = accessor.get(IInteractiveHistoryService); const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + + if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) { const notebookDocument = editorControl.notebookEditor.textModel; const textModel = editorControl.codeEditor.getModel(); @@ -629,7 +655,7 @@ registerAction2(class extends Action2 { title: localize2('interactive.history.next', 'Next value in history'), category: interactiveWindowCategory, f1: false, - keybinding: { + keybinding: [{ when: ContextKeyExpr.and( ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('top'), @@ -638,7 +664,16 @@ registerAction2(class extends Action2 { ), primary: KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib - }, + }, { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('activeEditor', 'workbench.editor.repl'), + INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('top'), + INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none'), + SuggestContext.Visible.toNegated() + ), + primary: KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib + }], }); } @@ -652,9 +687,9 @@ registerAction2(class extends Action2 { const textModel = editorControl.codeEditor.getModel(); if (notebookDocument && textModel) { - const previousValue = historyService.getNextValue(notebookDocument.uri); - if (previousValue) { - textModel.setValue(previousValue); + const nextValue = historyService.getNextValue(notebookDocument.uri); + if (nextValue !== null) { + textModel.setValue(nextValue); } } } @@ -814,10 +849,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis default: false, markdownDescription: localize('interactiveWindow.promptToSaveOnClose', "Prompt to save the interactive window when it is closed. Only new interactive windows will be affected by this setting change.") }, - ['interactiveWindow.executeWithShiftEnter']: { + [InteractiveWindowSetting.executeWithShiftEnter]: { + type: 'boolean', + default: false, + markdownDescription: localize('interactiveWindow.executeWithShiftEnter', "Execute the Interactive Window (REPL) input box with shift+enter, so that enter can be used to create a newline."), + tags: ['replExecute'] + }, + [InteractiveWindowSetting.showExecutionHint]: { type: 'boolean', default: true, - markdownDescription: localize('interactiveWindow.executeWithShiftEnter', "Execute the interactive window (REPL) input box with shift+enter, so that enter can be used to create a newline.") + markdownDescription: localize('interactiveWindow.showExecutionHint', "Display a hint in the Interactive Window (REPL) input box to indicate how to execute code."), + tags: ['replExecute'] } } }); diff --git a/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts b/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts index 46a52d9b..20ce01d8 100644 --- a/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts +++ b/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts @@ -8,5 +8,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const INTERACTIVE_INPUT_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('interactiveInputCursorAtBoundary', 'none'); export const InteractiveWindowSetting = { - interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell' + interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell', + executeWithShiftEnter: 'interactiveWindow.executeWithShiftEnter', + showExecutionHint: 'interactiveWindow.showExecutionHint' }; diff --git a/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index ba994f54..7dd408cd 100644 --- a/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -4,19 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/interactive'; -import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; -import { ICodeEditorViewState, IDecorationOptions } from 'vs/editor/common/editorCommon'; +import { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneScrollPosition, IEditorPaneSelectionChangeEvent, IEditorPaneWithScrolling } from 'vs/workbench/common/editor'; @@ -62,7 +60,10 @@ import { INTERACTIVE_WINDOW_EDITOR_ID } from 'vs/workbench/contrib/notebook/comm import 'vs/css!./interactiveEditor'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { deepClone } from 'vs/base/common/objects'; -import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; +import { MarginHoverController } from 'vs/editor/contrib/hover/browser/marginHoverController'; +import { ReplInputHintContentWidget } from 'vs/workbench/contrib/interactive/browser/replInputHintContentWidget'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; const DECORATION_KEY = 'interactiveInputDecoration'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; @@ -71,6 +72,7 @@ const INPUT_CELL_VERTICAL_PADDING = 8; const INPUT_CELL_HORIZONTAL_PADDING_RIGHT = 10; const INPUT_EDITOR_PADDING = 8; + export interface InteractiveEditorViewState { readonly notebook?: INotebookEditorViewState; readonly input?: ICodeEditorViewState | null; @@ -109,6 +111,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro private _editorMemento: IEditorMemento; private readonly _groupListener = this._register(new MutableDisposable()); private _runbuttonToolbar: ToolBar | undefined; + private _hintElement: ReplInputHintContentWidget | undefined; private _onDidFocusWidget = this._register(new Emitter()); override get onDidFocus(): Event { return this._onDidFocusWidget.event; } @@ -144,9 +147,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro themeService, storageService ); - this._instantiationService = instantiationService; this._notebookWidgetService = notebookWidgetService; - this._contextKeyService = contextKeyService; this._configurationService = configurationService; this._notebookKernelService = notebookKernelService; this._languageService = languageService; @@ -157,17 +158,22 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro this._notebookExecutionStateService = notebookExecutionStateService; this._extensionService = extensionService; + this._rootElement = DOM.$('.interactive-editor'); + this._contextKeyService = this._register(contextKeyService.createScoped(this._rootElement)); + this._contextKeyService.createKey('isCompositeNotebook', true); + this._instantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService]))); + this._editorOptions = this._computeEditorOptions(); this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) { this._editorOptions = this._computeEditorOptions(); } })); - this._notebookOptions = new NotebookOptions(this.window, configurationService, notebookExecutionStateService, codeEditorService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); + this._notebookOptions = instantiationService.createInstance(NotebookOptions, this.window, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); - this._register(this._keybindingService.onDidUpdateKeybindings(this._updateInputDecoration, this)); + this._register(this._keybindingService.onDidUpdateKeybindings(this._updateInputHint, this)); this._register(this._notebookExecutionStateService.onDidChangeExecution((e) => { if (e.type === NotebookExecutionType.cell && isEqual(e.notebook, this._notebookWidget.value?.viewModel?.notebookDocument.uri)) { const cell = this._notebookWidget.value?.getCellByHandle(e.cellHandle); @@ -187,7 +193,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro } protected createEditor(parent: HTMLElement): void { - this._rootElement = DOM.append(parent, DOM.$('.interactive-editor')); + DOM.append(parent, this._rootElement); this._rootElement.style.position = 'relative'; this._notebookEditorContainer = DOM.append(this._rootElement, DOM.$('.notebook-editor-container')); this._inputCellContainer = DOM.append(this._rootElement, DOM.$('.input-cell-container')); @@ -361,7 +367,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro this._widgetDisposableStore.clear(); - this._notebookWidget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group, notebookInput, { + this._notebookWidget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group.id, notebookInput, { isEmbedded: true, isReadOnly: true, contributions: NotebookEditorExtensionsRegistry.getSomeEditorContributions([ @@ -381,7 +387,8 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro cellEditorContributions: EditorExtensionsRegistry.getSomeEditorContributions([ SelectionClipboardContributionID, ContextMenuController.ID, - HoverController.ID, + ContentHoverController.ID, + MarginHoverController.ID, MarkerController.ID ]), options: this._notebookOptions, @@ -399,7 +406,8 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro ParameterHintsController.ID, SnippetController2.ID, TabCompletionController.ID, - HoverController.ID, + ContentHoverController.ID, + MarginHoverController.ID, MarkerController.ID ]) } @@ -485,16 +493,26 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro this._widgetDisposableStore.add(this.themeService.onDidColorThemeChange(() => { if (this.isVisible()) { - this._updateInputDecoration(); + this._updateInputHint(); } })); this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => { if (this.isVisible()) { - this._updateInputDecoration(); + this._updateInputHint(); } })); + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModel(() => { + this._updateInputHint(); + })); + + this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(InteractiveWindowSetting.showExecutionHint)) { + this._updateInputHint(); + } + }); + const cursorAtBoundaryContext = INTERACTIVE_INPUT_CURSOR_BOUNDARY.bindTo(this._contextKeyService); if (input.resource && input.historyService.has(input.resource)) { cursorAtBoundaryContext.set('top'); @@ -527,14 +545,19 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro this._widgetDisposableStore.add(editorModel.onDidChangeContent(() => { const value = editorModel.getValue(); - if (this.input?.resource && value !== '') { - (this.input as InteractiveEditorInput).historyService.replaceLast(this.input.resource, value); + if (this.input?.resource) { + const historyService = (this.input as InteractiveEditorInput).historyService; + if (!historyService.matchesCurrent(this.input.resource, value)) { + historyService.replaceLast(this.input.resource, value); + } } })); this._widgetDisposableStore.add(this._notebookWidget.value!.onDidScroll(() => this._onDidChangeScroll.fire())); this._syncWithKernel(); + + this._updateInputHint(); } override setOptions(options: INotebookEditorOptions | undefined): void { @@ -591,8 +614,6 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro NOTEBOOK_KERNEL.bindTo(this._contextKeyService).set(selectedOrSuggested.id); } } - - this._updateInputDecoration(); } layout(dimension: DOM.Dimension, position: DOM.IDomPosition): void { @@ -632,41 +653,22 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro return new DOM.Dimension(Math.max(0, width), Math.max(0, height)); } - private _updateInputDecoration(): void { + private _updateInputHint(): void { if (!this._codeEditorWidget) { return; } - if (!this._codeEditorWidget.hasModel()) { - return; - } + const shouldHide = + !this._codeEditorWidget.hasModel() || + this._configurationService.getValue(InteractiveWindowSetting.showExecutionHint) === false || + this._codeEditorWidget.getModel()!.getValueLength() !== 0; - const model = this._codeEditorWidget.getModel(); - - const decorations: IDecorationOptions[] = []; - - if (model?.getValueLength() === 0) { - const transparentForeground = resolveColorValue(editorForeground, this.themeService.getColorTheme())?.transparent(0.4); - const languageId = model.getLanguageId(); - const keybinding = this._keybindingService.lookupKeybinding('interactive.execute', this._contextKeyService)?.getLabel(); - const text = nls.localize('interactiveInputPlaceHolder', "Type '{0}' code here and press {1} to run", languageId, keybinding ?? 'ctrl+enter'); - decorations.push({ - range: { - startLineNumber: 0, - endLineNumber: 0, - startColumn: 0, - endColumn: 1 - }, - renderOptions: { - after: { - contentText: text, - color: transparentForeground ? transparentForeground.toString() : undefined - } - } - }); + if (!this._hintElement && !shouldHide) { + this._hintElement = this._instantiationService.createInstance(ReplInputHintContentWidget, this._codeEditorWidget); + } else if (this._hintElement && shouldHide) { + this._hintElement.dispose(); + this._hintElement = undefined; } - - this._codeEditorWidget.setDecorationsByType('interactive-decoration', DECORATION_KEY, decorations); } getScrollPosition(): IEditorPaneScrollPosition { @@ -701,6 +703,8 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro this._notebookWidget.value.onWillHide(); } } + + this._updateInputHint(); } override clearInput() { diff --git a/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts b/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts index cdfbf095..89fefcf0 100644 --- a/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts +++ b/patched-vscode/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts @@ -14,6 +14,7 @@ export const IInteractiveHistoryService = createDecorator>(); } + matchesCurrent(uri: URI, value: string): boolean { + const history = this._history.get(uri); + if (!history) { + return false; + } + + return history.current() === value; + } + addToHistory(uri: URI, value: string): void { - if (!this._history.has(uri)) { + const history = this._history.get(uri); + if (!history) { this._history.set(uri, new HistoryNavigator2([value], 50)); return; } - const history = this._history.get(uri)!; - history.resetCursor(); - if (history?.current() !== value) { - history?.add(value); - } + history.add(value); } + getPreviousValue(uri: URI): string | null { const history = this._history.get(uri); return history?.previous() ?? null; @@ -57,16 +65,14 @@ export class InteractiveHistoryService extends Disposable implements IInteractiv } replaceLast(uri: URI, value: string) { - if (!this._history.has(uri)) { + const history = this._history.get(uri); + if (!history) { this._history.set(uri, new HistoryNavigator2([value], 50)); return; } else { - const history = this._history.get(uri); - if (history?.current() !== value) { - history?.replaceLast(value); - } + history.replaceLast(value); + history.resetCursor(); } - } clearHistory(uri: URI) { diff --git a/patched-vscode/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts b/patched-vscode/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts new file mode 100644 index 00000000..6dc4644b --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { status } from 'vs/base/browser/ui/aria/aria'; +import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; +import { Event } from 'vs/base/common/event'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { OS } from 'vs/base/common/platform'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { InteractiveWindowSetting } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; + + +export class ReplInputHintContentWidget extends Disposable implements IContentWidget { + + private static readonly ID = 'replInput.widget.emptyHint'; + + private domNode: HTMLElement | undefined; + private ariaLabel: string = ''; + + constructor( + private readonly editor: ICodeEditor, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + ) { + super(); + + this._register(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (this.domNode && e.hasChanged(EditorOption.fontInfo)) { + this.editor.applyFontInfo(this.domNode); + } + })); + const onDidFocusEditorText = Event.debounce(this.editor.onDidFocusEditorText, () => undefined, 500); + this._register(onDidFocusEditorText(() => { + if (this.editor.hasTextFocus() && this.ariaLabel && configurationService.getValue(AccessibilityVerbositySettingId.ReplInputHint)) { + status(this.ariaLabel); + } + })); + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(InteractiveWindowSetting.executeWithShiftEnter)) { + this.setHint(); + } + })); + this.editor.addContentWidget(this); + } + + getId(): string { + return ReplInputHintContentWidget.ID; + } + + getPosition(): IContentWidgetPosition | null { + return { + position: { lineNumber: 1, column: 1 }, + preference: [ContentWidgetPositionPreference.EXACT] + }; + } + + getDomNode(): HTMLElement { + if (!this.domNode) { + this.domNode = dom.$('.empty-editor-hint'); + this.domNode.style.width = 'max-content'; + this.domNode.style.paddingLeft = '4px'; + + this.setHint(); + + this._register(dom.addDisposableListener(this.domNode, 'click', () => { + this.editor.focus(); + })); + + this.editor.applyFontInfo(this.domNode); + } + + return this.domNode; + } + + private setHint() { + if (!this.domNode) { + return; + } + while (this.domNode.firstChild) { + this.domNode.removeChild(this.domNode.firstChild); + } + + const hintElement = dom.$('div.empty-hint-text'); + hintElement.style.cursor = 'text'; + hintElement.style.whiteSpace = 'nowrap'; + + const keybinding = this.getKeybinding(); + const keybindingHintLabel = keybinding?.getLabel(); + + if (keybinding && keybindingHintLabel) { + const actionPart = localize('emptyHintText', 'Press {0} to execute. ', keybindingHintLabel); + + const [before, after] = actionPart.split(keybindingHintLabel).map((fragment) => { + const hintPart = dom.$('span', undefined, fragment); + hintPart.style.fontStyle = 'italic'; + return hintPart; + }); + + hintElement.appendChild(before); + + const label = new KeybindingLabel(hintElement, OS); + label.set(keybinding); + label.element.style.width = 'min-content'; + label.element.style.display = 'inline'; + + hintElement.appendChild(after); + this.domNode.append(hintElement); + + this.ariaLabel = actionPart.concat(localize('disableHint', ' Toggle {0} in settings to disable this hint.', AccessibilityVerbositySettingId.ReplInputHint)); + } + } + + private getKeybinding() { + const keybindings = this.keybindingService.lookupKeybindings('interactive.execute'); + const shiftEnterConfig = this.configurationService.getValue(InteractiveWindowSetting.executeWithShiftEnter); + const hasEnterChord = (kb: ResolvedKeybinding, modifier: string = '') => { + const chords = kb.getDispatchChords(); + const chord = modifier + 'Enter'; + const chordAlt = modifier + '[Enter]'; + return chords.length === 1 && (chords[0] === chord || chords[0] === chordAlt); + }; + + if (shiftEnterConfig) { + const keybinding = keybindings.find(kb => hasEnterChord(kb, 'shift+')); + if (keybinding) { + return keybinding; + } + } else { + let keybinding = keybindings.find(kb => hasEnterChord(kb)); + if (keybinding) { + return keybinding; + } + keybinding = this.keybindingService.lookupKeybindings('python.execInREPLEnter') + .find(kb => hasEnterChord(kb)); + if (keybinding) { + return keybinding; + } + } + + return keybindings?.[0]; + } + + override dispose(): void { + super.dispose(); + this.editor.removeContentWidget(this); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/issue/browser/issue.ts b/patched-vscode/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts similarity index 82% rename from patched-vscode/src/vs/workbench/contrib/issue/browser/issue.ts rename to patched-vscode/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts index 79e44855..f3e2966a 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/browser/issue.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts @@ -2,24 +2,28 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import { IProductConfiguration } from 'vs/base/common/product'; -import { $, createStyleSheet, reset, windowOpenNoOpener } from 'vs/base/browser/dom'; +import { $, createStyleSheet, isHTMLInputElement, isHTMLTextAreaElement, reset, windowOpenNoOpener } from 'vs/base/browser/dom'; import { Button, unthemedButtonStyles } from 'vs/base/browser/ui/button/button'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { mainWindow } from 'vs/base/browser/window'; import { Delayer, RunOnceScheduler } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; +import { groupBy } from 'vs/base/common/collections'; import { debounce } from 'vs/base/common/decorators'; import { CancellationError } from 'vs/base/common/errors'; -import { isLinuxSnap } from 'vs/base/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isLinuxSnap, isMacintosh } from 'vs/base/common/platform'; +import { IProductConfiguration } from 'vs/base/common/product'; import { escape } from 'vs/base/common/strings'; +import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; -import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; +import { OldIssueReporterData } from 'vs/platform/issue/common/issue'; import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/workbench/contrib/issue/browser/issueReporterModel'; -import { mainWindow } from 'vs/base/browser/window'; +import { IIssueFormService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IssueType } from 'vs/workbench/contrib/issue/common/issue'; +import { normalizeGitHubUrl } from 'vs/workbench/contrib/issue/common/issueReporterUtil'; const MAX_URL_LENGTH = 7500; @@ -53,7 +57,7 @@ export class BaseIssueReporterService extends Disposable { constructor( public disableExtensions: boolean, - public data: IssueReporterData, + public data: IssueReporterData | OldIssueReporterData, public os: { type: string; arch: string; @@ -62,7 +66,8 @@ export class BaseIssueReporterService extends Disposable { public product: IProductConfiguration, public readonly window: Window, public readonly isWeb: boolean, - @IIssueMainService public readonly issueMainService: IIssueMainService + @IIssueFormService public readonly issueFormService: IIssueFormService, + @IThemeService public readonly themeService: IThemeService, ) { super(); const targetExtension = data.extensionId ? data.enabledExtensions.find(extension => extension.id.toLocaleLowerCase() === data.extensionId?.toLocaleLowerCase()) : undefined; @@ -117,8 +122,7 @@ export class BaseIssueReporterService extends Disposable { const codiconStyleSheet = createStyleSheet(); codiconStyleSheet.id = 'codiconStyles'; - // TODO: Is there a way to use the IThemeService here instead - const iconsStyleSheet = this._register(getIconsStyleSheet(undefined)); + const iconsStyleSheet = this._register(getIconsStyleSheet(this.themeService)); function updateAll() { codiconStyleSheet.textContent = iconsStyleSheet.getCSS(); } @@ -127,6 +131,7 @@ export class BaseIssueReporterService extends Disposable { iconsStyleSheet.onDidChange(() => delayer.schedule()); delayer.schedule(); + this.handleExtensionData(data.enabledExtensions); this.setUpTypes(); this.applyStyles(data.styles); @@ -160,6 +165,11 @@ export class BaseIssueReporterService extends Disposable { content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { background-color: ${styles.inputBackground} !important; }`); } + if (styles.backgroundColor) { + content.push(`.monaco-workbench { background-color: ${styles.backgroundColor} !important; }`); + content.push(`.issue-reporter-body::-webkit-scrollbar-track { background-color: ${styles.backgroundColor}; }`); + } + if (styles.inputBorder) { content.push(`input[type="text"], textarea, select { border: 1px solid ${styles.inputBorder}; }`); } else { @@ -199,16 +209,13 @@ export class BaseIssueReporterService extends Disposable { content.push(`a:hover, .workbenchCommand:hover { color: ${styles.textLinkActiveForeground}; }`); } - if (styles.sliderBackgroundColor) { - content.push(`::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`); - } - if (styles.sliderActiveColor) { - content.push(`::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`); + content.push(`.issue-reporter-body::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`); } if (styles.sliderHoverColor) { - content.push(`::--webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`); + content.push(`.issue-reporter-body::-webkit-scrollbar-thumb { background-color: ${styles.sliderHoverColor}; }`); + content.push(`.issue-reporter-body::--webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`); } if (styles.buttonBackground) { @@ -239,27 +246,135 @@ export class BaseIssueReporterService extends Disposable { } } - public setEventHandlers(): void { - this.addEventListener('issue-type', 'change', (event: Event) => { - const issueType = parseInt((event.target).value); - this.issueReporterModel.update({ issueType: issueType }); - if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) { - this.issueMainService.$getPerformanceInfo().then(info => { - this.updatePerformanceInfo(info as Partial); - }); + private handleExtensionData(extensions: IssueReporterExtensionData[]) { + const installedExtensions = extensions.filter(x => !x.isBuiltin); + const { nonThemes, themes } = groupBy(installedExtensions, ext => { + return ext.isTheme ? 'themes' : 'nonThemes'; + }); + + const numberOfThemeExtesions = themes && themes.length; + this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); + this.updateExtensionTable(nonThemes, numberOfThemeExtesions); + if (this.disableExtensions || installedExtensions.length === 0) { + (this.getElementById('disableExtensions')).disabled = true; + } + + this.updateExtensionSelector(installedExtensions); + } + + private updateExtensionSelector(extensions: IssueReporterExtensionData[]): void { + interface IOption { + name: string; + id: string; + } + + const extensionOptions: IOption[] = extensions.map(extension => { + return { + name: extension.displayName || extension.name || '', + id: extension.id + }; + }); + + // Sort extensions by name + extensionOptions.sort((a, b) => { + const aName = a.name.toLowerCase(); + const bName = b.name.toLowerCase(); + if (aName > bName) { + return 1; } - // Resets placeholder - const descriptionTextArea = this.getElementById('issue-title'); - if (descriptionTextArea) { - descriptionTextArea.placeholder = localize('undefinedPlaceholder', "Please enter a title"); + if (aName < bName) { + return -1; } - this.updatePreviewButtonState(); - this.setSourceOptions(); - this.render(); + return 0; + }); + + const makeOption = (extension: IOption, selectedExtension?: IssueReporterExtensionData): HTMLOptionElement => { + const selected = selectedExtension && extension.id === selectedExtension.id; + return $('option', { + 'value': extension.id, + 'selected': selected || '' + }, extension.name); + }; + + const extensionsSelector = this.getElementById('extension-selector'); + if (extensionsSelector) { + const { selectedExtension } = this.issueReporterModel.getData(); + reset(extensionsSelector, this.makeOption('', localize('selectExtension', "Select extension"), true), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); + + if (!selectedExtension) { + extensionsSelector.selectedIndex = 0; + } + + this.addEventListener('extension-selector', 'change', async (e: Event) => { + this.clearExtensionData(); + const selectedExtensionId = (e.target).value; + this.selectedExtension = selectedExtensionId; + const extensions = this.issueReporterModel.getData().allExtensions; + const matches = extensions.filter(extension => extension.id === selectedExtensionId); + if (matches.length) { + this.issueReporterModel.update({ selectedExtension: matches[0] }); + const selectedExtension = this.issueReporterModel.getData().selectedExtension; + if (selectedExtension) { + const iconElement = document.createElement('span'); + iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); + this.setLoading(iconElement); + const openReporterData = await this.sendReporterMenu(selectedExtension); + if (openReporterData) { + if (this.selectedExtension === selectedExtensionId) { + this.removeLoading(iconElement, true); + // this.configuration.data = openReporterData; + this.data = openReporterData; + } + // else if (this.selectedExtension !== selectedExtensionId) { + // } + } + else { + if (!this.loadingExtensionData) { + iconElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); + } + this.removeLoading(iconElement); + // if not using command, should have no configuration data in fields we care about and check later. + this.clearExtensionData(); + + // case when previous extension was opened from normal openIssueReporter command + selectedExtension.data = undefined; + selectedExtension.uri = undefined; + } + if (this.selectedExtension === selectedExtensionId) { + // repopulates the fields with the new data given the selected extension. + this.updateExtensionStatus(matches[0]); + this.openReporter = false; + } + } else { + this.issueReporterModel.update({ selectedExtension: undefined }); + this.clearSearchResults(); + this.clearExtensionData(); + this.validateSelectedExtension(); + this.updateExtensionStatus(matches[0]); + } + } + }); + } + + this.addEventListener('problem-source', 'change', (_) => { + this.clearExtensionData(); + this.validateSelectedExtension(); }); + } + private async sendReporterMenu(extension: IssueReporterExtensionData): Promise { + try { + const data = await this.issueFormService.sendReporterMenu(extension.id); + return data; + } catch (e) { + console.error(e); + return undefined; + } + } + + public setEventHandlers(): void { (['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeExperiments', 'includeExtensionData'] as const).forEach(elementId => { this.addEventListener(elementId, 'click', (event: Event) => { event.stopPropagation(); @@ -361,6 +476,65 @@ export class BaseIssueReporterService extends Disposable { const { fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData(); this.searchIssues(title, fileOnExtension, fileOnMarketplace); }); + + this.previewButton.onDidClick(async () => { + this.delayedSubmit.trigger(async () => { + this.createIssue(); + }); + }); + + this.addEventListener('disableExtensions', 'click', () => { + this.issueFormService.reloadWithExtensionsDisabled(); + }); + + this.addEventListener('extensionBugsLink', 'click', (e: Event) => { + const url = (e.target).innerText; + windowOpenNoOpener(url); + }); + + this.addEventListener('disableExtensions', 'keydown', (e: Event) => { + e.stopPropagation(); + if ((e as KeyboardEvent).key === 'Enter' || (e as KeyboardEvent).key === ' ') { + this.issueFormService.reloadWithExtensionsDisabled(); + } + }); + + this.window.document.onkeydown = async (e: KeyboardEvent) => { + const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; + // Cmd/Ctrl+Enter previews issue and closes window + if (cmdOrCtrlKey && e.key === 'Enter') { + this.delayedSubmit.trigger(async () => { + if (await this.createIssue()) { + this.close(); + } + }); + } + + // Cmd/Ctrl + w closes issue window + if (cmdOrCtrlKey && e.key === 'w') { + e.stopPropagation(); + e.preventDefault(); + + const issueTitle = (this.getElementById('issue-title'))!.value; + const { issueDescription } = this.issueReporterModel.getData(); + if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) { + // fire and forget + this.issueFormService.showConfirmCloseDialog(); + } else { + this.close(); + } + } + + // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac + // Manually perform the selection + if (isMacintosh) { + if (cmdOrCtrlKey && e.key === 'a' && e.target) { + if (isHTMLInputElement(e.target) || isHTMLTextAreaElement(e.target)) { + (e.target).select(); + } + } + } + }; } public updatePerformanceInfo(info: Partial) { @@ -434,6 +608,10 @@ export class BaseIssueReporterService extends Disposable { if (issueType === IssueType.PerformanceIssue && this.receivedSystemInfo && this.receivedPerformanceInfo) { return true; } + + if (issueType === IssueType.FeatureRequest) { + return true; + } } return false; @@ -500,7 +678,7 @@ export class BaseIssueReporterService extends Disposable { } public async close(): Promise { - await this.issueMainService.$closeReporter(); + await this.issueFormService.closeReporter(); } public clearSearchResults(): void { @@ -790,7 +968,9 @@ export class BaseIssueReporterService extends Disposable { const inputElement = (this.getElementById(inputId)); const inputValidationMessage = this.getElementById(`${inputId}-empty-error`); const descriptionShortMessage = this.getElementById(`description-short-error`); - if (!inputElement.value) { + if (inputId === 'description' && this.nonGitHubIssueUrl && this.data.extensionId) { + return true; + } else if (!inputElement.value) { inputElement.classList.add('invalid-input'); inputValidationMessage?.classList.remove('hidden'); descriptionShortMessage?.classList.add('hidden'); @@ -800,8 +980,7 @@ export class BaseIssueReporterService extends Disposable { descriptionShortMessage?.classList.remove('hidden'); inputValidationMessage?.classList.add('hidden'); return false; - } - else { + } else { inputElement.classList.remove('invalid-input'); inputValidationMessage?.classList.add('hidden'); if (inputId === 'description') { @@ -930,7 +1109,7 @@ export class BaseIssueReporterService extends Disposable { } public async writeToClipboard(baseUrl: string, issueBody: string): Promise { - const shouldWrite = await this.issueMainService.$showClipboardDialog(); + const shouldWrite = await this.issueFormService.showClipboardDialog(); if (!shouldWrite) { throw new CancellationError(); } @@ -993,7 +1172,7 @@ export class BaseIssueReporterService extends Disposable { public clearExtensionData(): void { this.nonGitHubIssueUrl = false; this.issueReporterModel.update({ extensionData: undefined }); - this.data.issueBody = undefined; + this.data.issueBody = this.data.issueBody || ''; this.data.data = undefined; this.data.uri = undefined; } @@ -1076,7 +1255,7 @@ export class BaseIssueReporterService extends Disposable { const showLoading = this.getElementById('ext-loading')!; show(showLoading); while (showLoading.firstChild) { - showLoading.removeChild(showLoading.firstChild); + showLoading.firstChild.remove(); } showLoading.append(element); @@ -1097,7 +1276,7 @@ export class BaseIssueReporterService extends Disposable { const hideLoading = this.getElementById('ext-loading')!; hide(hideLoading); if (hideLoading.firstChild) { - hideLoading.removeChild(element); + element.remove(); } this.renderBlocks(); } @@ -1202,5 +1381,3 @@ export function hide(el: Element | undefined | null) { export function show(el: Element | undefined | null) { el?.classList.remove('hidden'); } - - diff --git a/patched-vscode/src/vs/workbench/contrib/issue/browser/issue.contribution.ts b/patched-vscode/src/vs/workbench/contrib/issue/browser/issue.contribution.ts index 668418a0..4dea77bb 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/browser/issue.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/browser/issue.contribution.ts @@ -5,19 +5,18 @@ import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { BrowserIssueService } from 'vs/workbench/contrib/issue/browser/issueService'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; -import { BaseIssueContribution } from 'vs/workbench/contrib/issue/common/issue.contribution'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IIssueMainService } from 'vs/platform/issue/common/issue'; import { IssueFormService } from 'vs/workbench/contrib/issue/browser/issueFormService'; +import { BrowserIssueService } from 'vs/workbench/contrib/issue/browser/issueService'; import 'vs/workbench/contrib/issue/browser/issueTroubleshoot'; +import { IIssueFormService, IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; +import { BaseIssueContribution } from 'vs/workbench/contrib/issue/common/issue.contribution'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; class WebIssueContribution extends BaseIssueContribution { @@ -38,7 +37,7 @@ class WebIssueContribution extends BaseIssueContribution { Registry.as(Extensions.Workbench).registerWorkbenchContribution(WebIssueContribution, LifecyclePhase.Restored); registerSingleton(IWorkbenchIssueService, BrowserIssueService, InstantiationType.Delayed); -registerSingleton(IIssueMainService, IssueFormService, InstantiationType.Delayed); +registerSingleton(IIssueFormService, IssueFormService, InstantiationType.Delayed); CommandsRegistry.registerCommand('_issues.getSystemStatus', (accessor) => { return nls.localize('statusUnsupported', "The --status argument is not yet supported in browsers."); diff --git a/patched-vscode/src/vs/workbench/contrib/issue/browser/issueFormService.ts b/patched-vscode/src/vs/workbench/contrib/issue/browser/issueFormService.ts index ca24026d..3a7b2ef1 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/browser/issueFormService.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/browser/issueFormService.ts @@ -3,105 +3,80 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { safeInnerHtml } from 'vs/base/browser/dom'; -import { mainWindow } from 'vs/base/browser/window'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import BaseHtml from 'vs/workbench/contrib/issue/browser/issueReporterPage'; +import Severity from 'vs/base/common/severity'; import 'vs/css!./media/issueReporter'; +import { localize } from 'vs/nls'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { PerformanceInfo, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ExtensionIdentifier, ExtensionIdentifierSet } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IIssueMainService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { ILogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; +import { IRectangle } from 'vs/platform/window/common/window'; +import BaseHtml from 'vs/workbench/contrib/issue/browser/issueReporterPage'; import { IssueWebReporter } from 'vs/workbench/contrib/issue/browser/issueReporterService'; +import { IIssueFormService, IssueReporterData } from 'vs/workbench/contrib/issue/common/issue'; import { AuxiliaryWindowMode, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export interface IssuePassData { issueTitle: string; issueBody: string; } -export class IssueFormService implements IIssueMainService { +export class IssueFormService implements IIssueFormService { readonly _serviceBrand: undefined; - private issueReporterWindow: Window | null = null; - private extensionIdentifierSet: ExtensionIdentifierSet = new ExtensionIdentifierSet(); + protected currentData: IssueReporterData | undefined; - constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IAuxiliaryWindowService private readonly auxiliaryWindowService: IAuxiliaryWindowService, - @IMenuService private readonly menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - ) { - - // listen for messages from the main window - mainWindow.addEventListener('message', async (event) => { - if (event.data && event.data.sendChannel === 'vscode:triggerReporterMenu') { - // creates menu from contributed - const menu = this.menuService.createMenu(MenuId.IssueReporter, this.contextKeyService); - - // render menu and dispose - const actions = menu.getActions({ renderShortTitle: true }).flatMap(entry => entry[1]); - for (const action of actions) { - try { - if (action.item && 'source' in action.item && action.item.source?.id === event.data.extensionId) { - this.extensionIdentifierSet.add(event.data.extensionId); - await action.run(); - } - } catch (error) { - console.error(error); - } - } + protected issueReporterWindow: Window | null = null; + protected extensionIdentifierSet: ExtensionIdentifierSet = new ExtensionIdentifierSet(); - if (!this.extensionIdentifierSet.has(event.data.extensionId)) { - // send undefined to indicate no action was taken - const replyChannel = `vscode:triggerReporterMenuResponse`; - mainWindow.postMessage({ replyChannel }, '*'); - } + protected arch: string = ''; + protected release: string = ''; + protected type: string = ''; - menu.dispose(); - } - }); - - } + constructor( + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IAuxiliaryWindowService protected readonly auxiliaryWindowService: IAuxiliaryWindowService, + @IMenuService protected readonly menuService: IMenuService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, + @ILogService protected readonly logService: ILogService, + @IDialogService protected readonly dialogService: IDialogService, + @IHostService protected readonly hostService: IHostService + ) { } async openReporter(data: IssueReporterData): Promise { - if (data.extensionId && this.extensionIdentifierSet.has(data.extensionId)) { - const replyChannel = `vscode:triggerReporterMenuResponse`; - mainWindow.postMessage({ data, replyChannel }, '*'); - this.extensionIdentifierSet.delete(new ExtensionIdentifier(data.extensionId)); + if (this.hasToReload(data)) { + return; } + await this.openAuxIssueReporter(data); + if (this.issueReporterWindow) { - const getModelData = await this.getIssueData(); - if (getModelData) { - const { issueTitle, issueBody } = getModelData; - if (issueTitle || issueBody) { - data.issueTitle = data.issueTitle ?? issueTitle; - data.issueBody = data.issueBody ?? issueBody; - - // close issue reporter and re-open with new data - this.issueReporterWindow.close(); - this.openAuxIssueReporter(data); - return; - } - } - this.issueReporterWindow.focus(); - return; + const issueReporter = this.instantiationService.createInstance(IssueWebReporter, false, data, { type: this.type, arch: this.arch, release: this.release }, product, this.issueReporterWindow); + issueReporter.render(); } - this.openAuxIssueReporter(data); } - async openAuxIssueReporter(data: IssueReporterData): Promise { + async openAuxIssueReporter(data: IssueReporterData, bounds?: IRectangle): Promise { + + let issueReporterBounds: Partial = { width: 700, height: 800 }; + + // Center Issue Reporter Window based on bounds from native host service + if (bounds && bounds.x && bounds.y) { + const centerX = bounds.x + bounds.width / 2; + const centerY = bounds.y + bounds.height / 2; + issueReporterBounds = { ...issueReporterBounds, x: centerX - 350, y: centerY - 400 }; + } + const disposables = new DisposableStore(); // Auxiliary Window - const auxiliaryWindow = disposables.add(await this.auxiliaryWindowService.open({ mode: AuxiliaryWindowMode.Normal })); - - this.issueReporterWindow = auxiliaryWindow.window; + const auxiliaryWindow = disposables.add(await this.auxiliaryWindowService.open({ mode: AuxiliaryWindowMode.Normal, bounds: issueReporterBounds, nativeTitlebar: true, disableFullscreen: true })); if (auxiliaryWindow) { await auxiliaryWindow.whenStylesHaveLoaded; @@ -117,9 +92,7 @@ export class IssueFormService implements IIssueMainService { auxiliaryWindow.window.document.body.appendChild(div); safeInnerHtml(div, BaseHtml()); - // create issue reporter and instantiate - const issueReporter = this.instantiationService.createInstance(IssueWebReporter, false, data, { type: '', arch: '', release: '' }, product, auxiliaryWindow.window); - issueReporter.render(); + this.issueReporterWindow = auxiliaryWindow.window; } else { console.error('Failed to open auxiliary window'); } @@ -131,94 +104,108 @@ export class IssueFormService implements IIssueMainService { }); } - async openProcessExplorer(data: ProcessExplorerData): Promise { - throw new Error('Method not implemented.'); - } + async sendReporterMenu(extensionId: string): Promise { + const menu = this.menuService.createMenu(MenuId.IssueReporter, this.contextKeyService); - stopTracing(): Promise { - throw new Error('Method not implemented.'); - } - getSystemStatus(): Promise { - throw new Error('Method not implemented.'); - } - $getSystemInfo(): Promise { - throw new Error('Method not implemented.'); - } - $getPerformanceInfo(): Promise { - throw new Error('Method not implemented.'); - } - $reloadWithExtensionsDisabled(): Promise { - throw new Error('Method not implemented.'); - } - $showConfirmCloseDialog(): Promise { - throw new Error('Method not implemented.'); - } - $showClipboardDialog(): Promise { - throw new Error('Method not implemented.'); - } - $getIssueReporterUri(extensionId: string): Promise { - throw new Error('Method not implemented.'); - } - $getIssueReporterData(extensionId: string): Promise { - throw new Error('Method not implemented.'); + // render menu and dispose + const actions = menu.getActions({ renderShortTitle: true }).flatMap(entry => entry[1]); + for (const action of actions) { + try { + if (action.item && 'source' in action.item && action.item.source?.id === extensionId) { + this.extensionIdentifierSet.add(extensionId); + await action.run(); + } + } catch (error) { + console.error(error); + } + } + + if (!this.extensionIdentifierSet.has(extensionId)) { + // send undefined to indicate no action was taken + return undefined; + } + + // we found the extension, now we clean up the menu and remove it from the set. This is to ensure that we do duplicate extension identifiers + this.extensionIdentifierSet.delete(new ExtensionIdentifier(extensionId)); + menu.dispose(); + + const result = this.currentData; + + // reset current data. + this.currentData = undefined; + + return result ?? undefined; } - $getIssueReporterTemplate(extensionId: string): Promise { - throw new Error('Method not implemented.'); + + //#region used by issue reporter + + async closeReporter(): Promise { + this.issueReporterWindow?.close(); } - $getReporterStatus(extensionId: string, extensionName: string): Promise { - throw new Error('Method not implemented.'); + + async reloadWithExtensionsDisabled(): Promise { + if (this.issueReporterWindow) { + try { + await this.hostService.reload({ disableExtensions: true }); + } catch (error) { + this.logService.error(error); + } + } } - async $sendReporterMenu(extensionId: string, extensionName: string): Promise { - const sendChannel = `vscode:triggerReporterMenu`; - mainWindow.postMessage({ sendChannel, extensionId, extensionName }, '*'); - - const result = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - mainWindow.removeEventListener('message', listener); - reject(new Error('Timeout exceeded')); - }, 5000); // Set the timeout value in milliseconds (e.g., 5000 for 5 seconds) - - const listener = (event: MessageEvent) => { - const replyChannel = `vscode:triggerReporterMenuResponse`; - if (event.data && event.data.replyChannel === replyChannel) { - clearTimeout(timeout); - mainWindow.removeEventListener('message', listener); - resolve(event.data.data); + async showConfirmCloseDialog(): Promise { + await this.dialogService.prompt({ + type: Severity.Warning, + message: localize('confirmCloseIssueReporter', "Your input will not be saved. Are you sure you want to close this window?"), + buttons: [ + { + label: localize({ key: 'yes', comment: ['&& denotes a mnemonic'] }, "&&Yes"), + run: () => { + this.closeReporter(); + this.issueReporterWindow = null; + } + }, + { + label: localize('cancel', "Cancel"), + run: () => { } } - }; - mainWindow.addEventListener('message', listener); + ] }); - - return result as IssueReporterData | undefined; } - // Listens to data from the issue reporter model, which is updated regularly - async getIssueData(): Promise { - const sendChannel = `vscode:triggerIssueData`; - mainWindow.postMessage({ sendChannel }, '*'); - - const result = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - mainWindow.removeEventListener('message', listener); - reject(new Error('Timeout exceeded')); - }, 5000); // Set the timeout value in milliseconds (e.g., 5000 for 5 seconds) - - const listener = (event: MessageEvent) => { - const replyChannel = `vscode:triggerIssueDataResponse`; - if (event.data && event.data.replyChannel === replyChannel) { - clearTimeout(timeout); - mainWindow.removeEventListener('message', listener); - resolve(event.data.data); + async showClipboardDialog(): Promise { + let result = false; + + await this.dialogService.prompt({ + type: Severity.Warning, + message: localize('issueReporterWriteToClipboard', "There is too much data to send to GitHub directly. The data will be copied to the clipboard, please paste it into the GitHub issue page that is opened."), + buttons: [ + { + label: localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK"), + run: () => { result = true; } + }, + { + label: localize('cancel', "Cancel"), + run: () => { result = false; } } - }; - mainWindow.addEventListener('message', listener); + ] }); - return result as IssuePassData | undefined; + return result; } - async $closeReporter(): Promise { - this.issueReporterWindow?.close(); + hasToReload(data: IssueReporterData): boolean { + if (data.extensionId && this.extensionIdentifierSet.has(data.extensionId)) { + this.currentData = data; + this.issueReporterWindow?.focus(); + return true; + } + + if (this.issueReporterWindow) { + this.issueReporterWindow.focus(); + return true; + } + + return false; } } diff --git a/patched-vscode/src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts b/patched-vscode/src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts index 37d52199..7e9169f0 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts @@ -11,10 +11,10 @@ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; -import { IssueSource } from 'vs/platform/issue/common/issue'; +import { IssueSource } from 'vs/workbench/contrib/issue/common/issue'; import { IProductService } from 'vs/platform/product/common/productService'; export class IssueQuickAccess extends PickerQuickAccessProvider { @@ -65,13 +65,8 @@ export class IssueQuickAccess extends PickerQuickAccessProvider entry[1]); - - menu.dispose(); + // gets menu actions from contributed + const actions = this.menuService.getMenuActions(MenuId.IssueReporter, this.contextKeyService, { renderShortTitle: true }).flatMap(entry => entry[1]); // create picks from contributed menu actions.forEach(action => { @@ -107,7 +102,7 @@ export class IssueQuickAccess extends PickerQuickAccessProvider ` ${escape(localize('show', "show"))} -
    +			
     				
     			
    @@ -107,7 +107,7 @@ export default (): string => ` +
    diff --git a/patched-vscode/src/vs/workbench/contrib/issue/browser/issueReporterService.ts b/patched-vscode/src/vs/workbench/contrib/issue/browser/issueReporterService.ts index 59eae428..866a12c7 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/browser/issueReporterService.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/browser/issueReporterService.ts @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, isHTMLInputElement, isHTMLTextAreaElement, reset, windowOpenNoOpener } from 'vs/base/browser/dom'; -import { Codicon } from 'vs/base/common/codicons'; -import { groupBy } from 'vs/base/common/collections'; -import { isMacintosh } from 'vs/base/common/platform'; import { IProductConfiguration } from 'vs/base/common/product'; -import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; -import { IIssueMainService, IssueReporterData, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; -import { BaseIssueReporterService } from 'vs/workbench/contrib/issue/browser/issue'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { BaseIssueReporterService } from 'vs/workbench/contrib/issue/browser/baseIssueReporterService'; +import { IIssueFormService, IssueReporterData } from 'vs/workbench/contrib/issue/common/issue'; // GitHub has let us know that we could up our limit here to 8k. We chose 7500 to play it safe. // ref https://github.com/microsoft/vscode/issues/159191 @@ -26,9 +22,10 @@ export class IssueWebReporter extends BaseIssueReporterService { }, product: IProductConfiguration, window: Window, - @IIssueMainService issueMainService: IIssueMainService + @IIssueFormService issueFormService: IIssueFormService, + @IThemeService themeService: IThemeService ) { - super(disableExtensions, data, os, product, window, true, issueMainService); + super(disableExtensions, data, os, product, window, true, issueFormService, themeService); const target = this.window.document.querySelector('.block-system .block-info'); @@ -37,194 +34,27 @@ export class IssueWebReporter extends BaseIssueReporterService { target?.appendChild(this.window.document.createTextNode(webInfo)); this.receivedSystemInfo = true; this.issueReporterModel.update({ systemInfoWeb: webInfo }); - } this.setEventHandlers(); - this.handleExtensionData(data.enabledExtensions); - } - - private handleExtensionData(extensions: IssueReporterExtensionData[]) { - const installedExtensions = extensions.filter(x => !x.isBuiltin); - const { nonThemes, themes } = groupBy(installedExtensions, ext => { - return ext.isTheme ? 'themes' : 'nonThemes'; - }); - - const numberOfThemeExtesions = themes && themes.length; - this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); - this.updateExtensionTable(nonThemes, numberOfThemeExtesions); - if (this.disableExtensions || installedExtensions.length === 0) { - (this.getElementById('disableExtensions')).disabled = true; - } - - this.updateExtensionSelector(installedExtensions); - } - - private async sendReporterMenu(extension: IssueReporterExtensionData): Promise { - try { - const data = await this.issueMainService.$sendReporterMenu(extension.id, extension.name); - return data; - } catch (e) { - console.error(e); - return undefined; - } } public override setEventHandlers(): void { super.setEventHandlers(); - this.previewButton.onDidClick(async () => { - this.delayedSubmit.trigger(async () => { - this.createIssue(); - }); - }); - - this.addEventListener('disableExtensions', 'click', () => { - this.issueMainService.$reloadWithExtensionsDisabled(); - }); - - this.addEventListener('extensionBugsLink', 'click', (e: Event) => { - const url = (e.target).innerText; - windowOpenNoOpener(url); - }); - - this.addEventListener('disableExtensions', 'keydown', (e: Event) => { - e.stopPropagation(); - if ((e as KeyboardEvent).key === 'Enter' || (e as KeyboardEvent).key === ' ') { - this.issueMainService.$reloadWithExtensionsDisabled(); - } - }); - - this.window.document.onkeydown = async (e: KeyboardEvent) => { - const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; - // Cmd/Ctrl+Enter previews issue and closes window - if (cmdOrCtrlKey && e.key === 'Enter') { - this.delayedSubmit.trigger(async () => { - if (await this.createIssue()) { - this.close(); - } - }); - } - // Cmd/Ctrl + w closes issue window - if (cmdOrCtrlKey && e.key === 'w') { - e.stopPropagation(); - e.preventDefault(); + this.addEventListener('issue-type', 'change', (event: Event) => { + const issueType = parseInt((event.target).value); + this.issueReporterModel.update({ issueType: issueType }); - const issueTitle = (this.getElementById('issue-title'))!.value; - const { issueDescription } = this.issueReporterModel.getData(); - if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) { - // fire and forget - this.issueMainService.$showConfirmCloseDialog(); - } else { - this.close(); - } + // Resets placeholder + const descriptionTextArea = this.getElementById('issue-title'); + if (descriptionTextArea) { + descriptionTextArea.placeholder = localize('undefinedPlaceholder', "Please enter a title"); } - // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac - // Manually perform the selection - if (isMacintosh) { - if (cmdOrCtrlKey && e.key === 'a' && e.target) { - if (isHTMLInputElement(e.target) || isHTMLTextAreaElement(e.target)) { - (e.target).select(); - } - } - } - }; - } - - private updateExtensionSelector(extensions: IssueReporterExtensionData[]): void { - interface IOption { - name: string; - id: string; - } - - const extensionOptions: IOption[] = extensions.map(extension => { - return { - name: extension.displayName || extension.name || '', - id: extension.id - }; - }); - - // Sort extensions by name - extensionOptions.sort((a, b) => { - const aName = a.name.toLowerCase(); - const bName = b.name.toLowerCase(); - if (aName > bName) { - return 1; - } - - if (aName < bName) { - return -1; - } - - return 0; - }); - - const makeOption = (extension: IOption, selectedExtension?: IssueReporterExtensionData): HTMLOptionElement => { - const selected = selectedExtension && extension.id === selectedExtension.id; - return $('option', { - 'value': extension.id, - 'selected': selected || '' - }, extension.name); - }; - - const extensionsSelector = this.getElementById('extension-selector'); - if (extensionsSelector) { - const { selectedExtension } = this.issueReporterModel.getData(); - reset(extensionsSelector, this.makeOption('', localize('selectExtension', "Select extension"), true), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); - - if (!selectedExtension) { - extensionsSelector.selectedIndex = 0; - } - - this.addEventListener('extension-selector', 'change', async (e: Event) => { - this.clearExtensionData(); - const selectedExtensionId = (e.target).value; - this.selectedExtension = selectedExtensionId; - const extensions = this.issueReporterModel.getData().allExtensions; - const matches = extensions.filter(extension => extension.id === selectedExtensionId); - if (matches.length) { - this.issueReporterModel.update({ selectedExtension: matches[0] }); - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - if (selectedExtension) { - const iconElement = document.createElement('span'); - iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); - this.setLoading(iconElement); - const openReporterData = await this.sendReporterMenu(selectedExtension); - if (openReporterData) { - if (this.selectedExtension === selectedExtensionId) { - this.removeLoading(iconElement, true); - this.data = openReporterData; - } else if (this.selectedExtension !== selectedExtensionId) { - } - } - else { - if (!this.loadingExtensionData) { - iconElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); - } - this.removeLoading(iconElement); - this.clearExtensionData(); - selectedExtension.data = undefined; - selectedExtension.uri = undefined; - } - if (this.selectedExtension === selectedExtensionId) { - // repopulates the fields with the new data given the selected extension. - this.updateExtensionStatus(matches[0]); - this.openReporter = false; - } - } else { - this.issueReporterModel.update({ selectedExtension: undefined }); - this.clearSearchResults(); - this.clearExtensionData(); - this.validateSelectedExtension(); - this.updateExtensionStatus(matches[0]); - } - } - }); - } - - this.addEventListener('problem-source', 'change', (_) => { - this.validateSelectedExtension(); + this.updatePreviewButtonState(); + this.setSourceOptions(); + this.render(); }); } } diff --git a/patched-vscode/src/vs/workbench/contrib/issue/browser/issueService.ts b/patched-vscode/src/vs/workbench/contrib/issue/browser/issueService.ts index 6030bbfd..0089a78d 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/browser/issueService.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/browser/issueService.ts @@ -3,27 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; -import { userAgent } from 'vs/base/common/platform'; -import { IExtensionDescription, ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; import { getZoomLevel } from 'vs/base/browser/browser'; +import * as dom from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; +import { userAgent } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles } from 'vs/platform/issue/common/issue'; +import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; import { IProductService } from 'vs/platform/product/common/productService'; import { buttonBackground, buttonForeground, buttonHoverBackground, foreground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IIssueFormService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; export class BrowserIssueService implements IWorkbenchIssueService { @@ -32,7 +31,7 @@ export class BrowserIssueService implements IWorkbenchIssueService { constructor( @IExtensionService private readonly extensionService: IExtensionService, @IProductService private readonly productService: IProductService, - @IIssueMainService private readonly issueMainService: IIssueMainService, + @IIssueFormService private readonly issueFormService: IIssueFormService, @IThemeService private readonly themeService: IThemeService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService, @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @@ -43,11 +42,6 @@ export class BrowserIssueService implements IWorkbenchIssueService { @IConfigurationService private readonly configurationService: IConfigurationService ) { } - //TODO @TylerLeonhardt @Tyriar to implement a process explorer for the web - async openProcessExplorer(): Promise { - console.error('openProcessExplorer is not implemented in web'); - } - async openReporter(options: Partial): Promise { // If web reporter setting is false open the old GitHub issue reporter if (!this.configurationService.getValue('issueReporter.experimental.webReporter')) { @@ -141,7 +135,7 @@ export class BrowserIssueService implements IWorkbenchIssueService { githubAccessToken }, options); - return this.issueMainService.openReporter(issueReporterData); + return this.issueFormService.openReporter(issueReporterData); } throw new Error(`No issue reporting URL configured for ${this.productService.nameLong}.`); diff --git a/patched-vscode/src/vs/workbench/contrib/issue/browser/test/testReporterModel.test.ts b/patched-vscode/src/vs/workbench/contrib/issue/browser/test/testReporterModel.test.ts new file mode 100644 index 00000000..0ca567fa --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/issue/browser/test/testReporterModel.test.ts @@ -0,0 +1,340 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// eslint-disable-next-line local/code-import-patterns +import assert from 'assert'; +// eslint-disable-next-line local/code-import-patterns +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IssueReporterModel } from 'vs/workbench/contrib/issue/browser/issueReporterModel'; +import { IssueType } from 'vs/workbench/contrib/issue/common/issue'; +import { normalizeGitHubUrl } from 'vs/workbench/contrib/issue/common/issueReporterUtil'; + +suite('IssueReporter', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('sets defaults to include all data', () => { + const issueReporterModel = new IssueReporterModel(); + assert.deepStrictEqual(issueReporterModel.getData(), { + allExtensions: [], + includeSystemInfo: true, + includeExtensionData: true, + includeWorkspaceInfo: true, + includeProcessInfo: true, + includeExtensions: true, + includeExperiments: true, + issueType: 0 + }); + }); + + test('serializes model skeleton when no data is provided', () => { + const issueReporterModel = new IssueReporterModel({}); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: + +Extensions: none +`); + }); + + test('serializes GPU information when data is provided', () => { + const issueReporterModel = new IssueReporterModel({ + issueType: 0, + systemInfo: { + os: 'Darwin', + cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)', + memory: '16.00GB', + vmHint: '0%', + processArgs: '', + screenReader: 'no', + remoteData: [], + gpuStatus: { + '2d_canvas': 'enabled', + 'checker_imaging': 'disabled_off' + } + } + }); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: + +
    +System Info + +|Item|Value| +|---|---| +|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)| +|GPU Status|2d_canvas: enabled
    checker_imaging: disabled_off| +|Load (avg)|undefined| +|Memory (System)|16.00GB| +|Process Argv|| +|Screen Reader|no| +|VM|0%| +
    Extensions: none +`); + }); + + test('serializes experiment info when data is provided', () => { + const issueReporterModel = new IssueReporterModel({ + issueType: 0, + systemInfo: { + os: 'Darwin', + cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)', + memory: '16.00GB', + vmHint: '0%', + processArgs: '', + screenReader: 'no', + remoteData: [], + gpuStatus: { + '2d_canvas': 'enabled', + 'checker_imaging': 'disabled_off' + } + }, + experimentInfo: 'vsliv695:30137379\nvsins829:30139715' + }); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: + +
    +System Info + +|Item|Value| +|---|---| +|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)| +|GPU Status|2d_canvas: enabled
    checker_imaging: disabled_off| +|Load (avg)|undefined| +|Memory (System)|16.00GB| +|Process Argv|| +|Screen Reader|no| +|VM|0%| +
    Extensions: none
    +A/B Experiments + +\`\`\` +vsliv695:30137379 +vsins829:30139715 +\`\`\` + +
    + +`); + }); + + test('serializes Linux environment information when data is provided', () => { + const issueReporterModel = new IssueReporterModel({ + issueType: 0, + systemInfo: { + os: 'Darwin', + cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)', + memory: '16.00GB', + vmHint: '0%', + processArgs: '', + screenReader: 'no', + remoteData: [], + gpuStatus: {}, + linuxEnv: { + desktopSession: 'ubuntu', + xdgCurrentDesktop: 'ubuntu', + xdgSessionDesktop: 'ubuntu:GNOME', + xdgSessionType: 'x11' + } + } + }); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: + +
    +System Info + +|Item|Value| +|---|---| +|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)| +|GPU Status|| +|Load (avg)|undefined| +|Memory (System)|16.00GB| +|Process Argv|| +|Screen Reader|no| +|VM|0%| +|DESKTOP_SESSION|ubuntu| +|XDG_CURRENT_DESKTOP|ubuntu| +|XDG_SESSION_DESKTOP|ubuntu:GNOME| +|XDG_SESSION_TYPE|x11| +
    Extensions: none +`); + }); + + test('serializes remote information when data is provided', () => { + const issueReporterModel = new IssueReporterModel({ + issueType: 0, + systemInfo: { + os: 'Darwin', + cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)', + memory: '16.00GB', + vmHint: '0%', + processArgs: '', + screenReader: 'no', + gpuStatus: { + '2d_canvas': 'enabled', + 'checker_imaging': 'disabled_off' + }, + remoteData: [ + { + hostName: 'SSH: Pineapple', + machineInfo: { + os: 'Linux x64 4.18.0', + cpus: 'Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz (2 x 2294)', + memory: '8GB', + vmHint: '100%' + } + } + ] + } + }); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: +Remote OS version: Linux x64 4.18.0 + +
    +System Info + +|Item|Value| +|---|---| +|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)| +|GPU Status|2d_canvas: enabled
    checker_imaging: disabled_off| +|Load (avg)|undefined| +|Memory (System)|16.00GB| +|Process Argv|| +|Screen Reader|no| +|VM|0%| + +|Item|Value| +|---|---| +|Remote|SSH: Pineapple| +|OS|Linux x64 4.18.0| +|CPUs|Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz (2 x 2294)| +|Memory (System)|8GB| +|VM|100%| +
    Extensions: none +`); + }); + + test('escapes backslashes in processArgs', () => { + const issueReporterModel = new IssueReporterModel({ + issueType: 0, + systemInfo: { + os: 'Darwin', + cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)', + memory: '16.00GB', + vmHint: '0%', + processArgs: '\\\\HOST\\path', + screenReader: 'no', + remoteData: [], + gpuStatus: {} + } + }); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: + +
    +System Info + +|Item|Value| +|---|---| +|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)| +|GPU Status|| +|Load (avg)|undefined| +|Memory (System)|16.00GB| +|Process Argv|\\\\\\\\HOST\\\\path| +|Screen Reader|no| +|VM|0%| +
    Extensions: none +`); + }); + + test('should supply mode if applicable', () => { + const issueReporterModel = new IssueReporterModel({ + isUnsupported: true, + restrictedMode: true + }); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: Restricted, Unsupported + +Extensions: none +`); + }); + test('should normalize GitHub urls', () => { + [ + 'https://github.com/repo', + 'https://github.com/repo/', + 'https://github.com/repo.git', + 'https://github.com/repo/issues', + 'https://github.com/repo/issues/', + 'https://github.com/repo/issues/new', + 'https://github.com/repo/issues/new/' + ].forEach(url => { + assert.strictEqual('https://github.com/repo', normalizeGitHubUrl(url)); + }); + }); + + test('should have support for filing on extensions for bugs, performance issues, and feature requests', () => { + [ + IssueType.Bug, + IssueType.FeatureRequest, + IssueType.PerformanceIssue + ].forEach(type => { + const issueReporterModel = new IssueReporterModel({ + issueType: type, + fileOnExtension: true + }); + + assert.strictEqual(issueReporterModel.fileOnExtension(), true); + }); + }); +}); diff --git a/patched-vscode/src/vs/workbench/contrib/issue/common/issue.contribution.ts b/patched-vscode/src/vs/workbench/contrib/issue/common/issue.contribution.ts index 8e4d7795..b3e9a06e 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/common/issue.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/common/issue.contribution.ts @@ -8,10 +8,9 @@ import { ICommandAction } from 'vs/platform/action/common/action'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandMetadata } from 'vs/platform/commands/common/commands'; -import { IssueReporterData } from 'vs/platform/issue/common/issue'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; +import { IWorkbenchIssueService, IssueReporterData } from 'vs/workbench/contrib/issue/common/issue'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/workbench/contrib/issue/common/issue.ts b/patched-vscode/src/vs/workbench/contrib/issue/common/issue.ts index 3ecd103c..978b6fb1 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/common/issue.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/common/issue.ts @@ -2,13 +2,152 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + +import { UriComponents } from 'vs/base/common/uri'; +import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IssueReporterData } from 'vs/platform/issue/common/issue'; +import { OldIssueReporterData } from 'vs/platform/issue/common/issue'; + +// Since data sent through the service is serialized to JSON, functions will be lost, so Color objects +// should not be sent as their 'toString' method will be stripped. Instead convert to strings before sending. +export interface WindowStyles { + backgroundColor?: string; + color?: string; +} +export interface WindowData { + styles: WindowStyles; + zoomLevel: number; +} + +export const enum IssueType { + Bug, + PerformanceIssue, + FeatureRequest +} + +export enum IssueSource { + VSCode = 'vscode', + Extension = 'extension', + Marketplace = 'marketplace' +} + +export interface IssueReporterStyles extends WindowStyles { + textLinkColor?: string; + textLinkActiveForeground?: string; + inputBackground?: string; + inputForeground?: string; + inputBorder?: string; + inputErrorBorder?: string; + inputErrorBackground?: string; + inputErrorForeground?: string; + inputActiveBorder?: string; + buttonBackground?: string; + buttonForeground?: string; + buttonHoverBackground?: string; + sliderBackgroundColor?: string; + sliderHoverColor?: string; + sliderActiveColor?: string; +} + +export interface IssueReporterExtensionData { + name: string; + publisher: string | undefined; + version: string; + id: string; + isTheme: boolean; + isBuiltin: boolean; + displayName: string | undefined; + repositoryUrl: string | undefined; + bugsUrl: string | undefined; + extensionData?: string; + extensionTemplate?: string; + data?: string; + uri?: UriComponents; +} + +export interface IssueReporterData extends WindowData { + styles: IssueReporterStyles; + enabledExtensions: IssueReporterExtensionData[]; + issueType?: IssueType; + issueSource?: IssueSource; + extensionId?: string; + experiments?: string; + restrictedMode: boolean; + isUnsupported: boolean; + githubAccessToken: string; + issueTitle?: string; + issueBody?: string; + data?: string; + uri?: UriComponents; +} + +export interface ISettingSearchResult { + extensionId: string; + key: string; + score: number; +} + +export interface ProcessExplorerStyles extends WindowStyles { + listHoverBackground?: string; + listHoverForeground?: string; + listFocusBackground?: string; + listFocusForeground?: string; + listFocusOutline?: string; + listActiveSelectionBackground?: string; + listActiveSelectionForeground?: string; + listHoverOutline?: string; + scrollbarShadowColor?: string; + scrollbarSliderBackgroundColor?: string; + scrollbarSliderHoverBackgroundColor?: string; + scrollbarSliderActiveBackgroundColor?: string; +} + +export interface ProcessExplorerData extends WindowData { + pid: number; + styles: ProcessExplorerStyles; + platform: string; + applicationName: string; +} + +export interface IssueReporterWindowConfiguration extends ISandboxConfiguration { + disableExtensions: boolean; + data: IssueReporterData | OldIssueReporterData; + os: { + type: string; + arch: string; + release: string; + }; +} + +export interface ProcessExplorerWindowConfiguration extends ISandboxConfiguration { + data: ProcessExplorerData; +} + +export const IIssueFormService = createDecorator('issueFormService'); + +export interface IIssueFormService { + readonly _serviceBrand: undefined; + + // Used by the issue reporter + openReporter(data: IssueReporterData): Promise; + reloadWithExtensionsDisabled(): Promise; + showConfirmCloseDialog(): Promise; + showClipboardDialog(): Promise; + sendReporterMenu(extensionId: string): Promise; + closeReporter(): Promise; +} export const IWorkbenchIssueService = createDecorator('workbenchIssueService'); export interface IWorkbenchIssueService { readonly _serviceBrand: undefined; openReporter(dataOverrides?: Partial): Promise; +} + +export const IWorkbenchProcessService = createDecorator('workbenchProcessService'); + +export interface IWorkbenchProcessService { + readonly _serviceBrand: undefined; openProcessExplorer(): Promise; } + diff --git a/patched-vscode/src/vs/workbench/contrib/issue/common/issueReporterUtil.ts b/patched-vscode/src/vs/workbench/contrib/issue/common/issueReporterUtil.ts new file mode 100644 index 00000000..da2b397b --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/issue/common/issueReporterUtil.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { rtrim } from 'vs/base/common/strings'; + +export function normalizeGitHubUrl(url: string): string { + // If the url has a .git suffix, remove it + if (url.endsWith('.git')) { + url = url.substr(0, url.length - 4); + } + + // Remove trailing slash + url = rtrim(url, '/'); + + if (url.endsWith('/new')) { + url = rtrim(url, '/new'); + } + + if (url.endsWith('/issues')) { + url = rtrim(url, '/issues'); + } + + return url; +} diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts index 3b86046d..bc56d83a 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issue.contribution.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { localize, localize2 } from 'vs/nls'; -import { MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { IWorkbenchIssueService, IssueType, IIssueFormService } from 'vs/workbench/contrib/issue/common/issue'; import { BaseIssueContribution } from 'vs/workbench/contrib/issue/common/issue.contribution'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -14,22 +13,23 @@ import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INativeHostService } from 'vs/platform/native/common/native'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IIssueMainService, IssueType } from 'vs/platform/issue/common/issue'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { IssueQuickAccess } from 'vs/workbench/contrib/issue/browser/issueQuickAccess'; +import { registerSingleton, InstantiationType } from 'vs/platform/instantiation/common/extensions'; +import { NativeIssueService } from 'vs/workbench/contrib/issue/electron-sandbox/issueService'; import 'vs/workbench/contrib/issue/electron-sandbox/issueMainService'; -import 'vs/workbench/contrib/issue/electron-sandbox/issueService'; import 'vs/workbench/contrib/issue/browser/issueTroubleshoot'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { NativeIssueFormService } from 'vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService'; //#region Issue Contribution +registerSingleton(IWorkbenchIssueService, NativeIssueService, InstantiationType.Delayed); +registerSingleton(IIssueFormService, NativeIssueFormService, InstantiationType.Delayed); + class NativeIssueContribution extends BaseIssueContribution { constructor( @@ -57,6 +57,16 @@ class NativeIssueContribution extends BaseIssueContribution { }); }; + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + properties: { + 'issueReporter.experimental.auxWindow': { + type: 'boolean', + default: true, + description: 'Enable the new experimental issue reporter in electron.', + }, + } + }); + this._register(configurationService.onDidChangeConfiguration(e => { if (!configurationService.getValue('extensions.experimental.issueQuickAccess') && disposable) { disposable.dispose(); @@ -87,87 +97,10 @@ class ReportPerformanceIssueUsingReporterAction extends Action2 { } override async run(accessor: ServicesAccessor): Promise { - const issueService = accessor.get(IWorkbenchIssueService); + const issueService = accessor.get(IWorkbenchIssueService); // later can just get IIssueFormService return issueService.openReporter({ issueType: IssueType.PerformanceIssue }); } } -//#endregion - -//#region Commands - -class OpenProcessExplorer extends Action2 { - - static readonly ID = 'workbench.action.openProcessExplorer'; - - constructor() { - super({ - id: OpenProcessExplorer.ID, - title: localize2('openProcessExplorer', 'Open Process Explorer'), - category: Categories.Developer, - f1: true - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const issueService = accessor.get(IWorkbenchIssueService); - - return issueService.openProcessExplorer(); - } -} -registerAction2(OpenProcessExplorer); -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '5_tools', - command: { - id: OpenProcessExplorer.ID, - title: localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer") - }, - order: 2 -}); - -class StopTracing extends Action2 { - - static readonly ID = 'workbench.action.stopTracing'; - - constructor() { - super({ - id: StopTracing.ID, - title: localize2('stopTracing', 'Stop Tracing'), - category: Categories.Developer, - f1: true - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const issueService = accessor.get(IIssueMainService); - const environmentService = accessor.get(INativeEnvironmentService); - const dialogService = accessor.get(IDialogService); - const nativeHostService = accessor.get(INativeHostService); - const progressService = accessor.get(IProgressService); - - if (!environmentService.args.trace) { - const { confirmed } = await dialogService.confirm({ - message: localize('stopTracing.message', "Tracing requires to launch with a '--trace' argument"), - primaryButton: localize({ key: 'stopTracing.button', comment: ['&& denotes a mnemonic'] }, "&&Relaunch and Enable Tracing"), - }); - - if (confirmed) { - return nativeHostService.relaunch({ addArgs: ['--trace'] }); - } - } - - await progressService.withProgress({ - location: ProgressLocation.Dialog, - title: localize('stopTracing.title', "Creating trace file..."), - cancellable: false, - detail: localize('stopTracing.detail', "This can take up to one minute to complete.") - }, () => issueService.stopTracing()); - } -} -registerAction2(StopTracing); - -CommandsRegistry.registerCommand('_issues.getSystemStatus', (accessor) => { - return accessor.get(IIssueMainService).getSystemStatus(); -}); -//#endregion +// #endregion diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueMainService.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueMainService.ts index a3cb2847..b347982d 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueMainService.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueMainService.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; -import { IIssueMainService } from 'vs/platform/issue/common/issue'; +import { IProcessMainService, IIssueMainService } from 'vs/platform/issue/common/issue'; registerMainProcessRemoteService(IIssueMainService, 'issue'); +registerMainProcessRemoteService(IProcessMainService, 'process'); + diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.esm.html b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.esm.html new file mode 100644 index 00000000..f14661a2 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.esm.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.html b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.html index 455e8236..9d853e35 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.html +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter-dev.html @@ -39,8 +39,7 @@ - - + diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.esm.html b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.esm.html new file mode 100644 index 00000000..2f87d248 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.esm.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html index c6290004..8d0bcc6c 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html @@ -39,5 +39,5 @@ - + diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.js b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.js index cad5ddba..15a7f2a2 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.js +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporter.js @@ -4,8 +4,13 @@ *--------------------------------------------------------------------------------------------*/ //@ts-check +'use strict'; + (function () { - 'use strict'; + + /** + * @import { ISandboxConfiguration } from '../../../../base/parts/sandbox/common/sandboxTypes' + */ const bootstrapWindow = bootstrapWindowLib(); @@ -24,12 +29,10 @@ ); /** - * @typedef {import('../../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration - * * @returns {{ * load: ( * modules: string[], - * resultCallback: (result, configuration: ISandboxConfiguration) => unknown, + * resultCallback: (result: any, configuration: ISandboxConfiguration) => unknown, * options?: { * configureDeveloperSettings?: (config: ISandboxConfiguration) => { * forceEnableDeveloperKeybindings?: boolean, diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterMain.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterMain.ts index 05f002de..84e28306 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterMain.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterMain.ts @@ -5,8 +5,8 @@ import { safeInnerHtml } from 'vs/base/browser/dom'; import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded +import { mainWindow } from 'vs/base/browser/window'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import BaseHtml from 'vs/workbench/contrib/issue/browser/issueReporterPage'; import 'vs/css!./media/issueReporter'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; @@ -15,13 +15,14 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; -import { IIssueMainService, IssueReporterWindowConfiguration } from 'vs/platform/issue/common/issue'; import { INativeHostService } from 'vs/platform/native/common/native'; import { NativeHostService } from 'vs/platform/native/common/nativeHostService'; -import { IssueReporter2 } from 'vs/workbench/contrib/issue/electron-sandbox/issueReporterService2'; -import { mainWindow } from 'vs/base/browser/window'; +import BaseHtml from 'vs/workbench/contrib/issue/browser/issueReporterPage'; +import { IProcessMainService, IIssueMainService, OldIssueReporterWindowConfiguration } from 'vs/platform/issue/common/issue'; +import { IssueReporter } from 'vs/workbench/contrib/issue/electron-sandbox/issueReporterService'; + -export function startup(configuration: IssueReporterWindowConfiguration) { +export function startup(configuration: OldIssueReporterWindowConfiguration) { const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac'; mainWindow.document.body.classList.add(platformClass); // used by our fonts @@ -29,7 +30,7 @@ export function startup(configuration: IssueReporterWindowConfiguration) { const instantiationService = initServices(configuration.windowId); - const issueReporter = instantiationService.createInstance(IssueReporter2, configuration); + const issueReporter = instantiationService.createInstance(IssueReporter, configuration); issueReporter.render(); mainWindow.document.body.style.display = 'block'; issueReporter.setInitialFocus(); @@ -50,3 +51,4 @@ function initServices(windowId: number) { } registerMainProcessRemoteService(IIssueMainService, 'issue'); +registerMainProcessRemoteService(IProcessMainService, 'process'); diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts index 961ba329..ae4f7d63 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService.ts @@ -16,14 +16,14 @@ import { isLinuxSnap, isMacintosh } from 'vs/base/common/platform'; import { escape } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; -import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/workbench/contrib/issue/browser/issueReporterModel'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; -import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IssueReporterWindowConfiguration, IssueType } from 'vs/platform/issue/common/issue'; -import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; +import { IIssueMainService, IProcessMainService, OldIssueReporterData, OldIssueReporterExtensionData, OldIssueReporterStyles, OldIssueReporterWindowConfiguration, OldIssueType } from 'vs/platform/issue/common/issue'; import { INativeHostService } from 'vs/platform/native/common/native'; import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/window/electron-sandbox/window'; +import { IssueReporterData, IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/workbench/contrib/issue/browser/issueReporterModel'; +import { normalizeGitHubUrl } from 'vs/workbench/contrib/issue/common/issueReporterUtil'; // GitHub has let us know that we could up our limit here to 8k. We chose 7500 to play it safe. // ref https://github.com/microsoft/vscode/issues/159191 @@ -57,15 +57,16 @@ export class IssueReporter extends Disposable { private nonGitHubIssueUrl = false; constructor( - private readonly configuration: IssueReporterWindowConfiguration, + private readonly configuration: OldIssueReporterWindowConfiguration, @INativeHostService private readonly nativeHostService: INativeHostService, - @IIssueMainService private readonly issueMainService: IIssueMainService + @IIssueMainService private readonly issueMainService: IIssueMainService, + @IProcessMainService private readonly processMainService: IProcessMainService ) { super(); const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id.toLocaleLowerCase() === configuration.data.extensionId?.toLocaleLowerCase()) : undefined; this.issueReporterModel = new IssueReporterModel({ ...configuration.data, - issueType: configuration.data.issueType || IssueType.Bug, + issueType: configuration.data.issueType || OldIssueType.Bug, versionInfo: { vscodeVersion: `${configuration.product.nameShort} ${!!configuration.product.darwinUniversalAssetId ? `${configuration.product.version} (Universal)` : configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isLinuxSnap ? ' snap' : ''}` @@ -107,15 +108,15 @@ export class IssueReporter extends Disposable { } } - this.issueMainService.$getSystemInfo().then(info => { + this.processMainService.$getSystemInfo().then(info => { this.issueReporterModel.update({ systemInfo: info }); this.receivedSystemInfo = true; this.updateSystemInfo(this.issueReporterModel.getData()); this.updatePreviewButtonState(); }); - if (configuration.data.issueType === IssueType.PerformanceIssue) { - this.issueMainService.$getPerformanceInfo().then(info => { + if (configuration.data.issueType === OldIssueType.PerformanceIssue) { + this.processMainService.$getPerformanceInfo().then(info => { this.updatePerformanceInfo(info as Partial); }); } @@ -168,7 +169,7 @@ export class IssueReporter extends Disposable { } // TODO @justschen: After migration to Aux Window, switch to dedicated css. - private applyStyles(styles: IssueReporterStyles) { + private applyStyles(styles: OldIssueReporterStyles) { const styleTag = document.createElement('style'); const content: string[] = []; @@ -244,7 +245,7 @@ export class IssueReporter extends Disposable { mainWindow.document.body.style.color = styles.color || ''; } - private handleExtensionData(extensions: IssueReporterExtensionData[]) { + private handleExtensionData(extensions: OldIssueReporterExtensionData[]) { const installedExtensions = extensions.filter(x => !x.isBuiltin); const { nonThemes, themes } = groupBy(installedExtensions, ext => { return ext.isTheme ? 'themes' : 'nonThemes'; @@ -260,7 +261,7 @@ export class IssueReporter extends Disposable { this.updateExtensionSelector(installedExtensions); } - private async updateIssueReporterUri(extension: IssueReporterExtensionData): Promise { + private async updateIssueReporterUri(extension: OldIssueReporterExtensionData): Promise { try { if (extension.uri) { const uri = URI.revive(extension.uri); @@ -271,7 +272,7 @@ export class IssueReporter extends Disposable { } } - private async sendReporterMenu(extension: IssueReporterExtensionData): Promise { + private async sendReporterMenu(extension: OldIssueReporterExtensionData): Promise { try { const data = await this.issueMainService.$sendReporterMenu(extension.id, extension.name); return data; @@ -285,8 +286,8 @@ export class IssueReporter extends Disposable { this.addEventListener('issue-type', 'change', (event: Event) => { const issueType = parseInt((event.target).value); this.issueReporterModel.update({ issueType: issueType }); - if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) { - this.issueMainService.$getPerformanceInfo().then(info => { + if (issueType === OldIssueType.PerformanceIssue && !this.receivedPerformanceInfo) { + this.processMainService.$getPerformanceInfo().then(info => { this.updatePerformanceInfo(info as Partial); }); } @@ -525,15 +526,15 @@ export class IssueReporter extends Disposable { return false; } - if (issueType === IssueType.Bug && this.receivedSystemInfo) { + if (issueType === OldIssueType.Bug && this.receivedSystemInfo) { return true; } - if (issueType === IssueType.PerformanceIssue && this.receivedSystemInfo && this.receivedPerformanceInfo) { + if (issueType === OldIssueType.PerformanceIssue && this.receivedSystemInfo && this.receivedPerformanceInfo) { return true; } - if (issueType === IssueType.FeatureRequest) { + if (issueType === OldIssueType.FeatureRequest) { return true; } @@ -724,14 +725,14 @@ export class IssueReporter extends Disposable { } private setUpTypes(): void { - const makeOption = (issueType: IssueType, description: string) => $('option', { 'value': issueType.valueOf() }, escape(description)); + const makeOption = (issueType: OldIssueType, description: string) => $('option', { 'value': issueType.valueOf() }, escape(description)); const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement; const { issueType } = this.issueReporterModel.getData(); reset(typeSelect, - makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), - makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), - makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue (freeze, slow, crash)")) + makeOption(OldIssueType.Bug, localize('bugReporter', "Bug Report")), + makeOption(OldIssueType.FeatureRequest, localize('featureRequest', "Feature Request")), + makeOption(OldIssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue (freeze, slow, crash)")) ); typeSelect.value = issueType.toString(); @@ -772,7 +773,7 @@ export class IssueReporter extends Disposable { sourceSelect.append(this.makeOption(IssueSource.Marketplace, localize('marketplace', "Extensions Marketplace"), false)); } - if (issueType !== IssueType.FeatureRequest) { + if (issueType !== OldIssueType.FeatureRequest) { sourceSelect.append(this.makeOption(IssueSource.Unknown, localize('unknown', "Don't know"), false)); } @@ -851,7 +852,7 @@ export class IssueReporter extends Disposable { }, 100); } - if (issueType === IssueType.Bug) { + if (issueType === OldIssueType.Bug) { if (!fileOnMarketplace) { show(blockContainer); show(systemBlock); @@ -863,7 +864,7 @@ export class IssueReporter extends Disposable { reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce") + ' ', $('span.required-input', undefined, '*')); reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); - } else if (issueType === IssueType.PerformanceIssue) { + } else if (issueType === OldIssueType.PerformanceIssue) { if (!fileOnMarketplace) { show(blockContainer); show(systemBlock); @@ -880,7 +881,7 @@ export class IssueReporter extends Disposable { reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce") + ' ', $('span.required-input', undefined, '*')); reset(descriptionSubtitle, localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); - } else if (issueType === IssueType.FeatureRequest) { + } else if (issueType === OldIssueType.FeatureRequest) { reset(descriptionTitle, localize('description', "Description") + ' ', $('span.required-input', undefined, '*')); reset(descriptionSubtitle, localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); } @@ -1008,9 +1009,6 @@ export class IssueReporter extends Disposable { } const gitHubDetails = this.parseGitHubUrl(issueUrl); - if (this.configuration.data.githubAccessToken && gitHubDetails) { - return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); - } const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value, issueUrl); let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; @@ -1022,6 +1020,8 @@ export class IssueReporter extends Disposable { console.error('Writing to clipboard failed'); return false; } + } else if (this.configuration.data.githubAccessToken && gitHubDetails) { + return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); } await this.nativeHostService.openExternal(url); @@ -1171,7 +1171,7 @@ export class IssueReporter extends Disposable { } } - private updateExtensionSelector(extensions: IssueReporterExtensionData[]): void { + private updateExtensionSelector(extensions: OldIssueReporterExtensionData[]): void { interface IOption { name: string; id: string; @@ -1199,7 +1199,7 @@ export class IssueReporter extends Disposable { return 0; }); - const makeOption = (extension: IOption, selectedExtension?: IssueReporterExtensionData): HTMLOptionElement => { + const makeOption = (extension: IOption, selectedExtension?: OldIssueReporterExtensionData): HTMLOptionElement => { const selected = selectedExtension && extension.id === selectedExtension.id; return $('option', { 'value': extension.id, @@ -1278,7 +1278,7 @@ export class IssueReporter extends Disposable { this.configuration.data.uri = undefined; } - private async updateExtensionStatus(extension: IssueReporterExtensionData) { + private async updateExtensionStatus(extension: OldIssueReporterExtensionData) { this.issueReporterModel.update({ selectedExtension: extension }); // uses this.configuuration.data to ensure that data is coming from `openReporter` command. @@ -1356,7 +1356,7 @@ export class IssueReporter extends Disposable { const showLoading = this.getElementById('ext-loading')!; show(showLoading); while (showLoading.firstChild) { - showLoading.removeChild(showLoading.firstChild); + showLoading.firstChild.remove(); } showLoading.append(element); @@ -1377,7 +1377,7 @@ export class IssueReporter extends Disposable { const hideLoading = this.getElementById('ext-loading')!; hide(hideLoading); if (hideLoading.firstChild) { - hideLoading.removeChild(element); + element.remove(); } this.renderBlocks(); } @@ -1415,7 +1415,7 @@ export class IssueReporter extends Disposable { mainWindow.document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo; } - private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void { + private updateExtensionTable(extensions: OldIssueReporterExtensionData[], numThemeExtensions: number): void { const target = mainWindow.document.querySelector('.block-extensions .block-info'); if (target) { if (this.configuration.disableExtensions) { @@ -1451,7 +1451,7 @@ export class IssueReporter extends Disposable { } } - private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): HTMLTableElement { + private getExtensionTableHtml(extensions: OldIssueReporterExtensionData[]): HTMLTableElement { return $('table', undefined, $('tr', undefined, $('th', undefined, 'Extension'), diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService2.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService2.ts index 6f6bdd1c..49110c65 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService2.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueReporterService2.ts @@ -2,21 +2,19 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, isHTMLInputElement, isHTMLTextAreaElement, reset, windowOpenNoOpener } from 'vs/base/browser/dom'; -import { mainWindow } from 'vs/base/browser/window'; -import { Codicon } from 'vs/base/common/codicons'; -import { groupBy } from 'vs/base/common/collections'; +import { $, reset } from 'vs/base/browser/dom'; import { CancellationError } from 'vs/base/common/errors'; -import { isMacintosh } from 'vs/base/common/platform'; -import { ThemeIcon } from 'vs/base/common/themables'; +import { IProductConfiguration } from 'vs/base/common/product'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; -import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterWindowConfiguration, IssueType } from 'vs/platform/issue/common/issue'; +import { IProcessMainService } from 'vs/platform/issue/common/issue'; import { INativeHostService } from 'vs/platform/native/common/native'; -import { applyZoom, zoomIn, zoomOut } from 'vs/platform/window/electron-sandbox/window'; -import { BaseIssueReporterService, hide, show } from 'vs/workbench/contrib/issue/browser/issue'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; +import { BaseIssueReporterService } from 'vs/workbench/contrib/issue/browser/baseIssueReporterService'; import { IssueReporterData as IssueReporterModelData } from 'vs/workbench/contrib/issue/browser/issueReporterModel'; +import { IIssueFormService, IssueReporterData, IssueType } from 'vs/workbench/contrib/issue/common/issue'; // GitHub has let us know that we could up our limit here to 8k. We chose 7500 to play it safe. // ref https://github.com/microsoft/vscode/issues/159191 @@ -24,134 +22,66 @@ const MAX_URL_LENGTH = 7500; export class IssueReporter2 extends BaseIssueReporterService { + private readonly processMainService: IProcessMainService; constructor( - private readonly configuration: IssueReporterWindowConfiguration, + disableExtensions: boolean, + data: IssueReporterData, + os: { + type: string; + arch: string; + release: string; + }, + product: IProductConfiguration, + window: Window, @INativeHostService private readonly nativeHostService: INativeHostService, - @IIssueMainService issueMainService: IIssueMainService + @IIssueFormService issueFormService: IIssueFormService, + @IProcessMainService processMainService: IProcessMainService, + @IThemeService themeService: IThemeService ) { - super(configuration.disableExtensions, configuration.data, configuration.os, configuration.product, mainWindow, false, issueMainService); - - this.issueMainService.$getSystemInfo().then(info => { + super(disableExtensions, data, os, product, window, false, issueFormService, themeService); + this.processMainService = processMainService; + this.processMainService.$getSystemInfo().then(info => { this.issueReporterModel.update({ systemInfo: info }); this.receivedSystemInfo = true; this.updateSystemInfo(this.issueReporterModel.getData()); this.updatePreviewButtonState(); }); - if (configuration.data.issueType === IssueType.PerformanceIssue) { - this.issueMainService.$getPerformanceInfo().then(info => { + if (this.data.issueType === IssueType.PerformanceIssue) { + this.processMainService.$getPerformanceInfo().then(info => { this.updatePerformanceInfo(info as Partial); }); } this.setEventHandlers(); - applyZoom(configuration.data.zoomLevel, mainWindow); - this.handleExtensionData(configuration.data.enabledExtensions); - this.updateExperimentsInfo(configuration.data.experiments); - this.updateRestrictedMode(configuration.data.restrictedMode); - this.updateUnsupportedMode(configuration.data.isUnsupported); - } - - private handleExtensionData(extensions: IssueReporterExtensionData[]) { - const installedExtensions = extensions.filter(x => !x.isBuiltin); - const { nonThemes, themes } = groupBy(installedExtensions, ext => { - return ext.isTheme ? 'themes' : 'nonThemes'; - }); - - const numberOfThemeExtesions = themes && themes.length; - this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); - this.updateExtensionTable(nonThemes, numberOfThemeExtesions); - if (this.disableExtensions || installedExtensions.length === 0) { - (this.getElementById('disableExtensions')).disabled = true; - } - - this.updateExtensionSelector(installedExtensions); - } - - private async sendReporterMenu(extension: IssueReporterExtensionData): Promise { - try { - const data = await this.issueMainService.$sendReporterMenu(extension.id, extension.name); - return data; - } catch (e) { - console.error(e); - return undefined; - } + applyZoom(this.data.zoomLevel, this.window); + this.updateExperimentsInfo(this.data.experiments); + this.updateRestrictedMode(this.data.restrictedMode); + this.updateUnsupportedMode(this.data.isUnsupported); } public override setEventHandlers(): void { super.setEventHandlers(); - // Keep all event listerns involving window and issue creation - this.previewButton.onDidClick(async () => { - this.delayedSubmit.trigger(async () => { - this.createIssue(); - }); - }); - - this.addEventListener('disableExtensions', 'click', () => { - this.issueMainService.$reloadWithExtensionsDisabled(); - }); - - this.addEventListener('extensionBugsLink', 'click', (e: Event) => { - const url = (e.target).innerText; - windowOpenNoOpener(url); - }); - - this.addEventListener('disableExtensions', 'keydown', (e: Event) => { - e.stopPropagation(); - if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) { - this.issueMainService.$reloadWithExtensionsDisabled(); - } - }); - - - // THIS IS THE MAIN IMPORTANT PART - mainWindow.document.onkeydown = async (e: KeyboardEvent) => { - const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; - // Cmd/Ctrl+Enter previews issue and closes window - if (cmdOrCtrlKey && e.key === 'Enter') { - this.delayedSubmit.trigger(async () => { - if (await this.createIssue()) { - this.close(); - } + this.addEventListener('issue-type', 'change', (event: Event) => { + const issueType = parseInt((event.target).value); + this.issueReporterModel.update({ issueType: issueType }); + if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) { + this.processMainService.$getPerformanceInfo().then(info => { + this.updatePerformanceInfo(info as Partial); }); } - // Cmd/Ctrl + w closes issue window - if (cmdOrCtrlKey && e.key === 'w') { - e.stopPropagation(); - e.preventDefault(); - - const issueTitle = (this.getElementById('issue-title'))!.value; - const { issueDescription } = this.issueReporterModel.getData(); - if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) { - // fire and forget - this.issueMainService.$showConfirmCloseDialog(); - } else { - this.close(); - } - } - - // Cmd/Ctrl + zooms in - if (cmdOrCtrlKey && (e.key === '+' || e.key === '=')) { - zoomIn(mainWindow); - } - - // Cmd/Ctrl - zooms out - if (cmdOrCtrlKey && e.key === '-') { - zoomOut(mainWindow); + // Resets placeholder + const descriptionTextArea = this.getElementById('issue-title'); + if (descriptionTextArea) { + descriptionTextArea.placeholder = localize('undefinedPlaceholder', "Please enter a title"); } - // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac - // Manually perform the selection - if (isMacintosh) { - if (cmdOrCtrlKey && e.key === 'a' && e.target) { - if (isHTMLInputElement(e.target) || isHTMLTextAreaElement(e.target)) { - (e.target).select(); - } - } - } - }; + this.updatePreviewButtonState(); + this.setSourceOptions(); + this.render(); + }); } public override async submitToGitHub(issueTitle: string, issueBody: string, gitHubDetails: { owner: string; repositoryName: string }): Promise { @@ -195,7 +125,7 @@ export class IssueReporter2 extends BaseIssueReporterService { if (!this.validateInputs()) { // If inputs are invalid, set focus to the first one and add listeners on them // to detect further changes - const invalidInput = mainWindow.document.getElementsByClassName('invalid-input'); + const invalidInput = this.window.document.getElementsByClassName('invalid-input'); if (invalidInput.length) { (invalidInput[0]).focus(); } @@ -215,6 +145,7 @@ export class IssueReporter2 extends BaseIssueReporterService { if (this.issueReporterModel.fileOnExtension()) { this.addEventListener('extension-selector', 'change', _ => { this.validateInput('extension-selector'); + this.validateInput('description'); }); } @@ -238,9 +169,6 @@ export class IssueReporter2 extends BaseIssueReporterService { } const gitHubDetails = this.parseGitHubUrl(issueUrl); - if (this.data.githubAccessToken && gitHubDetails) { - return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); - } const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value, issueUrl); let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; @@ -252,6 +180,8 @@ export class IssueReporter2 extends BaseIssueReporterService { console.error('Writing to clipboard failed'); return false; } + } else if (this.data.githubAccessToken && gitHubDetails) { + return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); } await this.nativeHostService.openExternal(url); @@ -259,7 +189,7 @@ export class IssueReporter2 extends BaseIssueReporterService { } public override async writeToClipboard(baseUrl: string, issueBody: string): Promise { - const shouldWrite = await this.issueMainService.$showClipboardDialog(); + const shouldWrite = await this.issueFormService.showClipboardDialog(); if (!shouldWrite) { throw new CancellationError(); } @@ -270,7 +200,7 @@ export class IssueReporter2 extends BaseIssueReporterService { } private updateSystemInfo(state: IssueReporterModelData) { - const target = mainWindow.document.querySelector('.block-system .block-info'); + const target = this.window.document.querySelector('.block-system .block-info'); if (target) { const systemInfo = state.systemInfo!; @@ -349,147 +279,6 @@ export class IssueReporter2 extends BaseIssueReporterService { } } - public updateExtensionSelector(extensions: IssueReporterExtensionData[]): void { - interface IOption { - name: string; - id: string; - } - - const extensionOptions: IOption[] = extensions.map(extension => { - return { - name: extension.displayName || extension.name || '', - id: extension.id - }; - }); - - // Sort extensions by name - extensionOptions.sort((a, b) => { - const aName = a.name.toLowerCase(); - const bName = b.name.toLowerCase(); - if (aName > bName) { - return 1; - } - - if (aName < bName) { - return -1; - } - - return 0; - }); - - const makeOption = (extension: IOption, selectedExtension?: IssueReporterExtensionData): HTMLOptionElement => { - const selected = selectedExtension && extension.id === selectedExtension.id; - return $('option', { - 'value': extension.id, - 'selected': selected || '' - }, extension.name); - }; - - const extensionsSelector = this.getElementById('extension-selector'); - if (extensionsSelector) { - const { selectedExtension } = this.issueReporterModel.getData(); - reset(extensionsSelector, this.makeOption('', localize('selectExtension', "Select extension"), true), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); - - if (!selectedExtension) { - extensionsSelector.selectedIndex = 0; - } - - this.addEventListener('extension-selector', 'change', async (e: Event) => { - this.clearExtensionData(); - const selectedExtensionId = (e.target).value; - this.selectedExtension = selectedExtensionId; - const extensions = this.issueReporterModel.getData().allExtensions; - const matches = extensions.filter(extension => extension.id === selectedExtensionId); - if (matches.length) { - this.issueReporterModel.update({ selectedExtension: matches[0] }); - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - if (selectedExtension) { - const iconElement = document.createElement('span'); - iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); - this.setLoading(iconElement); - const openReporterData = await this.sendReporterMenu(selectedExtension); - if (openReporterData) { - if (this.selectedExtension === selectedExtensionId) { - this.removeLoading(iconElement, true); - this.configuration.data = openReporterData; - this.data = openReporterData; - } else if (this.selectedExtension !== selectedExtensionId) { - } - } - else { - if (!this.loadingExtensionData) { - iconElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); - } - this.removeLoading(iconElement); - // if not using command, should have no configuration data in fields we care about and check later. - this.clearExtensionData(); - - // case when previous extension was opened from normal openIssueReporter command - selectedExtension.data = undefined; - selectedExtension.uri = undefined; - } - if (this.selectedExtension === selectedExtensionId) { - // repopulates the fields with the new data given the selected extension. - this.updateExtensionStatus(matches[0]); - this.openReporter = false; - } - } else { - this.issueReporterModel.update({ selectedExtension: undefined }); - this.clearSearchResults(); - this.clearExtensionData(); - this.validateSelectedExtension(); - this.updateExtensionStatus(matches[0]); - } - } - }); - } - - this.addEventListener('problem-source', 'change', (_) => { - this.validateSelectedExtension(); - }); - } - - public override setLoading(element: HTMLElement) { - // Show loading - this.openReporter = true; - this.loadingExtensionData = true; - this.updatePreviewButtonState(); - - const extensionDataCaption = this.getElementById('extension-id')!; - hide(extensionDataCaption); - - const extensionDataCaption2 = Array.from(mainWindow.document.querySelectorAll('.ext-parens')); - extensionDataCaption2.forEach(extensionDataCaption2 => hide(extensionDataCaption2)); - - const showLoading = this.getElementById('ext-loading')!; - show(showLoading); - while (showLoading.firstChild) { - showLoading.removeChild(showLoading.firstChild); - } - showLoading.append(element); - - this.renderBlocks(); - } - - public override removeLoading(element: HTMLElement, fromReporter: boolean = false) { - this.openReporter = fromReporter; - this.loadingExtensionData = false; - this.updatePreviewButtonState(); - - const extensionDataCaption = this.getElementById('extension-id')!; - show(extensionDataCaption); - - const extensionDataCaption2 = Array.from(mainWindow.document.querySelectorAll('.ext-parens')); - extensionDataCaption2.forEach(extensionDataCaption2 => show(extensionDataCaption2)); - - const hideLoading = this.getElementById('ext-loading')!; - hide(hideLoading); - if (hideLoading.firstChild) { - hideLoading.removeChild(element); - } - this.renderBlocks(); - } - private updateRestrictedMode(restrictedMode: boolean) { this.issueReporterModel.update({ restrictedMode }); } @@ -500,7 +289,7 @@ export class IssueReporter2 extends BaseIssueReporterService { private updateExperimentsInfo(experimentInfo: string | undefined) { this.issueReporterModel.update({ experimentInfo }); - const target = mainWindow.document.querySelector('.block-experiments .block-info'); + const target = this.window.document.querySelector('.block-experiments .block-info'); if (target) { target.textContent = experimentInfo ? experimentInfo : localize('noCurrentExperiments', "No current experiments."); } diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts index 3b5c8d26..b60b6f54 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/issueService.ts @@ -4,26 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { getZoomLevel } from 'vs/base/browser/browser'; -import { platform } from 'vs/base/common/process'; +import { mainWindow } from 'vs/base/browser/window'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionIdentifier, ExtensionType, ExtensionIdentifierSet } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierSet, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { activeContrastBorder, buttonBackground, buttonForeground, buttonHoverBackground, editorBackground, editorForeground, foreground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, listActiveSelectionBackground, listActiveSelectionForeground, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IIssueMainService, OldIssueReporterData, OldIssueReporterExtensionData, OldIssueReporterStyles } from 'vs/platform/issue/common/issue'; +import { buttonBackground, buttonForeground, buttonHoverBackground, foreground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IIssueFormService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; -import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/common/issue'; -import { mainWindow } from 'vs/base/browser/window'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class NativeIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -31,26 +29,25 @@ export class NativeIssueService implements IWorkbenchIssueService { constructor( @IIssueMainService private readonly issueMainService: IIssueMainService, + @IIssueFormService private readonly issueFormService: IIssueFormService, @IThemeService private readonly themeService: IThemeService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, - @IProductService private readonly productService: IProductService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IIntegrityService private readonly integrityService: IIntegrityService, @IMenuService private readonly menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { ipcRenderer.on('vscode:triggerReporterMenu', async (event, arg) => { const extensionId = arg.extensionId; - // creates menu from contributed - const menu = this.menuService.createMenu(MenuId.IssueReporter, this.contextKeyService); + // gets menu from contributed + const actions = this.menuService.getMenuActions(MenuId.IssueReporter, this.contextKeyService, { renderShortTitle: true }).flatMap(entry => entry[1]); // render menu and dispose - const actions = menu.getActions({ renderShortTitle: true }).flatMap(entry => entry[1]); actions.forEach(async action => { try { if (action.item && 'source' in action.item && action.item.source?.id === extensionId) { @@ -66,12 +63,13 @@ export class NativeIssueService implements IWorkbenchIssueService { // send undefined to indicate no action was taken ipcRenderer.send(`vscode:triggerReporterMenuResponse:${extensionId}`, undefined); } - menu.dispose(); }); } async openReporter(dataOverrides: Partial = {}): Promise { const extensionData: IssueReporterExtensionData[] = []; + const oldExtensionData: OldIssueReporterExtensionData[] = []; + const oldDataOverrides = dataOverrides as Partial; try { const extensions = await this.extensionManagementService.getInstalled(); const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension) || (dataOverrides.extensionId && extension.identifier.id === dataOverrides.extensionId)); @@ -95,6 +93,26 @@ export class NativeIssueService implements IWorkbenchIssueService { extensionData: 'Extensions data loading', }; })); + oldExtensionData.push(...enabledExtensions.map((extension): OldIssueReporterExtensionData => { + const { manifest } = extension; + const manifestKeys = manifest.contributes ? Object.keys(manifest.contributes) : []; + const isTheme = !manifest.main && !manifest.browser && manifestKeys.length === 1 && manifestKeys[0] === 'themes'; + const isBuiltin = extension.type === ExtensionType.System; + return { + name: manifest.name, + publisher: manifest.publisher, + version: manifest.version, + repositoryUrl: manifest.repository && manifest.repository.url, + bugsUrl: manifest.bugs && manifest.bugs.url, + displayName: manifest.displayName, + id: extension.identifier.id, + data: dataOverrides.data, + uri: dataOverrides.uri, + isTheme, + isBuiltin, + extensionData: 'Extensions data loading', + }; + })); } catch (e) { extensionData.push({ name: 'Workbench Issue Service', @@ -108,6 +126,18 @@ export class NativeIssueService implements IWorkbenchIssueService { isTheme: false, isBuiltin: true }); + oldExtensionData.push({ + name: 'Workbench Issue Service', + publisher: 'Unknown', + version: '0.0.0', + repositoryUrl: undefined, + bugsUrl: undefined, + extensionData: 'Extensions data loading', + displayName: `Extensions not loaded: ${e}`, + id: 'workbench.issue', + isTheme: false, + isBuiltin: true + }); } const experiments = await this.experimentService.getCurrentExperiments(); @@ -139,6 +169,16 @@ export class NativeIssueService implements IWorkbenchIssueService { githubAccessToken }, dataOverrides); + const oldIssueReporterData: OldIssueReporterData = Object.assign({ + styles: oldGetIssueReporterStyles(theme), + zoomLevel: getZoomLevel(mainWindow), + enabledExtensions: oldExtensionData, + experiments: experiments?.join('\n'), + restrictedMode: !this.workspaceTrustManagementService.isWorkspaceTrusted(), + isUnsupported, + githubAccessToken + }, oldDataOverrides); + if (issueReporterData.extensionId) { const extensionExists = extensionData.some(extension => ExtensionIdentifier.equals(extension.id, issueReporterData.extensionId)); if (!extensionExists) { @@ -150,40 +190,40 @@ export class NativeIssueService implements IWorkbenchIssueService { ipcRenderer.send(`vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}`, issueReporterData); this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); } - return this.issueMainService.openReporter(issueReporterData); - } - openProcessExplorer(): Promise { - const theme = this.themeService.getColorTheme(); - const data: ProcessExplorerData = { - pid: this.environmentService.mainPid, - zoomLevel: getZoomLevel(mainWindow), - styles: { - backgroundColor: getColor(theme, editorBackground), - color: getColor(theme, editorForeground), - listHoverBackground: getColor(theme, listHoverBackground), - listHoverForeground: getColor(theme, listHoverForeground), - listFocusBackground: getColor(theme, listFocusBackground), - listFocusForeground: getColor(theme, listFocusForeground), - listFocusOutline: getColor(theme, listFocusOutline), - listActiveSelectionBackground: getColor(theme, listActiveSelectionBackground), - listActiveSelectionForeground: getColor(theme, listActiveSelectionForeground), - listHoverOutline: getColor(theme, activeContrastBorder), - scrollbarShadowColor: getColor(theme, scrollbarShadow), - scrollbarSliderActiveBackgroundColor: getColor(theme, scrollbarSliderActiveBackground), - scrollbarSliderBackgroundColor: getColor(theme, scrollbarSliderBackground), - scrollbarSliderHoverBackgroundColor: getColor(theme, scrollbarSliderHoverBackground), - }, - platform: platform, - applicationName: this.productService.applicationName - }; - return this.issueMainService.openProcessExplorer(data); - } + if (this.configurationService.getValue('issueReporter.experimental.auxWindow')) { + return this.issueFormService.openReporter(issueReporterData); + } + + return this.issueMainService.openReporter(oldIssueReporterData); + } } export function getIssueReporterStyles(theme: IColorTheme): IssueReporterStyles { + return { + backgroundColor: getColor(theme, SIDE_BAR_BACKGROUND), + color: getColor(theme, foreground), + textLinkColor: getColor(theme, textLinkForeground), + textLinkActiveForeground: getColor(theme, textLinkActiveForeground), + inputBackground: getColor(theme, inputBackground), + inputForeground: getColor(theme, inputForeground), + inputBorder: getColor(theme, inputBorder), + inputActiveBorder: getColor(theme, inputActiveOptionBorder), + inputErrorBorder: getColor(theme, inputValidationErrorBorder), + inputErrorBackground: getColor(theme, inputValidationErrorBackground), + inputErrorForeground: getColor(theme, inputValidationErrorForeground), + buttonBackground: getColor(theme, buttonBackground), + buttonForeground: getColor(theme, buttonForeground), + buttonHoverBackground: getColor(theme, buttonHoverBackground), + sliderActiveColor: getColor(theme, scrollbarSliderActiveBackground), + sliderBackgroundColor: getColor(theme, SIDE_BAR_BACKGROUND), + sliderHoverColor: getColor(theme, scrollbarSliderHoverBackground), + }; +} + +export function oldGetIssueReporterStyles(theme: IColorTheme): OldIssueReporterStyles { return { backgroundColor: getColor(theme, SIDE_BAR_BACKGROUND), color: getColor(theme, foreground), diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/media/issueReporter.css b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/media/issueReporter.css index 152d6c38..fa2cf8a1 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/media/issueReporter.css +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/media/issueReporter.css @@ -3,6 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* +* TODO: @justschen - remove this file once the new issue reporter is enabled by default. +* Split between this and a `newIssueReporter` because of specificy from new issue reporter monaco-workbench stylesheets. +*/ + /** * Table */ diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/media/newIssueReporter.css b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/media/newIssueReporter.css new file mode 100644 index 00000000..00b0be10 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/media/newIssueReporter.css @@ -0,0 +1,470 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Table + */ + +.issue-reporter-body table { + width: 100%; + max-width: 100%; + background-color: transparent; + border-collapse: collapse; +} + +.issue-reporter-body th { + vertical-align: bottom; + border-bottom: 1px solid; + padding: 5px; + text-align: inherit; +} + +.issue-reporter-body td { + padding: 5px; + vertical-align: top; +} + +.issue-reporter-body tr td:first-child { + width: 30%; +} + +.issue-reporter-body label { + user-select: none; +} + +.issue-reporter-body .block-settingsSearchResults-details { + padding-bottom: .5rem; +} + +.issue-reporter-body .block-settingsSearchResults-details > div { + padding: .5rem .75rem; +} + +.issue-reporter-body .section { + margin-bottom: .5em; +} + +/** + * Forms + */ +.issue-reporter-body input[type="text"], +.issue-reporter-body textarea { + display: block; + width: 100%; + padding: .375rem .75rem; + font-size: 1rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: 1px solid #ced4da; +} + +.issue-reporter-body textarea { + overflow: auto; + resize: vertical; +} + +/** + * Button + */ + +.issue-reporter-body .monaco-text-button { + display: block; + width: auto; + padding: 4px 10px; + align-self: flex-end; + margin-bottom: 1em; + font-size: 13px; +} + +.issue-reporter-body select { + height: calc(2.25rem + 2px); + display: inline-block; + padding: 3px 3px; + font-size: 14px; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: none; +} + +.issue-reporter-body * { + box-sizing: border-box; +} + +.issue-reporter-body textarea, +.issue-reporter-body input, +.issue-reporter-body select { + font-family: inherit; +} + +.issue-reporter-body html { + color: #CCCCCC; + height: 100%; +} + +.issue-reporter-body .extension-caption .codicon-modifier-spin { + padding-bottom: 3px; + margin-left: 2px; +} + +/* Font Families (with CJK support) */ + +.issue-reporter-body .mac { + font-family: -apple-system, BlinkMacSystemFont, sans-serif; +} + +.issue-reporter-body .mac:lang(zh-Hans) { + font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; +} + +.issue-reporter-body .mac:lang(zh-Hant) { + font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; +} + +.issue-reporter-body .mac:lang(ja) { + font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; +} + +.issue-reporter-body .mac:lang(ko) { + font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; +} + +.issue-reporter-body .windows { + font-family: "Segoe WPC", "Segoe UI", sans-serif; +} + +.issue-reporter-body .windows:lang(zh-Hans) { + font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; +} + +.issue-reporter-body .windows:lang(zh-Hant) { + font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; +} + +.issue-reporter-body .windows:lang(ja) { + font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; +} + +.issue-reporter-body .windows:lang(ko) { + font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; +} + +/* Linux: add `system-ui` as first font and not `Ubuntu` to allow other distribution pick their standard OS font */ +.issue-reporter-body .linux { + font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; +} + +.issue-reporter-body .linux:lang(zh-Hans) { + font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; +} + +.issue-reporter-body .linux:lang(zh-Hant) { + font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; +} + +.issue-reporter-body .linux:lang(ja) { + font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; +} + +.issue-reporter-body .linux:lang(ko) { + font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; +} + +body.issue-reporter-body { + margin: 0 !important; + overflow-y: scroll !important; + height: 100% !important; +} + +.issue-reporter-body .hidden { + display: none; +} + +.issue-reporter-body .block { + font-size: 12px; +} + +.issue-reporter-body .block .block-info { + width: 100%; + font-size: 12px; + overflow: auto; + overflow-wrap: break-word; + margin: 5px; + padding: 10px; +} + +.issue-reporter-body #issue-reporter { + max-width: 85vw; + margin-left: auto; + margin-right: auto; + padding-top: 2em; + padding-bottom: 2em; + display: flex; + flex-direction: column; + min-height: 100%; + overflow: visible; +} + +.issue-reporter-body .description-section { + flex-grow: 1; + display: flex; + flex-direction: column; + flex-shrink: 0; +} + +.issue-reporter-body textarea { + flex-grow: 1; + min-height: 150px; +} + +.issue-reporter-body .block-info-text { + display: flex; + flex-grow: 1; +} + +.issue-reporter-body #github-submit-btn { + flex-shrink: 0; + margin-left: auto; + margin-top: 10px; + margin-bottom: 10px; +} + +.issue-reporter-body .two-col { + display: inline-block; + width: 49%; +} + +.issue-reporter-body #vscode-version { + width: 90%; +} + +.issue-reporter-body .input-group { + margin-bottom: 1em; +} + +.issue-reporter-body #extension-selection { + margin-top: 1em; +} + +.issue-reporter-body .issue-reporter select, +.issue-reporter-body .issue-reporter input, +.issue-reporter-body .issue-reporter textarea { + border: 1px solid transparent; + margin-top: 10px; +} + + +.issue-reporter-body #issue-reporter .validation-error { + font-size: 12px; + padding: 10px; + border-top: 0px !important; +} + +.issue-reporter-body #issue-reporter .system-info { + margin-bottom: 10px; +} + + +.issue-reporter-body input[type="checkbox"] { + width: auto; + display: inline-block; + margin-top: 0; + vertical-align: middle; + cursor: pointer; +} + +.issue-reporter-body input:disabled { + opacity: 0.6; +} + +.issue-reporter-body .list-title { + margin-top: 1em; + margin-left: 1em; +} + +.issue-reporter-body .instructions { + font-size: 12px; + margin-top: .5em; +} + +.issue-reporter-body a, +.issue-reporter-body .workbenchCommand { + cursor: pointer; + border: 1px solid transparent; +} + +.issue-reporter-body .workbenchCommand:disabled { + color: #868e96; + cursor: default +} + +.issue-reporter-body .block-extensions .block-info { + margin-bottom: 1.5em; +} + +/* Default styles, overwritten if a theme is provided */ +.issue-reporter-body input, +.issue-reporter-body select, +.issue-reporter-body textarea { + background-color: #3c3c3c; + border: none; + color: #cccccc; +} + +.issue-reporter-body a { + color: #CCCCCC; + text-decoration: none; +} + +.issue-reporter-body .showInfo, +.issue-reporter-body .input-group a { + color: var(--vscode-textLink-foreground); +} + +.issue-reporter-body .section .input-group .validation-error { + margin-left: 100px; +} + +.issue-reporter-body .section .inline-form-control, +.issue-reporter-body .section .inline-label { + display: inline-block; + font-size: initial; +} + +.issue-reporter-body .section .inline-label { + width: 95px; +} + +.issue-reporter-body .issue-reporter .inline-label, +.issue-reporter-body .issue-reporter #issue-description-label { + font-size: initial; + cursor: default; +} + +.issue-reporter-body .monaco-workbench .issue-reporter label { + cursor: default; +} + +.issue-reporter-body .section .inline-form-control, +.issue-reporter-body .section .input-group .validation-error { + width: calc(100% - 100px); +} + +.issue-reporter-body #issue-type, +.issue-reporter-body #issue-source, +.issue-reporter-body #extension-selector { + cursor: pointer; + appearance: auto; + border: none; + border-right: 6px solid transparent; + padding-left: 10px; +} + +.issue-reporter-body #similar-issues { + margin-left: 15%; + display: block; +} + +.issue-reporter-body #problem-source-help-text { + margin-left: calc(15% + 1em); +} + +@media (max-width: 950px) { + .issue-reporter-body .section .inline-label { + width: 15%; + } + + .issue-reporter-body #problem-source-help-text { + margin-left: calc(15% + 1em); + } + + .issue-reporter-body .section .inline-form-control, + .issue-reporter-body .section .input-group .validation-error { + width: calc(85% - 5px); + } + + .issue-reporter-body .section .input-group .validation-error { + margin-left: calc(15% + 4px); + } +} + +@media (max-width: 620px) { + .issue-reporter-body .section .inline-label { + display: none !important; + } + + .issue-reporter-body #problem-source-help-text { + margin-left: 1em; + } + + .issue-reporter-body .section .inline-form-control, + .issue-reporter-body .section .input-group .validation-error { + width: 100%; + } + + .issue-reporter-body #similar-issues, + .issue-reporter-body .section .input-group .validation-error { + margin-left: 0; + } +} + +.issue-reporter-body::-webkit-scrollbar { + width: 14px; +} + +.issue-reporter-body::-webkit-scrollbar-thumb { + min-height: 20px; +} + +.issue-reporter-body::-webkit-scrollbar-corner { + display: none; +} + +.issue-reporter-body .issues-container { + margin-left: 1.5em; + margin-top: .5em; + max-height: 92px; + overflow-y: auto; +} + +.issue-reporter-body .issues-container > .issue { + padding: 4px 0; + display: flex; +} + +.issue-reporter-body .issues-container > .issue > .issue-link { + width: calc(100% - 82px); + overflow: hidden; + padding-top: 3px; + white-space: nowrap; + text-overflow: ellipsis; +} + +.issue-reporter-body .issues-container > .issue > .issue-state .codicon { + width: 16px; +} + +.issue-reporter-body .issues-container > .issue > .issue-state { + display: flex; + width: 77px; + padding: 3px 6px; + margin-right: 5px; + color: #CCCCCC; + background-color: #3c3c3c; + border-radius: .25rem; +} + +.issue-reporter-body .issues-container > .issue .label { + padding-top: 2px; + margin-left: 5px; + width: 44px; + text-overflow: ellipsis; + overflow: hidden; +} + +.issue-reporter-body .issues-container > .issue .issue-icon { + padding-top: 2px; +} diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts new file mode 100644 index 00000000..ee2de28e --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/nativeIssueFormService.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/newIssueReporter'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INativeHostService } from 'vs/platform/native/common/native'; +import product from 'vs/platform/product/common/product'; +import { IssueFormService } from 'vs/workbench/contrib/issue/browser/issueFormService'; +import { IIssueFormService, IssueReporterData } from 'vs/workbench/contrib/issue/common/issue'; +import { IssueReporter2 } from 'vs/workbench/contrib/issue/electron-sandbox/issueReporterService2'; +import { IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; + +export class NativeIssueFormService extends IssueFormService implements IIssueFormService { + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @IAuxiliaryWindowService auxiliaryWindowService: IAuxiliaryWindowService, + @ILogService logService: ILogService, + @IDialogService dialogService: IDialogService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IHostService hostService: IHostService, + @INativeHostService private readonly nativeHostService: INativeHostService, + @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,) { + super(instantiationService, auxiliaryWindowService, menuService, contextKeyService, logService, dialogService, hostService); + } + + // override to grab platform info + override async openReporter(data: IssueReporterData): Promise { + if (this.hasToReload(data)) { + return; + } + + const bounds = await this.nativeHostService.getActiveWindowPosition(); + if (!bounds) { + return; + } + + await this.openAuxIssueReporter(data, bounds); + + // Get platform information + const { arch, release, type } = await this.nativeHostService.getOSProperties(); + this.arch = arch; + this.release = release; + this.type = type; + + // create issue reporter and instantiate + if (this.issueReporterWindow) { + const issueReporter = this.instantiationService.createInstance(IssueReporter2, !!this.environmentService.disableExtensions, data, { type: this.type, arch: this.arch, release: this.release }, product, this.issueReporterWindow); + issueReporter.render(); + } + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts new file mode 100644 index 00000000..43fe8fb3 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/process.contribution.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize, localize2 } from 'vs/nls'; +import { MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { IWorkbenchProcessService } from 'vs/workbench/contrib/issue/common/issue'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { INativeHostService } from 'vs/platform/native/common/native'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProcessMainService } from 'vs/platform/issue/common/issue'; +import 'vs/workbench/contrib/issue/electron-sandbox/processService'; +import 'vs/workbench/contrib/issue/electron-sandbox/issueMainService'; + + +//#region Commands + +class OpenProcessExplorer extends Action2 { + + static readonly ID = 'workbench.action.openProcessExplorer'; + + constructor() { + super({ + id: OpenProcessExplorer.ID, + title: localize2('openProcessExplorer', 'Open Process Explorer'), + category: Categories.Developer, + f1: true + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const processService = accessor.get(IWorkbenchProcessService); + + return processService.openProcessExplorer(); + } +} +registerAction2(OpenProcessExplorer); +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '5_tools', + command: { + id: OpenProcessExplorer.ID, + title: localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer") + }, + order: 2 +}); + +class StopTracing extends Action2 { + + static readonly ID = 'workbench.action.stopTracing'; + + constructor() { + super({ + id: StopTracing.ID, + title: localize2('stopTracing', 'Stop Tracing'), + category: Categories.Developer, + f1: true + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const processService = accessor.get(IProcessMainService); + const environmentService = accessor.get(INativeEnvironmentService); + const dialogService = accessor.get(IDialogService); + const nativeHostService = accessor.get(INativeHostService); + const progressService = accessor.get(IProgressService); + + if (!environmentService.args.trace) { + const { confirmed } = await dialogService.confirm({ + message: localize('stopTracing.message', "Tracing requires to launch with a '--trace' argument"), + primaryButton: localize({ key: 'stopTracing.button', comment: ['&& denotes a mnemonic'] }, "&&Relaunch and Enable Tracing"), + }); + + if (confirmed) { + return nativeHostService.relaunch({ addArgs: ['--trace'] }); + } + } + + await progressService.withProgress({ + location: ProgressLocation.Dialog, + title: localize('stopTracing.title', "Creating trace file..."), + cancellable: false, + detail: localize('stopTracing.detail', "This can take up to one minute to complete.") + }, () => processService.stopTracing()); + } +} +registerAction2(StopTracing); + +CommandsRegistry.registerCommand('_issues.getSystemStatus', (accessor) => { + return accessor.get(IProcessMainService).getSystemStatus(); +}); +//#endregion diff --git a/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/processService.ts b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/processService.ts new file mode 100644 index 00000000..60ebd4f8 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/issue/electron-sandbox/processService.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getZoomLevel } from 'vs/base/browser/browser'; +import { platform } from 'vs/base/common/process'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IProcessMainService, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { activeContrastBorder, editorBackground, editorForeground, listActiveSelectionBackground, listActiveSelectionForeground, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { IWorkbenchProcessService } from 'vs/workbench/contrib/issue/common/issue'; +import { mainWindow } from 'vs/base/browser/window'; + +export class ProcessService implements IWorkbenchProcessService { + declare readonly _serviceBrand: undefined; + + constructor( + @IProcessMainService private readonly processMainService: IProcessMainService, + @IThemeService private readonly themeService: IThemeService, + @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, + @IProductService private readonly productService: IProductService, + ) { } + + openProcessExplorer(): Promise { + const theme = this.themeService.getColorTheme(); + const data: ProcessExplorerData = { + pid: this.environmentService.mainPid, + zoomLevel: getZoomLevel(mainWindow), + styles: { + backgroundColor: getColor(theme, editorBackground), + color: getColor(theme, editorForeground), + listHoverBackground: getColor(theme, listHoverBackground), + listHoverForeground: getColor(theme, listHoverForeground), + listFocusBackground: getColor(theme, listFocusBackground), + listFocusForeground: getColor(theme, listFocusForeground), + listFocusOutline: getColor(theme, listFocusOutline), + listActiveSelectionBackground: getColor(theme, listActiveSelectionBackground), + listActiveSelectionForeground: getColor(theme, listActiveSelectionForeground), + listHoverOutline: getColor(theme, activeContrastBorder), + scrollbarShadowColor: getColor(theme, scrollbarShadow), + scrollbarSliderActiveBackgroundColor: getColor(theme, scrollbarSliderActiveBackground), + scrollbarSliderBackgroundColor: getColor(theme, scrollbarSliderBackground), + scrollbarSliderHoverBackgroundColor: getColor(theme, scrollbarSliderHoverBackground), + }, + platform: platform, + applicationName: this.productService.applicationName + }; + return this.processMainService.openProcessExplorer(data); + } + + +} + +function getColor(theme: IColorTheme, key: string): string | undefined { + const color = theme.getColor(key); + return color ? color.toString() : undefined; +} + +registerSingleton(IWorkbenchProcessService, ProcessService, InstantiationType.Delayed); diff --git a/patched-vscode/src/vs/workbench/contrib/issue/issue/testReporterModel.test.ts b/patched-vscode/src/vs/workbench/contrib/issue/test/browser/testReporterModel.test.ts similarity index 97% rename from patched-vscode/src/vs/workbench/contrib/issue/issue/testReporterModel.test.ts rename to patched-vscode/src/vs/workbench/contrib/issue/test/browser/testReporterModel.test.ts index d86022de..ccc2ae55 100644 --- a/patched-vscode/src/vs/workbench/contrib/issue/issue/testReporterModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/issue/test/browser/testReporterModel.test.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IssueReporterModel } from 'vs/workbench/contrib/issue/browser/issueReporterModel'; -import { IssueType } from 'vs/platform/issue/common/issue'; -import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; +import { IssueType } from 'vs/workbench/contrib/issue/common/issue'; +import { normalizeGitHubUrl } from 'vs/workbench/contrib/issue/common/issueReporterUtil'; suite('IssueReporter', () => { ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/patched-vscode/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/patched-vscode/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 3feb89c6..c4a3d7c4 100644 --- a/patched-vscode/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -28,12 +28,12 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { equals } from 'vs/base/common/arrays'; import { URI } from 'vs/base/common/uri'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IEditorGroupsService, IEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IHoverService, nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; +import { Event } from 'vs/base/common/event'; class LanguageStatusViewModel { @@ -65,22 +65,23 @@ class StoredCounter { class LanguageStatusContribution extends Disposable implements IWorkbenchContribution { constructor( - @IInstantiationService instantiationService: IInstantiationService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, ) { super(); - // --- main language status - const mainInstantiationService = this._register(instantiationService.createChild(new ServiceCollection( - [IEditorService, editorService.createScoped('main', this._store)] - ))); - this._register(mainInstantiationService.createInstance(LanguageStatus)); + for (const part of editorGroupService.parts) { + this.createLanguageStatus(part); + } + + this._register(editorGroupService.onDidCreateAuxiliaryEditorPart(part => this.createLanguageStatus(part))); + } + + private createLanguageStatus(part: IEditorPart): void { + const disposables = new DisposableStore(); + Event.once(part.onWillDispose)(() => disposables.dispose()); - // --- auxiliary language status - this._register(editorGroupService.onDidCreateAuxiliaryEditorPart(({ instantiationService, disposables }) => { - disposables.add(instantiationService.createInstance(LanguageStatus)); - })); + const scopedInstantiationService = this.editorGroupService.getScopedInstantiationService(part); + disposables.add(scopedInstantiationService.createInstance(LanguageStatus)); } } @@ -122,7 +123,7 @@ class LanguageStatus { this._update(); this._storeState(); } - }, this._disposables); + }, undefined, this._disposables); } diff --git a/patched-vscode/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css b/patched-vscode/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css index 4354ad02..25433bee 100644 --- a/patched-vscode/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css +++ b/patched-vscode/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css @@ -113,6 +113,7 @@ .monaco-workbench .hover-language-status > .element .right .monaco-link { margin: auto 0; white-space: nowrap; + text-decoration: var(--text-link-decoration); } .monaco-workbench .hover-language-status > .element .right .monaco-action-bar:not(:first-child) { diff --git a/patched-vscode/src/vs/workbench/contrib/list/browser/list.contribution.ts b/patched-vscode/src/vs/workbench/contrib/list/browser/list.contribution.ts index dffda536..e8936585 100644 --- a/patched-vscode/src/vs/workbench/contrib/list/browser/list.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/list/browser/list.contribution.ts @@ -5,6 +5,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { ListResizeColumnAction } from 'vs/workbench/contrib/list/browser/listResizeColumnAction'; export class ListContext implements IWorkbenchContribution { @@ -21,3 +23,5 @@ export class ListContext implements IWorkbenchContribution { } registerWorkbenchContribution2(ListContext.ID, ListContext, WorkbenchPhase.BlockStartup); +registerAction2(ListResizeColumnAction); + diff --git a/patched-vscode/src/vs/workbench/contrib/list/browser/listResizeColumnAction.ts b/patched-vscode/src/vs/workbench/contrib/list/browser/listResizeColumnAction.ts new file mode 100644 index 00000000..9c106b6c --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/list/browser/listResizeColumnAction.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TableColumnResizeQuickPick } from 'vs/workbench/contrib/list/browser/tableColumnResizeQuickPick'; +import { Table } from 'vs/base/browser/ui/table/tableWidget'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; + +export class ListResizeColumnAction extends Action2 { + constructor() { + super({ + id: 'list.resizeColumn', + title: { value: localize('list.resizeColumn', "Resize Column"), original: 'Resize Column' }, + category: { value: localize('list', "List"), original: 'List' }, + precondition: WorkbenchListFocusContextKey, + f1: true + }); + } + + async run(accessor: ServicesAccessor): Promise { + const listService = accessor.get(IListService); + const instantiationService = accessor.get(IInstantiationService); + + const list = listService.lastFocusedList; + if (list instanceof Table) { + await instantiationService.createInstance(TableColumnResizeQuickPick, list).show(); + } + } +} + diff --git a/patched-vscode/src/vs/workbench/contrib/list/browser/tableColumnResizeQuickPick.ts b/patched-vscode/src/vs/workbench/contrib/list/browser/tableColumnResizeQuickPick.ts new file mode 100644 index 00000000..370baff0 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/list/browser/tableColumnResizeQuickPick.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Table } from 'vs/base/browser/ui/table/tableWidget'; +import { Disposable } from 'vs/base/common/lifecycle'; +import Severity from 'vs/base/common/severity'; +import { localize } from 'vs/nls'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; + +interface IColumnResizeQuickPickItem extends IQuickPickItem { + index: number; +} + +export class TableColumnResizeQuickPick extends Disposable { + constructor( + private readonly _table: Table, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + ) { + super(); + } + + async show(): Promise { + const items: IColumnResizeQuickPickItem[] = []; + this._table.getColumnLabels().forEach((label, index) => { + if (label) { + items.push({ label, index }); + } + }); + const column = await this._quickInputService.pick(items, { placeHolder: localize('table.column.selection', "Select the column to resize, type to filter.") }); + if (!column) { + return; + } + const value = await this._quickInputService.input({ + placeHolder: localize('table.column.resizeValue.placeHolder', "i.e. 20, 60, 100..."), + prompt: localize('table.column.resizeValue.prompt', "Please enter a width in percentage for the '{0}' column.", column.label), + validateInput: (input: string) => this._validateColumnResizeValue(input) + }); + const percentageValue = value ? Number.parseInt(value) : undefined; + if (!percentageValue) { + return; + } + this._table.resizeColumn(column.index, percentageValue); + } + + private async _validateColumnResizeValue(input: string): Promise { + const percentage = Number.parseInt(input); + if (input && !Number.isInteger(percentage)) { + return localize('table.column.resizeValue.invalidType', "Please enter an integer."); + } else if (percentage < 0 || percentage > 100) { + return localize('table.column.resizeValue.invalidRange', "Please enter a number greater than 0 and less than or equal to 100."); + } + return null; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts b/patched-vscode/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts index c768ab60..e4368d9e 100644 --- a/patched-vscode/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts +++ b/patched-vscode/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts @@ -28,11 +28,16 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ILabelService } from 'vs/platform/label/common/label'; -import { firstOrDefault } from 'vs/base/common/arrays'; +import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { getLocalHistoryDateFormatter, LOCAL_HISTORY_ICON_RESTORE, LOCAL_HISTORY_MENU_CONTEXT_KEY } from 'vs/workbench/contrib/localHistory/browser/localHistory'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { ResourceSet } from 'vs/base/common/map'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; const LOCAL_HISTORY_CATEGORY = localize2('localHistory.category', 'Local History'); +const CTX_LOCAL_HISTORY_ENABLED = ContextKeyExpr.has('config.workbench.localHistory.enabled'); export interface ITimelineCommandArgument { uri: URI; @@ -316,7 +321,8 @@ registerAction2(class extends Action2 { id: 'workbench.action.localHistory.restoreViaPicker', title: localize2('localHistory.restoreViaPicker', 'Find Entry to Restore'), f1: true, - category: LOCAL_HISTORY_CATEGORY + category: LOCAL_HISTORY_CATEGORY, + precondition: CTX_LOCAL_HISTORY_ENABLED }); } async run(accessor: ServicesAccessor): Promise { @@ -328,34 +334,49 @@ registerAction2(class extends Action2 { const editorService = accessor.get(IEditorService); const fileService = accessor.get(IFileService); const commandService = accessor.get(ICommandService); + const historyService = accessor.get(IHistoryService); // Show all resources with associated history entries in picker // with progress because this operation will take longer the more // files have been saved overall. + // + // Sort the resources by history to put more relevant entries + // to the top. - const resourcePicker = quickInputService.createQuickPick(); + const resourcePickerDisposables = new DisposableStore(); + const resourcePicker = resourcePickerDisposables.add(quickInputService.createQuickPick()); let cts = new CancellationTokenSource(); - resourcePicker.onDidHide(() => cts.dispose(true)); + resourcePickerDisposables.add(resourcePicker.onDidHide(() => cts.dispose(true))); resourcePicker.busy = true; resourcePicker.show(); - const resources = await workingCopyHistoryService.getAll(cts.token); + const resources = new ResourceSet(await workingCopyHistoryService.getAll(cts.token)); + const recentEditorResources = new ResourceSet(coalesce(historyService.getHistory().map(({ resource }) => resource))); + + const resourcesSortedByRecency: URI[] = []; + for (const resource of recentEditorResources) { + if (resources.has(resource)) { + resourcesSortedByRecency.push(resource); + resources.delete(resource); + } + } + resourcesSortedByRecency.push(...[...resources].sort((r1, r2) => r1.fsPath < r2.fsPath ? -1 : 1)); resourcePicker.busy = false; resourcePicker.placeholder = localize('restoreViaPicker.filePlaceholder', "Select the file to show local history for"); resourcePicker.matchOnLabel = true; resourcePicker.matchOnDescription = true; - resourcePicker.items = resources.map(resource => ({ + resourcePicker.items = [...resourcesSortedByRecency].map(resource => ({ resource, label: basenameOrAuthority(resource), description: labelService.getUriLabel(dirname(resource), { relative: true }), iconClasses: getIconClasses(modelService, languageService, resource) - })).sort((r1, r2) => r1.resource.fsPath < r2.resource.fsPath ? -1 : 1); + })); await Event.toPromise(resourcePicker.onDidAccept); - resourcePicker.dispose(); + resourcePickerDisposables.dispose(); const resource = firstOrDefault(resourcePicker.selectedItems)?.resource; if (!resource) { @@ -365,10 +386,11 @@ registerAction2(class extends Action2 { // Show all entries for the picked resource in another picker // and open the entry in the end that was selected by the user - const entryPicker = quickInputService.createQuickPick(); + const entryPickerDisposables = new DisposableStore(); + const entryPicker = entryPickerDisposables.add(quickInputService.createQuickPick()); cts = new CancellationTokenSource(); - entryPicker.onDidHide(() => cts.dispose(true)); + entryPickerDisposables.add(entryPicker.onDidHide(() => cts.dispose(true))); entryPicker.busy = true; entryPicker.show(); @@ -376,6 +398,7 @@ registerAction2(class extends Action2 { const entries = await workingCopyHistoryService.getEntries(resource, cts.token); entryPicker.busy = false; + entryPicker.canAcceptInBackground = true; entryPicker.placeholder = localize('restoreViaPicker.entryPlaceholder', "Select the local history entry to open"); entryPicker.matchOnLabel = true; entryPicker.matchOnDescription = true; @@ -385,24 +408,27 @@ registerAction2(class extends Action2 { description: toLocalHistoryEntryDateLabel(entry.timestamp) })); - await Event.toPromise(entryPicker.onDidAccept); - entryPicker.dispose(); + entryPickerDisposables.add(entryPicker.onDidAccept(async e => { + if (!e.inBackground) { + entryPickerDisposables.dispose(); + } - const selectedItem = firstOrDefault(entryPicker.selectedItems); - if (!selectedItem) { - return; - } + const selectedItem = firstOrDefault(entryPicker.selectedItems); + if (!selectedItem) { + return; + } - const resourceExists = await fileService.exists(selectedItem.entry.workingCopy.resource); - if (resourceExists) { - return commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(selectedItem.entry, selectedItem.entry.workingCopy.resource)); - } + const resourceExists = await fileService.exists(selectedItem.entry.workingCopy.resource); + if (resourceExists) { + return commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(selectedItem.entry, selectedItem.entry.workingCopy.resource, { preserveFocus: e.inBackground })); + } - return openEntry(selectedItem.entry, editorService); + return openEntry(selectedItem.entry, editorService, { preserveFocus: e.inBackground }); + })); } }); -MenuRegistry.appendMenuItem(MenuId.TimelineTitle, { command: { id: 'workbench.action.localHistory.restoreViaPicker', title: localize2('localHistory.restoreViaPickerMenu', 'Local History: Find Entry to Restore...') }, group: 'submenu', order: 1 }); +MenuRegistry.appendMenuItem(MenuId.TimelineTitle, { command: { id: 'workbench.action.localHistory.restoreViaPicker', title: localize2('localHistory.restoreViaPickerMenu', 'Local History: Find Entry to Restore...') }, group: 'submenu', order: 1, when: CTX_LOCAL_HISTORY_ENABLED }); //#endregion @@ -427,18 +453,19 @@ registerAction2(class extends Action2 { const { entry } = await findLocalHistoryEntry(workingCopyHistoryService, item); if (entry) { - const inputBox = quickInputService.createInputBox(); + const disposables = new DisposableStore(); + const inputBox = disposables.add(quickInputService.createInputBox()); inputBox.title = localize('renameLocalHistoryEntryTitle', "Rename Local History Entry"); inputBox.ignoreFocusOut = true; inputBox.placeholder = localize('renameLocalHistoryPlaceholder', "Enter the new name of the local history entry"); inputBox.value = SaveSourceRegistry.getSourceLabel(entry.source); inputBox.show(); - inputBox.onDidAccept(() => { + disposables.add(inputBox.onDidAccept(() => { if (inputBox.value) { workingCopyHistoryService.updateEntry(entry, { source: inputBox.value }, CancellationToken.None); } - inputBox.dispose(); - }); + disposables.dispose(); + })); } } }); @@ -499,7 +526,8 @@ registerAction2(class extends Action2 { id: 'workbench.action.localHistory.deleteAll', title: localize2('localHistory.deleteAll', 'Delete All'), f1: true, - category: LOCAL_HISTORY_CATEGORY + category: LOCAL_HISTORY_CATEGORY, + precondition: CTX_LOCAL_HISTORY_ENABLED }); } async run(accessor: ServicesAccessor): Promise { @@ -534,7 +562,7 @@ registerAction2(class extends Action2 { title: localize2('localHistory.create', 'Create Entry'), f1: true, category: LOCAL_HISTORY_CATEGORY, - precondition: ActiveEditorContext + precondition: ContextKeyExpr.and(CTX_LOCAL_HISTORY_ENABLED, ActiveEditorContext) }); } async run(accessor: ServicesAccessor): Promise { @@ -549,19 +577,20 @@ registerAction2(class extends Action2 { return; // only enable for selected schemes } - const inputBox = quickInputService.createInputBox(); + const disposables = new DisposableStore(); + const inputBox = disposables.add(quickInputService.createInputBox()); inputBox.title = localize('createLocalHistoryEntryTitle', "Create Local History Entry"); inputBox.ignoreFocusOut = true; inputBox.placeholder = localize('createLocalHistoryPlaceholder', "Enter the new name of the local history entry for '{0}'", labelService.getUriBasenameLabel(resource)); inputBox.show(); - inputBox.onDidAccept(async () => { + disposables.add(inputBox.onDidAccept(async () => { const entrySource = inputBox.value; - inputBox.dispose(); + disposables.dispose(); if (entrySource) { await workingCopyHistoryService.addEntry({ resource, source: inputBox.value }, CancellationToken.None); } - }); + })); } }); @@ -569,12 +598,13 @@ registerAction2(class extends Action2 { //#region Helpers -async function openEntry(entry: IWorkingCopyHistoryEntry, editorService: IEditorService): Promise { +async function openEntry(entry: IWorkingCopyHistoryEntry, editorService: IEditorService, options?: IEditorOptions): Promise { const resource = LocalHistoryFileSystemProvider.toLocalHistoryFileSystem({ location: entry.location, associatedResource: entry.workingCopy.resource }); await editorService.openEditor({ resource, - label: localize('localHistoryEditorLabel', "{0} ({1} • {2})", entry.workingCopy.name, SaveSourceRegistry.getSourceLabel(entry.source), toLocalHistoryEntryDateLabel(entry.timestamp)) + label: localize('localHistoryEditorLabel', "{0} ({1} • {2})", entry.workingCopy.name, SaveSourceRegistry.getSourceLabel(entry.source), toLocalHistoryEntryDateLabel(entry.timestamp)), + options }); } @@ -585,9 +615,9 @@ async function closeEntry(entry: IWorkingCopyHistoryEntry, editorService: IEdito await editorService.closeEditors(editors, { preserveFocus: true }); } -export function toDiffEditorArguments(entry: IWorkingCopyHistoryEntry, resource: URI): unknown[]; -export function toDiffEditorArguments(previousEntry: IWorkingCopyHistoryEntry, entry: IWorkingCopyHistoryEntry): unknown[]; -export function toDiffEditorArguments(arg1: IWorkingCopyHistoryEntry, arg2: IWorkingCopyHistoryEntry | URI): unknown[] { +export function toDiffEditorArguments(entry: IWorkingCopyHistoryEntry, resource: URI, options?: IEditorOptions): unknown[]; +export function toDiffEditorArguments(previousEntry: IWorkingCopyHistoryEntry, entry: IWorkingCopyHistoryEntry, options?: IEditorOptions): unknown[]; +export function toDiffEditorArguments(arg1: IWorkingCopyHistoryEntry, arg2: IWorkingCopyHistoryEntry | URI, options?: IEditorOptions): unknown[] { // Left hand side is always a working copy history entry const originalResource = LocalHistoryFileSystemProvider.toLocalHistoryFileSystem({ location: arg1.location, associatedResource: arg1.workingCopy.resource }); @@ -620,7 +650,7 @@ export function toDiffEditorArguments(arg1: IWorkingCopyHistoryEntry, arg2: IWor originalResource, modifiedResource, label, - undefined // important to keep order of arguments in command proper + options ? [undefined, options] : undefined ]; } diff --git a/patched-vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts b/patched-vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts index d9ad34f1..7f2f1f13 100644 --- a/patched-vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts @@ -37,7 +37,8 @@ export class ConfigureDisplayLanguageAction extends Action2 { const installedLanguages = await languagePackService.getInstalledLanguages(); - const qp = quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const qp = disposables.add(quickInputService.createQuickPick({ useSeparators: true })); qp.matchOnDescription = true; qp.placeholder = localize('chooseLocale', "Select Display Language"); @@ -46,7 +47,6 @@ export class ConfigureDisplayLanguageAction extends Action2 { qp.items = items.concat(this.withMoreInfoButton(installedLanguages)); } - const disposables = new DisposableStore(); const source = new CancellationTokenSource(); disposables.add(qp.onDispose(() => { source.cancel(); diff --git a/patched-vscode/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts b/patched-vscode/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts index b603ed44..a93fc9f9 100644 --- a/patched-vscode/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts @@ -103,7 +103,7 @@ class NativeLocalizationWorkbenchContribution extends BaseLocalizationWorkbenchC if (!this.galleryService.isEnabled()) { return; } - if (!language || !locale || locale === 'en' || locale.indexOf('en-') === 0) { + if (!language || !locale || platform.Language.isDefaultVariant()) { return; } if (locale.startsWith(language) || languagePackSuggestionIgnoreList.includes(locale)) { diff --git a/patched-vscode/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/patched-vscode/src/vs/workbench/contrib/logs/common/logs.contribution.ts index b96ba30f..a5c42063 100644 --- a/patched-vscode/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -70,11 +70,11 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { super(); const contextKey = CONTEXT_LOG_LEVEL.bindTo(contextKeyService); contextKey.set(LogLevelToString(loggerService.getLogLevel())); - loggerService.onDidChangeLogLevel(e => { + this._register(loggerService.onDidChangeLogLevel(e => { if (isLogLevel(e)) { contextKey.set(LogLevelToString(loggerService.getLogLevel())); } - }); + })); this.onDidAddLoggers(loggerService.getRegisteredLoggers()); this._register(loggerService.onDidChangeLoggers(({ added, removed }) => { diff --git a/patched-vscode/src/vs/workbench/contrib/logs/common/logsActions.ts b/patched-vscode/src/vs/workbench/contrib/logs/common/logsActions.ts index 86c59a48..6873d757 100644 --- a/patched-vscode/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -75,7 +75,7 @@ export class SetLogLevelAction extends Action { return new Promise((resolve, reject) => { const disposables = new DisposableStore(); - const quickPick = this.quickInputService.createQuickPick(); + const quickPick = disposables.add(this.quickInputService.createQuickPick({ useSeparators: true })); quickPick.placeholder = nls.localize('selectlog', "Set Log Level"); quickPick.items = entries; let selectedItem: IQuickPickItem | undefined; @@ -108,7 +108,7 @@ export class SetLogLevelAction extends Action { return new Promise((resolve, reject) => { const disposables = new DisposableStore(); - const quickPick = this.quickInputService.createQuickPick(); + const quickPick = disposables.add(this.quickInputService.createQuickPick()); quickPick.placeholder = logChannel ? nls.localize('selectLogLevelFor', " {0}: Select log level", logChannel?.label) : nls.localize('selectLogLevel', "Select log level"); quickPick.items = entries; quickPick.activeItems = entries.filter((entry) => entry.level === this.loggerService.getLogLevel()); diff --git a/patched-vscode/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/patched-vscode/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 9b18b5cd..4e417a65 100644 --- a/patched-vscode/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/patched-vscode/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -3,17 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { hookDomPurifyHrefAndSrcSanitizer, basicMarkupHtmlTags } from 'vs/base/browser/dom'; +import { basicMarkupHtmlTags, hookDomPurifyHrefAndSrcSanitizer } from 'vs/base/browser/dom'; import * as dompurify from 'vs/base/browser/dompurify/dompurify'; import { allowedMarkdownAttr } from 'vs/base/browser/markdownRenderer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { marked } from 'vs/base/common/marked/marked'; +import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; +import { escape } from 'vs/base/common/strings'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { escape } from 'vs/base/common/strings'; -import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; export const DEFAULT_MARKDOWN_STYLES = ` body { @@ -33,7 +32,7 @@ img { } a { - text-decoration: none; + text-decoration: var(--text-link-decoration); } a:hover { @@ -184,7 +183,14 @@ function sanitize(documentContent: string, allowUnknownProtocols: boolean): stri } } -/** +interface IRenderMarkdownDocumentOptions { + readonly shouldSanitize?: boolean; + readonly allowUnknownProtocols?: boolean; + readonly renderer?: marked.Renderer; + readonly token?: CancellationToken; +} + +/*marked.* * Renders a string of markdown as a document. * * Uses VS Code's syntax highlighting code blocks. @@ -193,47 +199,117 @@ export async function renderMarkdownDocument( text: string, extensionService: IExtensionService, languageService: ILanguageService, - shouldSanitize: boolean = true, - allowUnknownProtocols: boolean = false, - token?: CancellationToken, - settingRenderer?: SimpleSettingRenderer + options?: IRenderMarkdownDocumentOptions ): Promise { + const m = new marked.Marked( + MarkedHighlight.markedHighlight({ + async: true, + async highlight(code: string, lang: string): Promise { + if (typeof lang !== 'string') { + return escape(code); + } + + await extensionService.whenInstalledExtensionsRegistered(); + if (options?.token?.isCancellationRequested) { + return ''; + } + + const languageId = languageService.getLanguageIdByLanguageName(lang) ?? languageService.getLanguageIdByLanguageName(lang.split(/\s+|:|,|(?!^)\{|\?]/, 1)[0]); + return tokenizeToString(languageService, code, languageId); + } + }) + ); + + const raw = await m.parse(text, { renderer: options?.renderer, async: true }); + if (options?.shouldSanitize ?? true) { + return sanitize(raw, options?.allowUnknownProtocols ?? false); + } else { + return raw; + } +} + +namespace MarkedHighlight { + // Copied from https://github.com/markedjs/marked-highlight/blob/main/src/index.js - const highlight = (code: string, lang: string | undefined, callback: ((error: any, code: string) => void) | undefined): any => { - if (!callback) { - return code; + export function markedHighlight(options: marked.MarkedOptions & { highlight: (code: string, lang: string, info: string) => string | Promise }) { + if (typeof options === 'function') { + options = { + highlight: options, + }; } - if (typeof lang !== 'string') { - callback(null, escape(code)); - return ''; + if (!options || typeof options.highlight !== 'function') { + throw new Error('Must provide highlight function'); } - extensionService.whenInstalledExtensionsRegistered().then(async () => { - if (token?.isCancellationRequested) { - callback(null, ''); - return; - } + return { + async: !!options.async, + walkTokens(token: marked.Token): Promise | void { + if (token.type !== 'code') { + return; + } - const languageId = languageService.getLanguageIdByLanguageName(lang) ?? languageService.getLanguageIdByLanguageName(lang.split(/\s+|:|,|(?!^)\{|\?]/, 1)[0]); - const html = await tokenizeToString(languageService, code, languageId); - callback(null, html); - }); - return ''; - }; + const lang = getLang(token.lang); + + if (options.async) { + return Promise.resolve(options.highlight(token.text, lang, token.lang || '')).then(updateToken(token)); + } - const renderer = new marked.Renderer(); - if (settingRenderer) { - renderer.html = settingRenderer.getHtmlRenderer(); + const code = options.highlight(token.text, lang, token.lang || ''); + if (code instanceof Promise) { + throw new Error('markedHighlight is not set to async but the highlight function is async. Set the async option to true on markedHighlight to await the async highlight function.'); + } + updateToken(token)(code); + }, + renderer: { + code({ text, lang, escaped }: marked.Tokens.Code) { + const classAttr = lang + ? ` class="language-${escape(lang)}"` + : ''; + text = text.replace(/\n$/, ''); + return `
    ${escaped ? text : escape(text, true)}\n
    `; + }, + } as any, + }; + } + + function getLang(lang: string) { + return (lang || '').match(/\S*/)![0]; + } + + function updateToken(token: any) { + return (code: string) => { + if (typeof code === 'string' && code !== token.text) { + token.escaped = true; + token.text = code; + } + }; } - return new Promise((resolve, reject) => { - marked(text, { highlight, renderer }, (err, value) => err ? reject(err) : resolve(value)); - }).then(raw => { - if (shouldSanitize) { - return sanitize(raw, allowUnknownProtocols); + // copied from marked helpers + const escapeTest = /[&<>"']/; + const escapeReplace = new RegExp(escapeTest.source, 'g'); + const escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/; + const escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, 'g'); + const escapeReplacement: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + [`'`]: ''', + }; + const getEscapeReplacement = (ch: string) => escapeReplacement[ch]; + function escape(html: string, encode?: boolean) { + if (encode) { + if (escapeTest.test(html)) { + return html.replace(escapeReplace, getEscapeReplacement); + } } else { - return raw; + if (escapeTestNoEncode.test(html)) { + return html.replace(escapeReplaceNoEncode, getEscapeReplacement); + } } - }); + + return html; + } } diff --git a/patched-vscode/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/patched-vscode/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 095a2978..00dd93a6 100644 --- a/patched-vscode/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/patched-vscode/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -4,22 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IPreferencesService, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { IPreferencesService, ISetting } from 'vs/workbench/services/preferences/common/preferences'; import { settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { DefaultSettings } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; - -const codeSettingRegex = /^/; +import { Schemas } from 'vs/base/common/network'; +import { Tokens } from 'vs/base/common/marked/marked'; export class SimpleSettingRenderer { - private _defaultSettings: DefaultSettings; + private readonly codeSettingRegex: RegExp; + private _updatedSettings = new Map(); // setting ID to user's original setting value private _encounteredSettings = new Map(); // setting ID to setting private _featuredSettings = new Map(); // setting ID to feature value @@ -29,9 +28,9 @@ export class SimpleSettingRenderer { @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IPreferencesService private readonly _preferencesService: IPreferencesService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IClipboardService private readonly _clipboardService: IClipboardService + @IClipboardService private readonly _clipboardService: IClipboardService, ) { - this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); + this.codeSettingRegex = new RegExp(`^`); } get featuredSettingStates(): Map { @@ -42,17 +41,17 @@ export class SimpleSettingRenderer { return result; } - getHtmlRenderer(): (html: string) => string { - return (html): string => { - const match = codeSettingRegex.exec(html); + getHtmlRenderer(): (token: Tokens.HTML) => string { + return ({ raw }: Tokens.HTML): string => { + const match = this.codeSettingRegex.exec(raw); if (match && match.length === 4) { const settingId = match[2]; const rendered = this.render(settingId, match[3]); if (rendered) { - html = html.replace(codeSettingRegex, rendered); + raw = raw.replace(this.codeSettingRegex, rendered); } } - return html; + return raw; }; } @@ -60,25 +59,11 @@ export class SimpleSettingRenderer { return `${Schemas.codeSetting}://${settingId}${value ? `/${value}` : ''}`; } - private settingsGroups: ISettingsGroup[] | undefined = undefined; private getSetting(settingId: string): ISetting | undefined { - if (!this.settingsGroups) { - this.settingsGroups = this._defaultSettings.getSettingsGroups(); - } if (this._encounteredSettings.has(settingId)) { return this._encounteredSettings.get(settingId); } - for (const group of this.settingsGroups) { - for (const section of group.sections) { - for (const setting of section.settings) { - if (setting.key === settingId) { - this._encounteredSettings.set(settingId, setting); - return setting; - } - } - } - } - return undefined; + return this._preferencesService.getSetting(settingId); } parseValue(settingId: string, value: string): any { @@ -165,7 +150,7 @@ export class SimpleSettingRenderer { return ` ${setting.key} - `; + `; } private getSettingMessage(setting: ISetting, newValue: boolean | string | number): string | undefined { diff --git a/patched-vscode/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/patched-vscode/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 55e38f3d..2da36560 100644 --- a/patched-vscode/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.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 * as assert from 'assert'; +import assert from 'assert'; import { IAction } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -58,7 +58,15 @@ suite('Markdown Setting Renderer Test', () => { suiteSetup(() => { configurationService = new MarkdownConfigurationService(); - preferencesService = {}; + preferencesService = { + getSetting: (setting) => { + let type = 'boolean'; + if (setting.includes('string')) { + type = 'string'; + } + return { type, key: setting }; + } + }; contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any, { writeText: async () => { } } as any); @@ -70,13 +78,13 @@ suite('Markdown Setting Renderer Test', () => { test('render code setting button with value', () => { const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlNoValue = ''; - const renderedHtmlNoValue = htmlRenderer(htmlNoValue); + const htmlNoValue = ''; + const renderedHtmlNoValue = htmlRenderer({ block: false, raw: htmlNoValue, pre: false, text: '', type: 'html' }); assert.strictEqual(renderedHtmlNoValue, - ` + ` example.booleanSetting - `); + `); }); test('actions with no value', () => { diff --git a/patched-vscode/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts b/patched-vscode/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts index e7355a0d..c97455cb 100644 --- a/patched-vscode/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts +++ b/patched-vscode/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts @@ -111,7 +111,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'type': 'object', 'properties': { 'problems.decorations.enabled': { - 'markdownDescription': localize('markers.showOnFile', "Show Errors & Warnings on files and folder. Overwritten by `#problems.visibility#` when it is off."), + 'markdownDescription': localize('markers.showOnFile', "Show Errors & Warnings on files and folder. Overwritten by {0} when it is off.", '`#problems.visibility#`'), 'type': 'boolean', 'default': true } diff --git a/patched-vscode/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/patched-vscode/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 40c99ee3..1a3dec09 100644 --- a/patched-vscode/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/patched-vscode/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -50,7 +50,7 @@ import { unsupportedSchemas } from 'vs/platform/markers/common/markerService'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import Severity from 'vs/base/common/severity'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; interface IResourceMarkersTemplateData { @@ -281,7 +281,7 @@ class MarkerWidget extends Disposable { private readonly icon: HTMLElement; private readonly iconContainer: HTMLElement; private readonly messageAndDetailsContainer: HTMLElement; - private readonly messageAndDetailsContainerHover: IUpdatableHover; + private readonly messageAndDetailsContainerHover: IManagedHover; private readonly disposables = this._register(new DisposableStore()); constructor( @@ -302,7 +302,7 @@ class MarkerWidget extends Disposable { this.iconContainer = dom.append(parent, dom.$('')); this.icon = dom.append(this.iconContainer, dom.$('')); this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details-container')); - this.messageAndDetailsContainerHover = this._register(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.messageAndDetailsContainer, '')); + this.messageAndDetailsContainerHover = this._register(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.messageAndDetailsContainer, '')); } render(element: Marker, filterData: MarkerFilterData | undefined): void { diff --git a/patched-vscode/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts b/patched-vscode/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts index b8334c94..bec69395 100644 --- a/patched-vscode/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; import { MarkersModel, Marker, ResourceMarkers, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel.ts index b043f084..279baa80 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel.ts @@ -126,7 +126,7 @@ export class TempFileMergeEditorModeFactory implements IMergeEditorInputModelFac class TempFileMergeEditorInputModel extends EditorModel implements IMergeEditorInputModel { private readonly savedAltVersionId = observableValue(this, this.model.resultTextModel.getAlternativeVersionId()); - private readonly altVersionId = observableFromEvent( + private readonly altVersionId = observableFromEvent(this, e => this.model.resultTextModel.onDidChangeContent(e), () => /** @description getAlternativeVersionId */ this.model.resultTextModel.getAlternativeVersionId() @@ -340,7 +340,7 @@ export class WorkspaceMergeEditorModeFactory implements IMergeEditorInputModelFa } class WorkspaceMergeEditorInputModel extends EditorModel implements IMergeEditorInputModel { - public readonly isDirty = observableFromEvent( + public readonly isDirty = observableFromEvent(this, Event.any(this.resultTextFileModel.onDidChangeDirty, this.resultTextFileModel.onDidSaveError), () => /** @description isDirty */ this.resultTextFileModel.isDirty() ); diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts index cc6e5700..f8d1d8b6 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts @@ -8,7 +8,7 @@ import { mergeCurrentHeaderBackground, mergeIncomingHeaderBackground, registerCo export const diff = registerColor( 'mergeEditor.change.background', - { dark: '#9bb95533', light: '#9bb95533', hcDark: '#9bb95533', hcLight: '#9bb95533', }, + '#9bb95533', localize('mergeEditor.change.background', 'The background color for changes.') ); @@ -38,49 +38,49 @@ export const conflictBorderUnhandledUnfocused = registerColor( export const conflictBorderUnhandledFocused = registerColor( 'mergeEditor.conflict.unhandledFocused.border', - { dark: '#ffa600', light: '#ffa600', hcDark: '#ffa600', hcLight: '#ffa600', }, + '#ffa600', localize('mergeEditor.conflict.unhandledFocused.border', 'The border color of unhandled focused conflicts.') ); export const conflictBorderHandledUnfocused = registerColor( 'mergeEditor.conflict.handledUnfocused.border', - { dark: '#86868649', light: '#86868649', hcDark: '#86868649', hcLight: '#86868649', }, + '#86868649', localize('mergeEditor.conflict.handledUnfocused.border', 'The border color of handled unfocused conflicts.') ); export const conflictBorderHandledFocused = registerColor( 'mergeEditor.conflict.handledFocused.border', - { dark: '#c1c1c1cc', light: '#c1c1c1cc', hcDark: '#c1c1c1cc', hcLight: '#c1c1c1cc', }, + '#c1c1c1cc', localize('mergeEditor.conflict.handledFocused.border', 'The border color of handled focused conflicts.') ); export const handledConflictMinimapOverViewRulerColor = registerColor( 'mergeEditor.conflict.handled.minimapOverViewRuler', - { dark: '#adaca8ee', light: '#adaca8ee', hcDark: '#adaca8ee', hcLight: '#adaca8ee', }, + '#adaca8ee', localize('mergeEditor.conflict.handled.minimapOverViewRuler', 'The foreground color for changes in input 1.') ); export const unhandledConflictMinimapOverViewRulerColor = registerColor( 'mergeEditor.conflict.unhandled.minimapOverViewRuler', - { dark: '#fcba03FF', light: '#fcba03FF', hcDark: '#fcba03FF', hcLight: '#fcba03FF', }, + '#fcba03FF', localize('mergeEditor.conflict.unhandled.minimapOverViewRuler', 'The foreground color for changes in input 1.') ); export const conflictingLinesBackground = registerColor( 'mergeEditor.conflictingLines.background', - { dark: '#ffea0047', light: '#ffea0047', hcDark: '#ffea0047', hcLight: '#ffea0047', }, + '#ffea0047', localize('mergeEditor.conflictingLines.background', 'The background of the "Conflicting Lines" text.') ); const contentTransparency = 0.4; export const conflictInput1Background = registerColor( 'mergeEditor.conflict.input1.background', - { dark: transparent(mergeCurrentHeaderBackground, contentTransparency), light: transparent(mergeCurrentHeaderBackground, contentTransparency), hcDark: transparent(mergeCurrentHeaderBackground, contentTransparency), hcLight: transparent(mergeCurrentHeaderBackground, contentTransparency) }, + transparent(mergeCurrentHeaderBackground, contentTransparency), localize('mergeEditor.conflict.input1.background', 'The background color of decorations in input 1.') ); export const conflictInput2Background = registerColor( 'mergeEditor.conflict.input2.background', - { dark: transparent(mergeIncomingHeaderBackground, contentTransparency), light: transparent(mergeIncomingHeaderBackground, contentTransparency), hcDark: transparent(mergeIncomingHeaderBackground, contentTransparency), hcLight: transparent(mergeIncomingHeaderBackground, contentTransparency) }, + transparent(mergeIncomingHeaderBackground, contentTransparency), localize('mergeEditor.conflict.input2.background', 'The background color of decorations in input 2.') ); diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts index 8d88a891..3290793d 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts @@ -54,8 +54,8 @@ export class ConflictActionsFactory extends Disposable { newStyle += `${this._styleClassName} { font-family: var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}}`; } this._styleElement.textContent = newStyle; - this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily ?? 'inherit'); - this._editor.getContainerDomNode().style.setProperty(fontFeaturesVar, editorFontInfo.fontFeatureSettings); + this._editor.getContainerDomNode().style?.setProperty(fontFamilyVar, fontFamily ?? 'inherit'); + this._editor.getContainerDomNode().style?.setProperty(fontFeaturesVar, editorFontInfo.fontFeatureSettings); } private _getLayoutInfo() { diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index b751b6bc..7d564cb4 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -10,12 +10,12 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditor import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; export class EditorGutter extends Disposable { - private readonly scrollTop = observableFromEvent( + private readonly scrollTop = observableFromEvent(this, this._editor.onDidScrollChange, (e) => /** @description editor.onDidScrollChange */ this._editor.getScrollTop() ); private readonly isScrollTopZero = this.scrollTop.map((scrollTop) => /** @description isScrollTopZero */ scrollTop === 0); - private readonly modelAttached = observableFromEvent( + private readonly modelAttached = observableFromEvent(this, this._editor.onDidChangeModel, (e) => /** @description editor.onDidChangeModel */ this._editor.hasModel() ); @@ -126,7 +126,7 @@ export class EditorGutter extends D for (const id of unusedIds) { const view = this.views.get(id)!; view.gutterItemView.dispose(); - this._domNode.removeChild(view.domNode); + view.domNode.remove(); this.views.delete(id); } } @@ -154,4 +154,3 @@ export interface IGutterItemView extends IDisposable update(item: T): void; layout(top: number, height: number, viewTop: number, viewHeight: number): void; } - diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index 29af08fb..b75ca359 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -79,17 +79,17 @@ export abstract class CodeEditorView extends Disposable { this.editor.updateOptions(newOptions); } - public readonly isFocused = observableFromEvent( + public readonly isFocused = observableFromEvent(this, Event.any(this.editor.onDidBlurEditorWidget, this.editor.onDidFocusEditorWidget), () => /** @description editor.hasWidgetFocus */ this.editor.hasWidgetFocus() ); - public readonly cursorPosition = observableFromEvent( + public readonly cursorPosition = observableFromEvent(this, this.editor.onDidChangeCursorPosition, () => /** @description editor.getPosition */ this.editor.getPosition() ); - public readonly selection = observableFromEvent( + public readonly selection = observableFromEvent(this, this.editor.onDidChangeCursorSelection, () => /** @description editor.getSelections */ this.editor.getSelections() ); diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts index 3175a1ca..27a48ee2 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/fixedZoneWidget.ts @@ -33,6 +33,7 @@ export abstract class FixedZoneWidget extends Disposable { domNode: document.createElement('div'), afterLineNumber: afterLineNumber, heightInPx: height, + ordinal: 50000 + 1, onComputedHeight: (height) => { this.widgetDomNode.style.height = `${height}px`; }, diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index 1c0f093d..2094bfec 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -193,6 +193,7 @@ export class MergeEditorViewModel extends Disposable { ): void { this.manuallySetActiveModifiedBaseRange.set({ range: baseRange, counter: this.counter++ }, tx); this.model.setState(baseRange, state, inputNumber, tx); + this.lastFocusedEditor.clearCache(tx); } private goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void { diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts index 85c475e3..135d0d9a 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/test/browser/mapping.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/patched-vscode/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/patched-vscode/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts index 0441e448..a07bc71e 100644 --- a/patched-vscode/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IReader, transaction } from 'vs/base/common/observable'; import { isDefined } from 'vs/base/common/types'; diff --git a/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts b/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts index 5b1ab36c..e8abecf6 100644 --- a/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts +++ b/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/actions.ts @@ -10,10 +10,10 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize2 } from 'vs/nls'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ITextEditorOptions, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { getCommandsContext, resolveCommandsContext } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { IEditorCommandsContext } from 'vs/workbench/common/editor'; -import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { resolveCommandsContext } from 'vs/workbench/browser/parts/editor/editorCommandsContext'; import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -40,21 +40,28 @@ export class GoToFileAction extends Action2 { const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; let selections: Selection[] | undefined = undefined; - if (activeEditorPane instanceof MultiDiffEditor) { - const editor = activeEditorPane.tryGetCodeEditor(uri); - if (editor) { - selections = editor.editor.getSelections() ?? undefined; - } + if (!(activeEditorPane instanceof MultiDiffEditor)) { + return; } - const editor = await editorService.openEditor({ resource: uri }); - if (selections && (editor instanceof TextFileEditor)) { - const c = editor.getControl(); - if (c) { - c.setSelections(selections); - c.revealLineInCenter(selections[0].selectionStartLineNumber); - } + const editor = activeEditorPane.tryGetCodeEditor(uri); + if (editor) { + selections = editor.editor.getSelections() ?? undefined; } + + let targetUri = uri; + const item = activeEditorPane.findDocumentDiffItem(uri); + if (item && item.goToFileUri) { + targetUri = item.goToFileUri; + } + + await editorService.openEditor({ + resource: targetUri, + options: { + selection: selections?.[0], + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, + } satisfies ITextEditorOptions, + }); } } @@ -75,9 +82,15 @@ export class CollapseAllAction extends Action2 { }); } - async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - const { editor } = resolveCommandsContext(accessor.get(IEditorGroupsService), getCommandsContext(accessor, resourceOrContext, context)); + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + const groupContext = resolvedContext.groupedEditors[0]; + if (!groupContext) { + return; + } + + const editor = groupContext.editors[0]; if (editor instanceof MultiDiffEditorInput) { const viewModel = await editor.getViewModel(); viewModel.collapseAll(); @@ -102,9 +115,15 @@ export class ExpandAllAction extends Action2 { }); } - async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { - const { editor } = resolveCommandsContext(accessor.get(IEditorGroupsService), getCommandsContext(accessor, resourceOrContext, context)); + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const resolvedContext = resolveCommandsContext(args, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IListService)); + + const groupContext = resolvedContext.groupedEditors[0]; + if (!groupContext) { + return; + } + const editor = groupContext.editors[0]; if (editor instanceof MultiDiffEditorInput) { const viewModel = await editor.getViewModel(); viewModel.expandAll(); diff --git a/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index ffe7a873..2b5168de 100644 --- a/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -18,7 +18,7 @@ import { AbstractEditorWithViewState } from 'vs/workbench/browser/parts/editor/e import { ICompositeControl } from 'vs/workbench/common/composite'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; +import { IDocumentDiffItemWithMultiDiffEditorItem, MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; @@ -27,6 +27,7 @@ import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/br import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { Range } from 'vs/editor/common/core/range'; +import { MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -139,6 +140,13 @@ export class MultiDiffEditor extends AbstractEditorWithViewState new MultiDiffEditorItem( resource.originalUri ? URI.parse(resource.originalUri) : undefined, resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, + resource.goToFileUri ? URI.parse(resource.goToFileUri) : undefined, )), false ); @@ -112,15 +115,16 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor label: this.label, multiDiffSourceUri: this.multiDiffSource.toString(), resources: this.initialResources?.map(resource => ({ - originalUri: resource.original?.toString(), - modifiedUri: resource.modified?.toString(), + originalUri: resource.originalUri?.toString(), + modifiedUri: resource.modifiedUri?.toString(), + goToFileUri: resource.goToFileUri?.toString(), })), }; } public setLanguageId(languageId: string, source?: string | undefined): void { const activeDiffItem = this._viewModel.requireValue().activeDiffItem.get(); - const value = activeDiffItem?.entry?.value; + const value = activeDiffItem?.documentDiffItem; if (!value) { return; } const target = value.modified ?? value.original; if (!target) { return; } @@ -144,26 +148,20 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor const source = await this._resolvedSource.getPromise(); const textResourceConfigurationService = this._textResourceConfigurationService; - // Enables delayed disposing - const garbage = new DisposableStore(); - const documentsWithPromises = mapObservableArrayCached(this, source.resources, async (r, store) => { /** @description documentsWithPromises */ let original: IReference | undefined; let modified: IReference | undefined; - const store2 = new DisposableStore(); - store.add(toDisposable(() => { - // Mark the text model references as garbage when they get stale (don't dispose them yet) - garbage.add(store2); - })); + + const multiDiffItemStore = new DisposableStore(); try { [original, modified] = await Promise.all([ - r.original ? this._textModelService.createModelReference(r.original) : undefined, - r.modified ? this._textModelService.createModelReference(r.modified) : undefined, + r.originalUri ? this._textModelService.createModelReference(r.originalUri) : undefined, + r.modifiedUri ? this._textModelService.createModelReference(r.modifiedUri) : undefined, ]); - if (original) { store2.add(original); } - if (modified) { store2.add(modified); } + if (original) { multiDiffItemStore.add(original); } + if (modified) { multiDiffItemStore.add(modified); } } catch (e) { // e.g. "File seems to be binary and cannot be opened as text" console.error(e); @@ -171,10 +169,12 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor return undefined; } - const uri = (r.modified ?? r.original)!; - return new ConstLazyPromise({ + const uri = (r.modifiedUri ?? r.originalUri)!; + const result: IDocumentDiffItemWithMultiDiffEditorItem = { + multiDiffEditorItem: r, original: original?.object.textEditorModel, modified: modified?.object.textEditorModel, + contextKeys: r.contextKeys, get options() { return { ...getReadonlyConfiguration(modified?.object.isReadonly() ?? true), @@ -186,10 +186,11 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor h(); } }), - }); - }, i => JSON.stringify([i.modified?.toString(), i.original?.toString()])); + }; + return store.add(RefCounted.createOfNonDisposable(result, multiDiffItemStore, this)); + }, i => JSON.stringify([i.modifiedUri?.toString(), i.originalUri?.toString()])); - const documents = observableValue[]>('documents', []); + const documents = observableValue[]>('documents', []); const updateDocuments = derived(async reader => { /** @description Update documents */ @@ -197,18 +198,13 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor const docs = await Promise.all(docsPromises); const newDocuments = docs.filter(isDefined); documents.set(newDocuments, undefined); - - garbage.clear(); // Only dispose text models after the documents have been updated }); const a = recomputeInitiallyAndOnChange(updateDocuments); await updateDocuments.get(); const result: IMultiDiffEditorModel & IDisposable = { - dispose: () => { - a.dispose(); - garbage.dispose(); - }, + dispose: () => a.dispose(), documents: new ValueWithChangeEventFromObservable(documents), contextKeys: source.source?.contextKeys, }; @@ -238,9 +234,16 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } public readonly resources = derived(this, reader => this._resolvedSource.cachedPromiseResult.read(reader)?.data?.resources.read(reader)); + + private readonly textFileServiceOnDidChange = new FastEventDispatcher( + this._textFileService.files.onDidChangeDirty, + item => item.resource.toString(), + uri => uri.toString() + ); + private readonly _isDirtyObservables = mapObservableArrayCached(this, this.resources.map(r => r ?? []), res => { - const isModifiedDirty = res.modified ? isUriDirty(this._textFileService, res.modified) : constObservable(false); - const isOriginalDirty = res.original ? isUriDirty(this._textFileService, res.original) : constObservable(false); + const isModifiedDirty = res.modifiedUri ? isUriDirty(this.textFileServiceOnDidChange, this._textFileService, res.modifiedUri) : constObservable(false); + const isOriginalDirty = res.originalUri ? isUriDirty(this.textFileServiceOnDidChange, this._textFileService, res.originalUri) : constObservable(false); return derived(reader => /** @description modifiedDirty||originalDirty */ isModifiedDirty.read(reader) || isOriginalDirty.read(reader)); }, i => i.getKey()); private readonly _isDirtyObservable = derived(this, reader => this._isDirtyObservables.read(reader).some(isDirty => isDirty.read(reader))) @@ -291,11 +294,71 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }; } -function isUriDirty(textFileService: ITextFileService, uri: URI) { - return observableFromEvent( - Event.filter(textFileService.files.onDidChangeDirty, e => e.resource.toString() === uri.toString()), - () => textFileService.isDirty(uri) - ); +export interface IDocumentDiffItemWithMultiDiffEditorItem extends IDocumentDiffItem { + multiDiffEditorItem: MultiDiffEditorItem; +} + +/** + * Uses a map to efficiently dispatch events to listeners that are interested in a specific key. +*/ +class FastEventDispatcher { + private _count = 0; + private readonly _buckets = new Map void>>(); + + private _eventSubscription: IDisposable | undefined; + + constructor( + private readonly _event: Event, + private readonly _getEventArgsKey: (item: T) => string, + private readonly _keyToString: (key: TKey) => string, + ) { + } + + public filteredEvent(filter: TKey): (listener: (e: T) => any) => IDisposable { + return listener => { + const key = this._keyToString(filter); + let bucket = this._buckets.get(key); + if (!bucket) { + bucket = new Set(); + this._buckets.set(key, bucket); + } + bucket.add(listener); + + this._count++; + if (this._count === 1) { + this._eventSubscription = this._event(this._handleEventChange); + } + + return { + dispose: () => { + bucket!.delete(listener); + if (bucket!.size === 0) { + this._buckets.delete(key); + } + this._count--; + + if (this._count === 0) { + this._eventSubscription?.dispose(); + this._eventSubscription = undefined; + } + } + }; + }; + } + + private readonly _handleEventChange = (e: T) => { + const key = this._getEventArgsKey(e); + const bucket = this._buckets.get(key); + if (bucket) { + for (const listener of bucket) { + listener(e); + } + } + }; +} + +function isUriDirty(onDidChangeDirty: FastEventDispatcher, textFileService: ITextFileService, uri: URI) { + return observableFromEvent(onDidChangeDirty.filteredEvent(uri), () => textFileService.isDirty(uri)); } function getReadonlyConfiguration(isReadonly: boolean | IMarkdownString | undefined): { readOnly: boolean; readOnlyMessage: IMarkdownString | undefined } { @@ -361,6 +424,7 @@ interface ISerializedMultiDiffEditorInput { resources: { originalUri: string | undefined; modifiedUri: string | undefined; + goToFileUri: string | undefined; }[] | undefined; } diff --git a/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts b/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts index 43f2f3eb..3fd5aa14 100644 --- a/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts +++ b/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts @@ -33,16 +33,18 @@ export interface IResolvedMultiDiffSource { export class MultiDiffEditorItem { constructor( - readonly original: URI | undefined, - readonly modified: URI | undefined, + readonly originalUri: URI | undefined, + readonly modifiedUri: URI | undefined, + readonly goToFileUri: URI | undefined, + readonly contextKeys?: Record ) { - if (!original && !modified) { + if (!originalUri && !modifiedUri) { throw new BugIndicatingError('Invalid arguments'); } } getKey(): string { - return JSON.stringify([this.modified?.toString(), this.original?.toString()]); + return JSON.stringify([this.modifiedUri?.toString(), this.originalUri?.toString()]); } } diff --git a/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts b/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts index a53d4d6c..43ff34c3 100644 --- a/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts +++ b/patched-vscode/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts @@ -61,11 +61,11 @@ export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver { async resolveDiffSource(uri: URI): Promise { const { repositoryUri, groupId } = ScmMultiDiffSourceResolver.parseUri(uri)!; - const repository = await waitForState(observableFromEvent( + const repository = await waitForState(observableFromEvent(this, this._scmService.onDidAddRepository, () => [...this._scmService.repositories].find(r => r.provider.rootUri?.toString() === repositoryUri.toString())) ); - const group = await waitForState(observableFromEvent( + const group = await waitForState(observableFromEvent(this, repository.provider.onDidChangeResourceGroups, () => repository.provider.groups.find(g => g.id === groupId) )); @@ -76,7 +76,7 @@ export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver { class ScmResolvedMultiDiffSource implements IResolvedMultiDiffSource { private readonly _resources = observableFromEvent( this._group.onDidChangeResources, - () => /** @description resources */ this._group.resources.map(e => new MultiDiffEditorItem(e.multiDiffEditorOriginalUri, e.multiDiffEditorModifiedUri)) + () => /** @description resources */ this._group.resources.map(e => new MultiDiffEditorItem(e.multiDiffEditorOriginalUri, e.multiDiffEditorModifiedUri, e.sourceUri)) ); readonly resources = new ValueWithChangeEventFromObservable(this._resources); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts index ab728db5..721b711f 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts @@ -161,13 +161,13 @@ class ExecutionStateCellStatusBarItem extends Disposable { const state = runState?.state; const { lastRunSuccess } = internalMetadata; if (!state && lastRunSuccess) { - return [{ + return [{ text: `$(${successStateIcon.id})`, color: themeColorFromId(cellStatusIconSuccess), tooltip: localize('notebook.cell.status.success', "Success"), alignment: CellStatusbarAlignment.Left, priority: Number.MAX_SAFE_INTEGER - }]; + } satisfies INotebookCellStatusBarItem]; } else if (!state && lastRunSuccess === false) { return [{ text: `$(${errorStateIcon.id})`, @@ -177,22 +177,22 @@ class ExecutionStateCellStatusBarItem extends Disposable { priority: Number.MAX_SAFE_INTEGER }]; } else if (state === NotebookCellExecutionState.Pending || state === NotebookCellExecutionState.Unconfirmed) { - return [{ + return [{ text: `$(${pendingStateIcon.id})`, tooltip: localize('notebook.cell.status.pending', "Pending"), alignment: CellStatusbarAlignment.Left, priority: Number.MAX_SAFE_INTEGER - }]; + } satisfies INotebookCellStatusBarItem]; } else if (state === NotebookCellExecutionState.Executing) { const icon = runState?.didPause ? executingStateIcon : ThemeIcon.modify(executingStateIcon, 'spin'); - return [{ + return [{ text: `$(${icon.id})`, tooltip: localize('notebook.cell.status.executing', "Executing"), alignment: CellStatusbarAlignment.Left, priority: Number.MAX_SAFE_INTEGER - }]; + } satisfies INotebookCellStatusBarItem]; } return []; @@ -318,12 +318,12 @@ class TimerCellStatusBarItem extends Disposable { } - return { + return { text: formatCellDuration(duration, false), alignment: CellStatusbarAlignment.Left, priority: Number.MAX_SAFE_INTEGER - 5, tooltip - }; + } satisfies INotebookCellStatusBarItem; } override dispose() { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts index eeef10e6..205bce80 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts @@ -8,6 +8,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -32,7 +33,8 @@ export class EmptyCellEditorHintContribution extends EmptyTextEditorHintContribu @IInlineChatSessionService inlineChatSessionService: IInlineChatSessionService, @IChatAgentService chatAgentService: IChatAgentService, @ITelemetryService telemetryService: ITelemetryService, - @IProductService productService: IProductService + @IProductService productService: IProductService, + @IContextMenuService contextMenuService: IContextMenuService ) { super( editor, @@ -44,7 +46,8 @@ export class EmptyCellEditorHintContribution extends EmptyTextEditorHintContribu inlineChatSessionService, chatAgentService, telemetryService, - productService + productService, + contextMenuService ); const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 5ad7c674..ea176602 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -10,8 +10,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { CENTER_ACTIVE_CELL } from 'vs/workbench/contrib/notebook/browser/contrib/navigation/arrow'; import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { SELECT_NOTEBOOK_INDENTATION_ID } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; @@ -20,8 +19,9 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; +import { IEditorGroupsService, IEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { Event } from 'vs/base/common/event'; class ImplictKernelSelector implements IDisposable { @@ -179,8 +179,6 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(KernelStatus, LifecyclePhase.Restored); - export class ActiveCellStatus extends Disposable implements IWorkbenchContribution { private readonly _itemDisposables = this._register(new DisposableStore()); @@ -255,9 +253,7 @@ export class ActiveCellStatus extends Disposable implements IWorkbenchContributi } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ActiveCellStatus, LifecyclePhase.Restored); - -export class NotebookIndentationStatus extends Disposable implements IWorkbenchContribution { +export class NotebookIndentationStatus extends Disposable { private readonly _itemDisposables = this._register(new DisposableStore()); private readonly _accessor = this._register(new MutableDisposable()); @@ -339,4 +335,31 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC } } -registerWorkbenchContribution2(NotebookIndentationStatus.ID, NotebookIndentationStatus, WorkbenchPhase.AfterRestored); // TODO@Yoyokrazy -- unsure on the phase +export class NotebookEditorStatusContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'notebook.contrib.editorStatus'; + + constructor( + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + ) { + super(); + + for (const part of editorGroupService.parts) { + this.createNotebookStatus(part); + } + + this._register(editorGroupService.onDidCreateAuxiliaryEditorPart(part => this.createNotebookStatus(part))); + } + + private createNotebookStatus(part: IEditorPart): void { + const disposables = new DisposableStore(); + Event.once(part.onWillDispose)(() => disposables.dispose()); + + const scopedInstantiationService = this.editorGroupService.getScopedInstantiationService(part); + disposables.add(scopedInstantiationService.createInstance(KernelStatus)); + disposables.add(scopedInstantiationService.createInstance(ActiveCellStatus)); + disposables.add(scopedInstantiationService.createInstance(NotebookIndentationStatus)); + } +} + +registerWorkbenchContribution2(NotebookEditorStatusContribution.ID, NotebookEditorStatusContribution, WorkbenchPhase.AfterRestored); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findFilters.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findFilters.ts index 0901d295..cf981209 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findFilters.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findFilters.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; -import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { INotebookFindScope, NotebookFindScopeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export interface INotebookFindChangeEvent { markupInput?: boolean; markupPreview?: boolean; codeInput?: boolean; codeOutput?: boolean; - searchInRanges?: boolean; + findScope?: boolean; } export class NotebookFindFilters extends Disposable { @@ -70,31 +70,19 @@ export class NotebookFindFilters extends Disposable { } } - private _searchInRanges: boolean = false; + private _findScope: INotebookFindScope = { findScopeType: NotebookFindScopeType.None }; - get searchInRanges(): boolean { - return this._searchInRanges; + get findScope(): INotebookFindScope { + return this._findScope; } - set searchInRanges(value: boolean) { - if (this._searchInRanges !== value) { - this._searchInRanges = value; - this._onDidChange.fire({ searchInRanges: value }); + set findScope(value: INotebookFindScope) { + if (this._findScope !== value) { + this._findScope = value; + this._onDidChange.fire({ findScope: true }); } } - private _selectedRanges: ICellRange[] = []; - - get selectedRanges(): ICellRange[] { - return this._selectedRanges; - } - - set selectedRanges(value: ICellRange[]) { - if (this._selectedRanges !== value) { - this._selectedRanges = value; - this._onDidChange.fire({ searchInRanges: this._searchInRanges }); - } - } private readonly _initialMarkupInput: boolean; private readonly _initialMarkupPreview: boolean; @@ -106,8 +94,7 @@ export class NotebookFindFilters extends Disposable { markupPreview: boolean, codeInput: boolean, codeOutput: boolean, - searchInRanges: boolean, - selectedRanges: ICellRange[] + findScope: INotebookFindScope ) { super(); @@ -115,8 +102,7 @@ export class NotebookFindFilters extends Disposable { this._markupPreview = markupPreview; this._codeInput = codeInput; this._codeOutput = codeOutput; - this._searchInRanges = searchInRanges; - this._selectedRanges = selectedRanges; + this._findScope = findScope; this._initialMarkupInput = markupInput; this._initialMarkupPreview = markupPreview; @@ -125,7 +111,7 @@ export class NotebookFindFilters extends Disposable { } isModified(): boolean { - // do not include searchInRanges or selectedRanges in the check. This will incorrectly mark the filter icon as modified + // do not include findInSelection or either selectedRanges in the check. This will incorrectly mark the filter icon as modified return ( this._markupInput !== this._initialMarkupInput || this._markupPreview !== this._initialMarkupPreview @@ -139,7 +125,6 @@ export class NotebookFindFilters extends Disposable { this._markupPreview = v.markupPreview; this._codeInput = v.codeInput; this._codeOutput = v.codeOutput; - this._searchInRanges = v.searchInRanges; - this._selectedRanges = v.selectedRanges; + this._findScope = v.findScope; } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts index 6fa5a4fe..214de021 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts @@ -57,7 +57,6 @@ export class FindMatchDecorationModel extends Disposable { }); this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, [{ - ownerId: cell.handle, handle: cell.handle, options: { overviewRuler: { @@ -67,7 +66,7 @@ export class FindMatchDecorationModel extends Disposable { position: NotebookOverviewRulerLane.Center } } - } as INotebookDeltaDecoration]); + }]); return null; } @@ -80,7 +79,6 @@ export class FindMatchDecorationModel extends Disposable { this._currentMatchDecorations = { kind: 'output', index: index }; this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, [{ - ownerId: cell.handle, handle: cell.handle, options: { overviewRuler: { @@ -90,7 +88,7 @@ export class FindMatchDecorationModel extends Disposable { position: NotebookOverviewRulerLane.Center } } - } as INotebookDeltaDecoration]); + } satisfies INotebookDeltaDecoration]); return offset; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index de522b34..0ca2d422 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { findFirstIdxMonotonousOrArrLen } from 'vs/base/common/arraysFind'; import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async'; -import { INotebookEditor, CellEditState, CellFindMatchWithIndex, CellWebviewFindMatch, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch } from 'vs/editor/common/model'; import { PrefixSumComputer } from 'vs/editor/common/model/prefixSumComputer'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/browser/findState'; -import { CellKind, INotebookSearchOptions, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { findFirstIdxMonotonousOrArrLen } from 'vs/base/common/arraysFind'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters'; import { FindMatchDecorationModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel'; +import { CellEditState, CellFindMatchWithIndex, CellWebviewFindMatch, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CellKind, INotebookFindOptions, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class CellFindMatchModel implements CellFindMatchWithIndex { readonly cell: ICellViewModel; @@ -115,7 +115,7 @@ export class FindModel extends Disposable { } private _updateCellStates(e: FindReplaceStateChangedEvent) { - if (!this._state.filters?.markupInput || !this._state.filters?.markupPreview || !this._state.filters?.searchInRanges || !this._state.filters?.selectedRanges) { + if (!this._state.filters?.markupInput || !this._state.filters?.markupPreview || !this._state.filters?.findScope) { return; } @@ -127,7 +127,7 @@ export class FindModel extends Disposable { } // search markup sources first to decide if a markup cell should be in editing mode const wordSeparators = this._configurationService.inspect('editor.wordSeparators').value; - const options: INotebookSearchOptions = { + const options: INotebookFindOptions = { regex: this._state.isRegex, wholeWord: this._state.wholeWord, caseSensitive: this._state.matchCase, @@ -136,8 +136,7 @@ export class FindModel extends Disposable { includeCodeInput: false, includeMarkupPreview: false, includeOutput: false, - searchInRanges: this._state.filters?.searchInRanges, - selectedRanges: this._state.filters?.selectedRanges + findScope: this._state.filters?.findScope, }; const contentMatches = viewModel.find(this._state.searchString, options); @@ -476,7 +475,7 @@ export class FindModel extends Disposable { const val = this._state.searchString; const wordSeparators = this._configurationService.inspect('editor.wordSeparators').value; - const options: INotebookSearchOptions = { + const options: INotebookFindOptions = { regex: this._state.isRegex, wholeWord: this._state.wholeWord, caseSensitive: this._state.matchCase, @@ -485,8 +484,7 @@ export class FindModel extends Disposable { includeCodeInput: this._state.filters?.codeInput ?? true, includeMarkupPreview: !!this._state.filters?.markupPreview, includeOutput: !!this._state.filters?.codeOutput, - searchInRanges: this._state.filters?.searchInRanges, - selectedRanges: this._state.filters?.selectedRanges + findScope: this._state.filters?.findScope, }; ret = await this._notebookEditor.find(val, options, token); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/media/notebookFind.css b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/media/notebookFind.css index fe115e23..d61cc797 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/media/notebookFind.css +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/media/notebookFind.css @@ -19,3 +19,7 @@ padding: 0 2px; box-sizing: border-box; } + +.monaco-workbench .nb-findScope { + background-color: var(--vscode-editor-findRangeHighlightBackground); +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts index fd39a06a..fb3d93d9 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts @@ -10,6 +10,7 @@ import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { FindStartFocusAction, getSelectionSearchString, IFindStartOptions, StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/browser/findController'; @@ -19,13 +20,12 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IShowNotebookFindWidgetOptions, NotebookFindContrib } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget'; +import { INotebookCommandContext, NotebookMultiCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, NotebookFindScopeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INTERACTIVE_WINDOW_IS_ACTIVE_EDITOR, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { INotebookCommandContext, NotebookMultiCellAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; registerNotebookContribution(NotebookFindContrib.id, NotebookFindContrib); @@ -78,12 +78,7 @@ registerAction2(class extends NotebookMultiCellAction { } const controller = editor.getContribution(NotebookFindContrib.id); - - if (context.selectedCells.length > 1) { - controller.show(undefined, { searchInRanges: true, selectedRanges: editor.getSelections() }); - } else { - controller.show(undefined, { searchInRanges: false, selectedRanges: [] }); - } + controller.show(undefined, { findScope: { findScopeType: NotebookFindScopeType.None } }); } }); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 7b261f5d..8c4b59a6 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -3,55 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; +import 'vs/css!./notebookFindReplaceWidget'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; +import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Widget } from 'vs/base/browser/ui/widget'; +import { Action, ActionRunner, IAction, IActionRunner, Separator } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; +import { Codicon } from 'vs/base/common/codicons'; import { KeyCode } from 'vs/base/common/keyCodes'; -import 'vs/css!./notebookFindReplaceWidget'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isSafari } from 'vs/base/common/platform'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { Range } from 'vs/editor/common/core/range'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/browser/findState'; import { findNextMatchIcon, findPreviousMatchIcon, findReplaceAllIcon, findReplaceIcon, findSelectionIcon, SimpleButton } from 'vs/editor/contrib/find/browser/findWidget'; -import * as nls from 'vs/nls'; -import { ContextScopedReplaceInput, registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget'; +import { parseReplaceString, ReplacePattern } from 'vs/editor/contrib/find/browser/replacePattern'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { ContextScopedReplaceInput, registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { defaultInputBoxStyles, defaultProgressBarStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { asCssVariable, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ThemeIcon } from 'vs/base/common/themables'; -import { parseReplaceString, ReplacePattern } from 'vs/editor/contrib/find/browser/replacePattern'; -import { Codicon } from 'vs/base/common/codicons'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Action, ActionRunner, IAction, IActionRunner, Separator } from 'vs/base/common/actions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IMenu } from 'vs/platform/actions/common/actions'; -import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { filterIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters'; -import { isSafari } from 'vs/base/common/platform'; -import { ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; -import { INotebookDeltaDecoration, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { defaultInputBoxStyles, defaultProgressBarStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; -import { asCssVariable, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IShowNotebookFindWidgetOptions } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget'; +import { ICellModelDecorations, ICellModelDeltaDecorations, ICellViewModel, INotebookDeltaDecoration, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookFindScopeType, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous Match"); -// const NLS_FILTER_BTN_LABEL = nls.localize('label.findFilterButton', "Search in View"); const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next Match"); -const NLS_FIND_IN_CELL_SELECTION_BTN_LABEL = nls.localize('label.findInCellSelectionButton', "Find in Cell Selection"); +const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind', "Find in Selection"); const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace"); const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace"); @@ -66,7 +67,7 @@ const NOTEBOOK_FIND_IN_MARKUP_PREVIEW = nls.localize('notebook.find.filter.findI const NOTEBOOK_FIND_IN_CODE_INPUT = nls.localize('notebook.find.filter.findInCodeInput', "Code Cell Source"); const NOTEBOOK_FIND_IN_CODE_OUTPUT = nls.localize('notebook.find.filter.findInCodeOutput', "Code Cell Output"); -const NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH = 318; +const NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH = 419; const NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING = 4; class NotebookFindFilterActionViewItem extends DropdownMenuActionViewItem { constructor(readonly filters: NotebookFindFilters, action: IAction, options: IActionViewItemOptions, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService) { @@ -319,8 +320,8 @@ export abstract class SimpleFindReplaceWidget extends Widget { private _filters: NotebookFindFilters; private readonly inSelectionToggle: Toggle; - private searchInSelectionEnabled: boolean; - private selectionDecorationIds: string[] = []; + private cellSelectionDecorationIds: string[] = []; + private textSelectionDecorationIds: ICellModelDecorations[] = []; constructor( @IContextViewService private readonly _contextViewService: IContextViewService, @@ -341,7 +342,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { codeOutput: boolean; }>(NotebookSetting.findFilters) ?? { markupSource: true, markupPreview: true, codeSource: true, codeOutput: true }; - this._filters = new NotebookFindFilters(findFilters.markupSource, findFilters.markupPreview, findFilters.codeSource, findFilters.codeOutput, false, []); + this._filters = new NotebookFindFilters(findFilters.markupSource, findFilters.markupPreview, findFilters.codeSource, findFilters.codeOutput, { findScopeType: NotebookFindScopeType.None }); this._state.change({ filters: this._filters }, false); this._filters.onDidChange(() => { @@ -386,6 +387,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { null, this._contextViewService, { + // width:FIND_INPUT_AREA_WIDTH, label: NLS_FIND_INPUT_LABEL, placeholder: NLS_FIND_INPUT_PLACEHOLDER, validation: (value: string): InputBoxMessage | null => { @@ -462,22 +464,54 @@ export abstract class SimpleFindReplaceWidget extends Widget { this.inSelectionToggle = this._register(new Toggle({ icon: findSelectionIcon, - title: NLS_FIND_IN_CELL_SELECTION_BTN_LABEL, + title: NLS_TOGGLE_SELECTION_FIND_TITLE, isChecked: false, inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), })); + this.inSelectionToggle.domNode.style.display = 'inline'; this.inSelectionToggle.onChange(() => { const checked = this.inSelectionToggle.checked; - this._filters.searchInRanges = checked; if (checked) { - this._filters.selectedRanges = this._notebookEditor.getSelections(); - this.setCellSelectionDecorations(); + // selection logic: + // 1. if there are multiple cells, do that. + // 2. if there is only one cell, do the following: + // - if there is a multi-line range highlighted, textual in selection + // - if there is no range, cell in selection for that cell + + const cellSelection: ICellRange[] = this._notebookEditor.getSelections(); + const textSelection: Range[] = this._notebookEditor.getSelectionViewModels()[0].getSelections(); + + if (cellSelection.length > 1 || cellSelection.some(range => range.end - range.start > 1)) { + this._filters.findScope = { + findScopeType: NotebookFindScopeType.Cells, + selectedCellRanges: cellSelection + }; + this.setCellSelectionDecorations(); + + } else if (textSelection.length > 1 || textSelection.some(range => range.endLineNumber - range.startLineNumber >= 1)) { + this._filters.findScope = { + findScopeType: NotebookFindScopeType.Text, + selectedCellRanges: cellSelection, + selectedTextRanges: textSelection + }; + this.setTextSelectionDecorations(textSelection, this._notebookEditor.getSelectionViewModels()[0]); + + } else { + this._filters.findScope = { + findScopeType: NotebookFindScopeType.Cells, + selectedCellRanges: cellSelection + }; + this.setCellSelectionDecorations(); + } } else { - this._filters.selectedRanges = []; + this._filters.findScope = { + findScopeType: NotebookFindScopeType.None + }; this.clearCellSelectionDecorations(); + this.clearTextSelectionDecorations(); } }); @@ -496,22 +530,6 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._innerFindDomNode.appendChild(this.inSelectionToggle.domNode); this._innerFindDomNode.appendChild(closeBtn.domNode); - this.searchInSelectionEnabled = this._configurationService.getValue(NotebookSetting.findScope); - this.inSelectionToggle.domNode.style.display = this.searchInSelectionEnabled ? 'inline' : 'none'; - - this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(NotebookSetting.findScope)) { - this.searchInSelectionEnabled = this._configurationService.getValue(NotebookSetting.findScope); - if (this.searchInSelectionEnabled) { - this.inSelectionToggle.domNode.style.display = 'inline'; - } else { - this.inSelectionToggle.domNode.style.display = 'none'; - this.inSelectionToggle.checked = false; - this.clearCellSelectionDecorations(); - } - } - }); - // _domNode wraps _innerDomNode, ensuring that this._domNode.appendChild(this._innerFindDomNode); @@ -704,11 +722,37 @@ export abstract class SimpleFindReplaceWidget extends Widget { options: { className: 'nb-multiCellHighlight', outputClassName: 'nb-multiCellHighlight' } } satisfies INotebookDeltaDecoration); } - this.selectionDecorationIds = this._notebookEditor.deltaCellDecorations([], decorations); + this.cellSelectionDecorationIds = this._notebookEditor.deltaCellDecorations([], decorations); } private clearCellSelectionDecorations() { - this._notebookEditor.deltaCellDecorations(this.selectionDecorationIds, []); + this._notebookEditor.deltaCellDecorations(this.cellSelectionDecorationIds, []); + } + + private setTextSelectionDecorations(textRanges: Range[], cell: ICellViewModel) { + this._notebookEditor.changeModelDecorations(changeAccessor => { + const decorations: ICellModelDeltaDecorations[] = []; + for (const range of textRanges) { + decorations.push({ + ownerId: cell.handle, + decorations: [{ + range: range, + options: { + description: 'text search range for notebook search scope', + isWholeLine: true, + className: 'nb-findScope' + } + }] + }); + } + this.textSelectionDecorationIds = changeAccessor.deltaDecorations([], decorations); + }); + } + + private clearTextSelectionDecorations() { + this._notebookEditor.changeModelDecorations(changeAccessor => { + changeAccessor.deltaDecorations(this.textSelectionDecorationIds, []); + }); } protected _updateMatchesCount(): void { @@ -717,9 +761,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { override dispose() { super.dispose(); - if (this._domNode && this._domNode.parentElement) { - this._domNode.parentElement.removeChild(this._domNode); - } + this._domNode.remove(); } public getDomNode() { @@ -750,20 +792,11 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._findInput.focus(); } - public show(initialInput?: string, options?: { focus?: boolean; searchInRanges?: boolean; selectedRanges?: ICellRange[] }): void { + public show(initialInput?: string, options?: IShowNotebookFindWidgetOptions): void { if (initialInput) { this._findInput.setValue(initialInput); } - if (this.searchInSelectionEnabled && options?.searchInRanges !== undefined) { - this._filters.searchInRanges = options.searchInRanges; - this.inSelectionToggle.checked = options.searchInRanges; - if (options.searchInRanges && options.selectedRanges) { - this._filters.selectedRanges = options.selectedRanges; - this.setCellSelectionDecorations(); - } - } - this._isVisible = true; setTimeout(() => { @@ -812,7 +845,10 @@ export abstract class SimpleFindReplaceWidget extends Widget { public hide(): void { if (this._isVisible) { this.inSelectionToggle.checked = false; - this._notebookEditor.deltaCellDecorations(this.selectionDecorationIds, []); + this._notebookEditor.deltaCellDecorations(this.cellSelectionDecorationIds, []); + this._notebookEditor.changeModelDecorations(changeAccessor => { + changeAccessor.deltaDecorations(this.textSelectionDecorationIds, []); + }); this._domNode.classList.remove('visible-transition'); this._domNode.setAttribute('aria-hidden', 'true'); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index c4307db6..15954bc2 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -25,8 +25,8 @@ import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contr import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget'; import { CellEditState, ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookFindScope } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; const FIND_HIDE_TRANSITION = 'find-hide-transition'; const FIND_SHOW_TRANSITION = 'find-show-transition'; @@ -40,8 +40,7 @@ export interface IShowNotebookFindWidgetOptions { matchIndex?: number; focus?: boolean; searchStringSeededFrom?: { cell: ICellViewModel; range: Range }; - searchInRanges?: boolean; - selectedRanges?: ICellRange[]; + findScope?: INotebookFindScope; } export class NotebookFindContrib extends Disposable implements INotebookEditorContribution { @@ -348,9 +347,7 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi this._matchesCount.title = ''; // remove previous content - if (this._matchesCount.firstChild) { - this._matchesCount.removeChild(this._matchesCount.firstChild); - } + this._matchesCount.firstChild?.remove(); let label: string; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts new file mode 100644 index 00000000..1a2621a0 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor.ts @@ -0,0 +1,754 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Emitter, Event } from 'vs/base/common/event'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { EditorConfiguration } from 'vs/editor/browser/config/editorConfiguration'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection, SelectionDirection } from 'vs/editor/common/core/selection'; +import { IWordAtPosition, USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper'; +import { CommandExecutor, CursorsController } from 'vs/editor/common/cursor/cursor'; +import { DeleteOperations } from 'vs/editor/common/cursor/cursorDeleteOperations'; +import { CursorConfiguration, ICursorSimpleModel } from 'vs/editor/common/cursorCommon'; +import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { IModelDeltaDecoration, ITextModel, PositionAffinity } from 'vs/editor/common/model'; +import { indentOfLine } from 'vs/editor/common/model/textModel'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ICoordinatesConverter } from 'vs/editor/common/viewModel'; +import { ViewModelEventsCollector } from 'vs/editor/common/viewModelEventDispatcher'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IPastFutureElements, IUndoRedoElement, IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; +import { INotebookActionContext, NotebookAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { getNotebookEditorFromEditorPane, ICellViewModel, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; +import { NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; +import { registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions'; + +const NOTEBOOK_ADD_FIND_MATCH_TO_SELECTION_ID = 'notebook.addFindMatchToSelection'; + +enum NotebookMultiCursorState { + Idle, + Selecting, + Editing, +} + +interface TrackedMatch { + cellViewModel: ICellViewModel; + initialSelection: Selection; + wordSelections: Selection[]; + config: IEditorConfiguration; + decorationIds: string[]; + undoRedoHistory: IPastFutureElements; +} + +export const NOTEBOOK_MULTI_SELECTION_CONTEXT = { + IsNotebookMultiSelect: new RawContextKey('isNotebookMultiSelect', false), + NotebookMultiSelectState: new RawContextKey('notebookMultiSelectState', NotebookMultiCursorState.Idle), +}; + +export class NotebookMultiCursorController extends Disposable implements INotebookEditorContribution { + + static readonly id: string = 'notebook.multiCursorController'; + + private state: NotebookMultiCursorState = NotebookMultiCursorState.Idle; + + private word: string = ''; + private trackedMatches: TrackedMatch[] = []; + + private readonly _onDidChangeAnchorCell = this._register(new Emitter()); + readonly onDidChangeAnchorCell: Event = this._onDidChangeAnchorCell.event; + private anchorCell: [ICellViewModel, ICodeEditor] | undefined; + + private readonly anchorDisposables = this._register(new DisposableStore()); + private readonly cursorsDisposables = this._register(new DisposableStore()); + private cursorsControllers: ResourceMap = new ResourceMap(); + + private _nbIsMultiSelectSession = NOTEBOOK_MULTI_SELECTION_CONTEXT.IsNotebookMultiSelect.bindTo(this.contextKeyService); + private _nbMultiSelectState = NOTEBOOK_MULTI_SELECTION_CONTEXT.NotebookMultiSelectState.bindTo(this.contextKeyService); + + + constructor( + private readonly notebookEditor: INotebookEditor, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ITextModelService private readonly textModelService: ITextModelService, + @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUndoRedoService private readonly undoRedoService: IUndoRedoService, + ) { + super(); + + if (!this.configurationService.getValue('notebook.multiSelect.enabled')) { + return; + } + + this.anchorCell = this.notebookEditor.activeCellAndCodeEditor; + + // anchor cell will catch and relay all type, cut, paste events to the cursors controllers + // need to create new controllers when the anchor cell changes, then update their listeners + // ** cursor controllers need to happen first, because anchor listeners relay to them + this._register(this.onDidChangeAnchorCell(() => { + this.updateCursorsControllers(); + this.updateAnchorListeners(); + })); + } + + private updateCursorsControllers() { + this.cursorsDisposables.clear(); + this.trackedMatches.forEach(async match => { + const textModelRef = await this.textModelService.createModelReference(match.cellViewModel.uri); + const textModel = textModelRef.object.textEditorModel; + if (!textModel) { + return; + } + + const cursorSimpleModel = this.constructCursorSimpleModel(match.cellViewModel); + const converter = this.constructCoordinatesConverter(); + const editorConfig = match.config; + + const controller = this.cursorsDisposables.add(new CursorsController( + textModel, + cursorSimpleModel, + converter, + new CursorConfiguration(textModel.getLanguageId(), textModel.getOptions(), editorConfig, this.languageConfigurationService) + )); + controller.setSelections(new ViewModelEventsCollector(), undefined, match.wordSelections, CursorChangeReason.Explicit); + this.cursorsControllers.set(match.cellViewModel.uri, controller); + }); + } + + private constructCoordinatesConverter(): ICoordinatesConverter { + return { + convertViewPositionToModelPosition(viewPosition: Position): Position { + return viewPosition; + }, + convertViewRangeToModelRange(viewRange: Range): Range { + return viewRange; + }, + validateViewPosition(viewPosition: Position, expectedModelPosition: Position): Position { + return viewPosition; + }, + validateViewRange(viewRange: Range, expectedModelRange: Range): Range { + return viewRange; + }, + convertModelPositionToViewPosition(modelPosition: Position, affinity?: PositionAffinity, allowZeroLineNumber?: boolean, belowHiddenRanges?: boolean): Position { + return modelPosition; + }, + convertModelRangeToViewRange(modelRange: Range, affinity?: PositionAffinity): Range { + return modelRange; + }, + modelPositionIsVisible(modelPosition: Position): boolean { + return true; + }, + getModelLineViewLineCount(modelLineNumber: number): number { + return 1; + }, + getViewLineNumberOfModelPosition(modelLineNumber: number, modelColumn: number): number { + return modelLineNumber; + } + }; + } + + private constructCursorSimpleModel(cell: ICellViewModel): ICursorSimpleModel { + return { + getLineCount(): number { + return cell.textBuffer.getLineCount(); + }, + getLineContent(lineNumber: number): string { + return cell.textBuffer.getLineContent(lineNumber); + }, + getLineMinColumn(lineNumber: number): number { + return cell.textBuffer.getLineMinColumn(lineNumber); + }, + getLineMaxColumn(lineNumber: number): number { + return cell.textBuffer.getLineMaxColumn(lineNumber); + }, + getLineFirstNonWhitespaceColumn(lineNumber: number): number { + return cell.textBuffer.getLineFirstNonWhitespaceColumn(lineNumber); + }, + getLineLastNonWhitespaceColumn(lineNumber: number): number { + return cell.textBuffer.getLineLastNonWhitespaceColumn(lineNumber); + }, + normalizePosition(position: Position, affinity: PositionAffinity): Position { + return position; + }, + getLineIndentColumn(lineNumber: number): number { + return indentOfLine(cell.textBuffer.getLineContent(lineNumber)) + 1; + } + }; + } + + private updateAnchorListeners() { + this.anchorDisposables.clear(); + + if (!this.anchorCell) { + throw new Error('Anchor cell is undefined'); + } + + // typing + this.anchorDisposables.add(this.anchorCell[1].onWillType((input) => { + const collector = new ViewModelEventsCollector(); + this.trackedMatches.forEach(match => { + const controller = this.cursorsControllers.get(match.cellViewModel.uri); + if (!controller) { + // should not happen + return; + } + if (match.cellViewModel.handle !== this.anchorCell?.[0].handle) { // don't relay to active cell, already has a controller for typing + controller.type(collector, input, 'keyboard'); + } + }); + })); + + this.anchorDisposables.add(this.anchorCell[1].onDidType(() => { + this.state = NotebookMultiCursorState.Editing; // typing will continue to work as normal across ranges, just preps for another cmd+d + this._nbMultiSelectState.set(NotebookMultiCursorState.Editing); + + const anchorController = this.cursorsControllers.get(this.anchorCell![0].uri); + if (!anchorController) { + return; + } + const activeSelections = this.notebookEditor.activeCodeEditor?.getSelections(); + if (!activeSelections) { + return; + } + + // need to keep anchor cursor controller in sync manually (for delete usage), since we don't relay type event to it + anchorController.setSelections(new ViewModelEventsCollector(), 'keyboard', activeSelections, CursorChangeReason.Explicit); + + this.trackedMatches.forEach(match => { + const controller = this.cursorsControllers.get(match.cellViewModel.uri); + if (!controller) { + return; + } + + // this is used upon exiting the multicursor session to set the selections back to the correct cursor state + match.initialSelection = controller.getSelection(); + // clear tracked selection data as it is invalid once typing begins + match.wordSelections = []; + }); + + this.updateLazyDecorations(); + })); + + // exit mode + this.anchorDisposables.add(this.anchorCell[1].onDidChangeCursorSelection((e) => { + if (e.source === 'mouse' || e.source === 'deleteRight') { + this.resetToIdleState(); + } + })); + + this.anchorDisposables.add(this.anchorCell[1].onDidBlurEditorWidget(() => { + if (this.state === NotebookMultiCursorState.Selecting || this.state === NotebookMultiCursorState.Editing) { + this.resetToIdleState(); + } + })); + } + + private updateFinalUndoRedo() { + const anchorCellModel = this.anchorCell?.[1].getModel(); + if (!anchorCellModel) { + // should not happen + return; + } + + const newElementsMap: ResourceMap = new ResourceMap(); + const resources: URI[] = []; + + this.trackedMatches.forEach(trackedMatch => { + const undoRedoState = trackedMatch.undoRedoHistory; + if (!undoRedoState) { + return; + } + + resources.push(trackedMatch.cellViewModel.uri); + + const currentPastElements = this.undoRedoService.getElements(trackedMatch.cellViewModel.uri).past.slice(); + const oldPastElements = trackedMatch.undoRedoHistory.past.slice(); + const newElements = currentPastElements.slice(oldPastElements.length); + if (newElements.length === 0) { + return; + } + + newElementsMap.set(trackedMatch.cellViewModel.uri, newElements); + + this.undoRedoService.removeElements(trackedMatch.cellViewModel.uri); + oldPastElements.forEach(element => { + this.undoRedoService.pushElement(element); + }); + }); + + this.undoRedoService.pushElement({ + type: UndoRedoElementType.Workspace, + resources: resources, + label: 'Multi Cursor Edit', + code: 'multiCursorEdit', + confirmBeforeUndo: false, + undo: async () => { + newElementsMap.forEach(async value => { + value.reverse().forEach(async element => { + await element.undo(); + }); + }); + }, + redo: async () => { + newElementsMap.forEach(async value => { + value.forEach(async element => { + await element.redo(); + }); + }); + } + }); + } + + public resetToIdleState() { + this.state = NotebookMultiCursorState.Idle; + this._nbMultiSelectState.set(NotebookMultiCursorState.Idle); + this._nbIsMultiSelectSession.set(false); + this.updateFinalUndoRedo(); + + this.trackedMatches.forEach(match => { + this.clearDecorations(match); + match.cellViewModel.setSelections([match.initialSelection]); // correct cursor placement upon exiting cmd-d session + }); + + this.anchorDisposables.clear(); + this.cursorsDisposables.clear(); + this.cursorsControllers.clear(); + this.trackedMatches = []; + } + + public async findAndTrackNextSelection(cell: ICellViewModel): Promise { + if (this.state === NotebookMultiCursorState.Idle) { // move cursor to end of the symbol + track it, transition to selecting state + const textModel = cell.textModel; + if (!textModel) { + return; + } + + const inputSelection = cell.getSelections()[0]; + const word = this.getWord(inputSelection, textModel); + if (!word) { + return; + } + this.word = word.word; + + const newSelection = new Selection( + inputSelection.startLineNumber, + word.startColumn, + inputSelection.startLineNumber, + word.endColumn + ); + cell.setSelections([newSelection]); + + this.anchorCell = this.notebookEditor.activeCellAndCodeEditor; + if (!this.anchorCell || this.anchorCell[0].handle !== cell.handle) { + throw new Error('Active cell is not the same as the cell passed as context'); + } + if (!(this.anchorCell[1] instanceof CodeEditorWidget)) { + throw new Error('Active cell is not an instance of CodeEditorWidget'); + } + + textModel.pushStackElement(); + + this.trackedMatches = []; + const editorConfig = this.constructCellEditorOptions(this.anchorCell[0]); + const newMatch: TrackedMatch = { + cellViewModel: cell, + initialSelection: inputSelection, + wordSelections: [newSelection], + config: editorConfig, // cache this in the match so we can create new cursors controllers with the correct language config + decorationIds: [], + undoRedoHistory: this.undoRedoService.getElements(cell.uri) + }; + this.trackedMatches.push(newMatch); + + this.initializeMultiSelectDecorations(newMatch); + this._nbIsMultiSelectSession.set(true); + this.state = NotebookMultiCursorState.Selecting; + this._nbMultiSelectState.set(NotebookMultiCursorState.Selecting); + this._onDidChangeAnchorCell.fire(); + + } else if (this.state === NotebookMultiCursorState.Selecting) { // use the word we stored from idle state transition to find next match, track it + const notebookTextModel = this.notebookEditor.textModel; + if (!notebookTextModel) { + return; + } + + const index = this.notebookEditor.getCellIndex(cell); + if (index === undefined) { + return; + } + + const findResult = notebookTextModel.findNextMatch( + this.word, + { cellIndex: index, position: cell.getSelections()[cell.getSelections().length - 1].getEndPosition() }, + false, + true, + USUAL_WORD_SEPARATORS //! might want to get these from the editor config + ); + if (!findResult) { + return; //todo: some sort of message to the user alerting them that there are no more matches? editor does not do this + } + + const resultCellViewModel = this.notebookEditor.getCellByHandle(findResult.cell.handle); + if (!resultCellViewModel) { + return; + } + + let newMatch: TrackedMatch; + if (findResult.cell.handle !== cell.handle) { // result is in a different cell, move focus there and apply selection, then update anchor + await this.notebookEditor.revealRangeInViewAsync(resultCellViewModel, findResult.match.range); + this.notebookEditor.focusNotebookCell(resultCellViewModel, 'editor'); + + const initialSelection = resultCellViewModel.getSelections()[0]; + const newSelection = Selection.fromRange(findResult.match.range, SelectionDirection.LTR); + resultCellViewModel.setSelections([newSelection]); + + this.anchorCell = this.notebookEditor.activeCellAndCodeEditor; + if (!this.anchorCell || !(this.anchorCell[1] instanceof CodeEditorWidget)) { + throw new Error('Active cell is not an instance of CodeEditorWidget'); + } + + const textModel = await resultCellViewModel.resolveTextModel(); + textModel.pushStackElement(); + + newMatch = { + cellViewModel: resultCellViewModel, + initialSelection: initialSelection, + wordSelections: [newSelection], + config: this.constructCellEditorOptions(this.anchorCell[0]), + decorationIds: [], + undoRedoHistory: this.undoRedoService.getElements(resultCellViewModel.uri) + } satisfies TrackedMatch; + this.trackedMatches.push(newMatch); + + this._onDidChangeAnchorCell.fire(); + + } else { // match is in the same cell, find tracked entry, update and set selections + newMatch = this.trackedMatches.find(match => match.cellViewModel.handle === findResult.cell.handle)!; + newMatch.wordSelections.push(Selection.fromRange(findResult.match.range, SelectionDirection.LTR)); + resultCellViewModel.setSelections(newMatch.wordSelections); + } + + this.initializeMultiSelectDecorations(newMatch); + } + } + + public async deleteLeft(): Promise { + this.trackedMatches.forEach(match => { + const controller = this.cursorsControllers.get(match.cellViewModel.uri); + if (!controller) { + // should not happen + return; + } + + const [, commands] = DeleteOperations.deleteLeft( + controller.getPrevEditOperationType(), + controller.context.cursorConfig, + controller.context.model, + controller.getSelections(), + controller.getAutoClosedCharacters(), + ); + + const delSelections = CommandExecutor.executeCommands(controller.context.model, controller.getSelections(), commands); + if (!delSelections) { + return; + } + controller.setSelections(new ViewModelEventsCollector(), undefined, delSelections, CursorChangeReason.Explicit); + }); + } + + async undo() { + const models: ITextModel[] = []; + for (const match of this.trackedMatches) { + const model = await match.cellViewModel.resolveTextModel(); + if (model) { + models.push(model); + } + } + + await Promise.all(models.map(model => model.undo())); + } + + async redo() { + const models: ITextModel[] = []; + for (const match of this.trackedMatches) { + const model = await match.cellViewModel.resolveTextModel(); + if (model) { + models.push(model); + } + } + + await Promise.all(models.map(model => model.redo())); + } + + private constructCellEditorOptions(cell: ICellViewModel): EditorConfiguration { + const cellEditorOptions = new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(cell.language), this.notebookEditor.notebookOptions, this.configurationService); + const options = cellEditorOptions.getUpdatedValue(cell.internalMetadata, cell.uri); + return new EditorConfiguration(false, MenuId.EditorContent, options, null, this.accessibilityService); + } + + /** + * Updates the multicursor selection decorations for a specific matched cell + * + * @param match -- match object containing the viewmodel + selections + */ + private initializeMultiSelectDecorations(match: TrackedMatch) { + const decorations: IModelDeltaDecoration[] = []; + + match.wordSelections.forEach(selection => { + decorations.push({ + range: selection, + options: { + description: '', + className: 'nb-multicursor-selection', + } + }); + }); + + match.decorationIds = match.cellViewModel.deltaModelDecorations( + match.decorationIds, + decorations + ); + } + + private updateLazyDecorations() { + // for every tracked match that is not in the visible range, dispose of their decorations and update them based off the cursorcontroller + this.trackedMatches.forEach(match => { + const cellIndex = this.notebookEditor.getCellIndex(match.cellViewModel); + if (cellIndex === undefined) { + return; + } + + const controller = this.cursorsControllers.get(match.cellViewModel.uri); + if (!controller) { + // should not happen + return; + } + const selections = controller.getSelections(); + + + const newDecorations = selections?.map(selection => { + return { + range: selection, + options: { + description: '', + className: 'nb-multicursor-selection', + } + }; + }); + + match.decorationIds = match.cellViewModel.deltaModelDecorations( + match.decorationIds, + newDecorations ?? [] + ); + }); + } + + private clearDecorations(match: TrackedMatch) { + match.decorationIds = match.cellViewModel.deltaModelDecorations( + match.decorationIds, + [] + ); + } + + private getWord(selection: Selection, model: ITextModel): IWordAtPosition | null { + const lineNumber = selection.startLineNumber; + const startColumn = selection.startColumn; + + if (model.isDisposed()) { + return null; + } + + return model.getWordAtPosition({ + lineNumber: lineNumber, + column: startColumn + }); + } + + override dispose(): void { + super.dispose(); + this.anchorDisposables.dispose(); + this.cursorsDisposables.dispose(); + + this.trackedMatches.forEach(match => { + this.clearDecorations(match); + }); + this.trackedMatches = []; + } + +} + +class NotebookAddMatchToMultiSelectionAction extends NotebookAction { + constructor() { + super({ + id: NOTEBOOK_ADD_FIND_MATCH_TO_SELECTION_ID, + title: localize('addFindMatchToSelection', "Add Find Match to Selection"), + keybinding: { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('config.notebook.multiSelect.enabled', true), + NOTEBOOK_IS_ACTIVE_EDITOR, + NOTEBOOK_CELL_EDITOR_FOCUSED, + ), + primary: KeyMod.CtrlCmd | KeyCode.KeyD, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + override async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + if (!context.cell) { + return; + } + + const controller = editor.getContribution(NotebookMultiCursorController.id); + controller.findAndTrackNextSelection(context.cell); + } +} + +class NotebookExitMultiSelectionAction extends NotebookAction { + constructor() { + super({ + id: 'noteMultiCursor.exit', + title: localize('exitMultiSelection', "Exit Multi Cursor Mode"), + keybinding: { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('config.notebook.multiSelect.enabled', true), + NOTEBOOK_IS_ACTIVE_EDITOR, + NOTEBOOK_MULTI_SELECTION_CONTEXT.IsNotebookMultiSelect, + ), + primary: KeyCode.Escape, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + override async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution(NotebookMultiCursorController.id); + controller.resetToIdleState(); + } +} + +class NotebookDeleteLeftMultiSelectionAction extends NotebookAction { + constructor() { + super({ + id: 'noteMultiCursor.deleteLeft', + title: localize('deleteLeftMultiSelection', "Delete Left"), + keybinding: { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('config.notebook.multiSelect.enabled', true), + NOTEBOOK_IS_ACTIVE_EDITOR, + NOTEBOOK_MULTI_SELECTION_CONTEXT.IsNotebookMultiSelect, + ContextKeyExpr.or( + NOTEBOOK_MULTI_SELECTION_CONTEXT.NotebookMultiSelectState.isEqualTo(NotebookMultiCursorState.Selecting), + NOTEBOOK_MULTI_SELECTION_CONTEXT.NotebookMultiSelectState.isEqualTo(NotebookMultiCursorState.Editing) + ) + ), + primary: KeyCode.Backspace, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + override async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution(NotebookMultiCursorController.id); + controller.deleteLeft(); + } +} + +class NotebookMultiCursorUndoRedoContribution extends Disposable { + + static readonly ID = 'workbench.contrib.notebook.multiCursorUndoRedo'; + + constructor(@IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService) { + super(); + + if (!this.configurationService.getValue('notebook.multiSelect.enabled')) { + return; + } + + const PRIORITY = 10005; + this._register(UndoCommand.addImplementation(PRIORITY, 'notebook-multicursor-undo-redo', () => { + const editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + if (!editor) { + return false; + } + + if (!editor.hasModel()) { + return false; + } + + const controller = editor.getContribution(NotebookMultiCursorController.id); + + return controller.undo(); + }, ContextKeyExpr.and( + ContextKeyExpr.equals('config.notebook.multiSelect.enabled', true), + NOTEBOOK_IS_ACTIVE_EDITOR, + NOTEBOOK_MULTI_SELECTION_CONTEXT.IsNotebookMultiSelect, + ))); + + this._register(RedoCommand.addImplementation(PRIORITY, 'notebook-multicursor-undo-redo', () => { + const editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + if (!editor) { + return false; + } + + if (!editor.hasModel()) { + return false; + } + + const controller = editor.getContribution(NotebookMultiCursorController.id); + return controller.redo(); + }, ContextKeyExpr.and( + ContextKeyExpr.equals('config.notebook.multiSelect.enabled', true), + NOTEBOOK_IS_ACTIVE_EDITOR, + NOTEBOOK_MULTI_SELECTION_CONTEXT.IsNotebookMultiSelect, + ))); + } +} + +registerNotebookContribution(NotebookMultiCursorController.id, NotebookMultiCursorController); +registerAction2(NotebookAddMatchToMultiSelectionAction); +registerAction2(NotebookExitMultiSelectionAction); +registerAction2(NotebookDeleteLeftMultiSelectionAction); +registerWorkbenchContribution2(NotebookMultiCursorUndoRedoContribution.ID, NotebookMultiCursorUndoRedoContribution, WorkbenchPhase.BlockRestore); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index e6656eaa..203839df 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -22,7 +22,7 @@ import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from 'vs/workbench/contrib/not import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_CELL_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; const NOTEBOOK_FOCUS_BOTTOM = 'notebook.focusBottom'; @@ -104,10 +104,10 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction { weight: KeybindingWeight.WorkbenchContrib }, { - when: NOTEBOOK_EDITOR_FOCUSED, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, }, - weight: KeybindingWeight.WorkbenchContrib + when: ContextKeyExpr.and(NOTEBOOK_CELL_EDITOR_FOCUSED, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + primary: KeyMod.CtrlCmd | KeyCode.PageDown, + mac: { primary: KeyMod.WinCtrl | KeyCode.PageUp, }, + weight: KeybindingWeight.WorkbenchContrib + 1 }, ] }); @@ -180,10 +180,10 @@ registerAction2(class FocusPreviousCellAction extends NotebookCellAction { weight: KeybindingWeight.WorkbenchContrib, // markdown keybinding, focus on list: higher weight to override list.focusDown }, { - when: NOTEBOOK_EDITOR_FOCUSED, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp }, - weight: KeybindingWeight.WorkbenchContrib + when: ContextKeyExpr.and(NOTEBOOK_CELL_EDITOR_FOCUSED, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + primary: KeyMod.CtrlCmd | KeyCode.PageUp, + mac: { primary: KeyMod.WinCtrl | KeyCode.PageUp, }, + weight: KeybindingWeight.WorkbenchContrib + 1 }, ], }); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts index 01c0e2b2..3992e68a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree.ts @@ -66,10 +66,9 @@ export class NotebookVariableRenderer implements ITreeRenderer e.anchor, getActions: () => actions diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 1a0f19d4..c9d3c97a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -29,8 +29,8 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { IEditorPane } from 'vs/workbench/common/editor'; import { CellFoldingState, CellRevealType, ICellModelDecorations, ICellModelDeltaDecorations, ICellViewModel, INotebookEditor, INotebookEditorOptions, INotebookEditorPane, INotebookViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; -import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; -import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookCellOutlineDataSource, NotebookCellOutlineDataSource } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource'; +import { CellKind, NotebookCellsChangeType, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IBreadcrumbsDataSource, IOutline, IOutlineComparator, IOutlineCreator, IOutlineListConfig, IOutlineService, IQuickPickDataSource, IQuickPickOutlineElement, OutlineChangeEvent, OutlineConfigCollapseItemsValues, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; @@ -46,12 +46,14 @@ import { MenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/pla import { IAction } from 'vs/base/common/actions'; import { NotebookSectionArgs } from 'vs/workbench/contrib/notebook/browser/controller/sectionActions'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; -import { disposableTimeout } from 'vs/base/common/async'; +import { Delayer, disposableTimeout } from 'vs/base/common/async'; import { IOutlinePane } from 'vs/workbench/contrib/outline/browser/outline'; import { Codicon } from 'vs/base/common/codicons'; import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { NotebookOutlineConstants } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; -import { INotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory'; +import { INotebookCellOutlineDataSourceFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory'; +import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; class NotebookOutlineTemplate { @@ -286,33 +288,44 @@ class NotebookOutlineVirtualDelegate implements IListVirtualDelegate { + private readonly _disposables = new DisposableStore(); + + private gotoShowCodeCellSymbols: boolean; + constructor( - private _getEntries: () => OutlineEntry[], + private readonly notebookCellOutlineDataSourceRef: IReference | undefined, @IConfigurationService private readonly _configurationService: IConfigurationService, @IThemeService private readonly _themeService: IThemeService - ) { } + ) { + this.gotoShowCodeCellSymbols = this._configurationService.getValue(NotebookSetting.gotoSymbolsAllSymbols); + + this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(NotebookSetting.gotoSymbolsAllSymbols)) { + this.gotoShowCodeCellSymbols = this._configurationService.getValue(NotebookSetting.gotoSymbolsAllSymbols); + } + })); + } getQuickPickElements(): IQuickPickOutlineElement[] { const bucket: OutlineEntry[] = []; - for (const entry of this._getEntries()) { + for (const entry of this.notebookCellOutlineDataSourceRef?.object?.entries ?? []) { entry.asFlatList(bucket); } const result: IQuickPickOutlineElement[] = []; const { hasFileIcons } = this._themeService.getFileIconTheme(); - const showSymbols = this._configurationService.getValue(NotebookSetting.gotoSymbolsAllSymbols); const isSymbol = (element: OutlineEntry) => !!element.symbolKind; const isCodeCell = (element: OutlineEntry) => (element.cell.cellKind === CellKind.Code && element.level === NotebookOutlineConstants.NonHeaderOutlineLevel); // code cell entries are exactly level 7 by this constant for (let i = 0; i < bucket.length; i++) { const element = bucket[i]; const nextElement = bucket[i + 1]; // can be undefined - if (!showSymbols + if (!this.gotoShowCodeCellSymbols && isSymbol(element)) { continue; } - if (showSymbols + if (this.gotoShowCodeCellSymbols && isCodeCell(element) && nextElement && isSymbol(nextElement)) { continue; @@ -330,32 +343,98 @@ export class NotebookQuickPickProvider implements IQuickPickDataSource { + + private readonly _disposables = new DisposableStore(); + + private showCodeCells: boolean; + private showCodeCellSymbols: boolean; + private showMarkdownHeadersOnly: boolean; + constructor( - private _getEntries: () => OutlineEntry[], + private readonly outlineDataSourceRef: IReference | undefined, @IConfigurationService private readonly _configurationService: IConfigurationService, - ) { } + ) { + this.showCodeCells = this._configurationService.getValue(NotebookSetting.outlineShowCodeCells); + this.showCodeCellSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); + this.showMarkdownHeadersOnly = this._configurationService.getValue(NotebookSetting.outlineShowMarkdownHeadersOnly); - *getChildren(element: NotebookCellOutline | OutlineEntry): Iterable { - const showCodeCells = this._configurationService.getValue(NotebookSetting.outlineShowCodeCells); - const showCodeCellSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); - const showMarkdownHeadersOnly = this._configurationService.getValue(NotebookSetting.outlineShowMarkdownHeadersOnly); + this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(NotebookSetting.outlineShowCodeCells)) { + this.showCodeCells = this._configurationService.getValue(NotebookSetting.outlineShowCodeCells); + } + if (e.affectsConfiguration(NotebookSetting.outlineShowCodeCellSymbols)) { + this.showCodeCellSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); + } + if (e.affectsConfiguration(NotebookSetting.outlineShowMarkdownHeadersOnly)) { + this.showMarkdownHeadersOnly = this._configurationService.getValue(NotebookSetting.outlineShowMarkdownHeadersOnly); + } + })); + } + + public getActiveEntry(): OutlineEntry | undefined { + const newActive = this.outlineDataSourceRef?.object?.activeElement; + if (!newActive) { + return undefined; + } + + if (!this.filterEntry(newActive)) { + return newActive; + } + + // find a valid parent + let parent = newActive.parent; + while (parent) { + if (this.filterEntry(parent)) { + parent = parent.parent; + } else { + return parent; + } + } + + // no valid parent found, return undefined + return undefined; + } + + /** + * Checks if the given outline entry should be filtered out of the outlinePane + * + * @param entry the OutlineEntry to check + * @returns true if the entry should be filtered out of the outlinePane + */ + private filterEntry(entry: OutlineEntry): boolean { + // if any are true, return true, this entry should NOT be included in the outline + if ( + (this.showMarkdownHeadersOnly && entry.cell.cellKind === CellKind.Markup && entry.level === NotebookOutlineConstants.NonHeaderOutlineLevel) || // show headers only + cell is mkdn + is level 7 (not header) + (!this.showCodeCells && entry.cell.cellKind === CellKind.Code) || // show code cells off + cell is code + (!this.showCodeCellSymbols && entry.cell.cellKind === CellKind.Code && entry.level > NotebookOutlineConstants.NonHeaderOutlineLevel) // show symbols off + cell is code + is level >7 (nb symbol levels) + ) { + return true; + } + + return false; + } + *getChildren(element: NotebookCellOutline | OutlineEntry): Iterable { const isOutline = element instanceof NotebookCellOutline; - const entries = isOutline ? this._getEntries() : element.children; + const entries = isOutline ? this.outlineDataSourceRef?.object?.entries ?? [] : element.children; for (const entry of entries) { if (entry.cell.cellKind === CellKind.Markup) { - if (!showMarkdownHeadersOnly) { + if (!this.showMarkdownHeadersOnly) { yield entry; } else if (entry.level < NotebookOutlineConstants.NonHeaderOutlineLevel) { yield entry; } - } else if (showCodeCells && entry.cell.cellKind === CellKind.Code) { - if (showCodeCellSymbols) { + } else if (this.showCodeCells && entry.cell.cellKind === CellKind.Code) { + if (this.showCodeCellSymbols) { yield entry; } else if (entry.level === NotebookOutlineConstants.NonHeaderOutlineLevel) { yield entry; @@ -363,26 +442,45 @@ export class NotebookOutlinePaneProvider implements IDataSource { + + private readonly _disposables = new DisposableStore(); + + private showCodeCells: boolean; + constructor( - private _getActiveElement: () => OutlineEntry | undefined, + private readonly outlineDataSourceRef: IReference | undefined, @IConfigurationService private readonly _configurationService: IConfigurationService, - ) { } + ) { + this.showCodeCells = this._configurationService.getValue(NotebookSetting.breadcrumbsShowCodeCells); + this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(NotebookSetting.breadcrumbsShowCodeCells)) { + this.showCodeCells = this._configurationService.getValue(NotebookSetting.breadcrumbsShowCodeCells); + } + })); + } getBreadcrumbElements(): readonly OutlineEntry[] { const result: OutlineEntry[] = []; - const showCodeCells = this._configurationService.getValue(NotebookSetting.breadcrumbsShowCodeCells); - let candidate = this._getActiveElement(); + let candidate = this.outlineDataSourceRef?.object?.activeElement; while (candidate) { - if (showCodeCells || candidate.cell.cellKind !== CellKind.Code) { + if (this.showCodeCells || candidate.cell.cellKind !== CellKind.Code) { result.unshift(candidate); } candidate = candidate.parent; } return result; } + + dispose(): void { + this._disposables.dispose(); + } } class NotebookComparator implements IOutlineComparator { @@ -401,64 +499,80 @@ class NotebookComparator implements IOutlineComparator { } export class NotebookCellOutline implements IOutline { + readonly outlineKind = 'notebookCells'; - private readonly _dispoables = new DisposableStore(); + private readonly _disposables = new DisposableStore(); + private readonly _modelDisposables = new DisposableStore(); + private readonly _dataSourceDisposables = new DisposableStore(); private readonly _onDidChange = new Emitter(); - readonly onDidChange: Event = this._onDidChange.event; - get entries(): OutlineEntry[] { - return this._outlineProviderReference?.object?.entries ?? []; - } - - private readonly _entriesDisposables = new DisposableStore(); + private readonly delayerRecomputeState: Delayer = this._disposables.add(new Delayer(300)); + private readonly delayerRecomputeActive: Delayer = this._disposables.add(new Delayer(200)); + // this can be long, because it will force a recompute at the end, so ideally we only do this once all nb language features are registered + private readonly delayerRecomputeSymbols: Delayer = this._disposables.add(new Delayer(2000)); readonly config: IOutlineListConfig; + private _outlineDataSourceReference: IReference | undefined; + // These three fields will always be set via setDataSources() on L475 + private _treeDataSource!: IDataSource; + private _quickPickDataSource!: IQuickPickDataSource; + private _breadcrumbsDataSource!: IBreadcrumbsDataSource; - readonly outlineKind = 'notebookCells'; + // view settings + private gotoShowCodeCellSymbols: boolean; + private outlineShowCodeCellSymbols: boolean; + // getters get activeElement(): OutlineEntry | undefined { - return this._outlineProviderReference?.object?.activeElement; + this.checkDelayer(); + if (this._target === OutlineTarget.OutlinePane) { + return (this.config.treeDataSource as NotebookOutlinePaneProvider).getActiveEntry(); + } else { + console.error('activeElement should not be called outside of the OutlinePane'); + return undefined; + } + } + get entries(): OutlineEntry[] { + this.checkDelayer(); + return this._outlineDataSourceReference?.object?.entries ?? []; + } + get uri(): URI | undefined { + return this._outlineDataSourceReference?.object?.uri; + } + get isEmpty(): boolean { + return this._outlineDataSourceReference?.object?.isEmpty ?? true; } - private _outlineProviderReference: IReference | undefined; - private readonly _localDisposables = new DisposableStore(); + private checkDelayer() { + if (this.delayerRecomputeState.isTriggered()) { + this.delayerRecomputeState.cancel(); + this.recomputeState(); + } + } constructor( private readonly _editor: INotebookEditorPane, - _target: OutlineTarget, - @IInstantiationService instantiationService: IInstantiationService, + private readonly _target: OutlineTarget, + @IThemeService private readonly _themeService: IThemeService, @IEditorService private readonly _editorService: IEditorService, - @IConfigurationService _configurationService: IConfigurationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, ) { - const installSelectionListener = () => { - const notebookEditor = _editor.getControl(); - if (!notebookEditor?.hasModel()) { - this._outlineProviderReference?.dispose(); - this._outlineProviderReference = undefined; - this._localDisposables.clear(); - } else { - this._outlineProviderReference?.dispose(); - this._localDisposables.clear(); - this._outlineProviderReference = instantiationService.invokeFunction((accessor) => accessor.get(INotebookCellOutlineProviderFactory).getOrCreate(notebookEditor, _target)); - this._localDisposables.add(this._outlineProviderReference.object.onDidChange(e => { - this._onDidChange.fire(e); - })); - } - }; + this.gotoShowCodeCellSymbols = this._configurationService.getValue(NotebookSetting.gotoSymbolsAllSymbols); + this.outlineShowCodeCellSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); - this._dispoables.add(_editor.onDidChangeModel(() => { - installSelectionListener(); - })); + this.initializeOutline(); - installSelectionListener(); const delegate = new NotebookOutlineVirtualDelegate(); - const renderers = [instantiationService.createInstance(NotebookOutlineRenderer, this._editor.getControl(), _target)]; + const renderers = [this._instantiationService.createInstance(NotebookOutlineRenderer, this._editor.getControl(), this._target)]; const comparator = new NotebookComparator(); const options: IWorkbenchDataTreeOptions = { - collapseByDefault: _target === OutlineTarget.Breadcrumbs || (_target === OutlineTarget.OutlinePane && _configurationService.getValue(OutlineConfigKeys.collapseItems) === OutlineConfigCollapseItemsValues.Collapsed), + collapseByDefault: this._target === OutlineTarget.Breadcrumbs || (this._target === OutlineTarget.OutlinePane && this._configurationService.getValue(OutlineConfigKeys.collapseItems) === OutlineConfigCollapseItemsValues.Collapsed), expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, accessibilityProvider: new NotebookOutlineAccessibility(), @@ -467,9 +581,9 @@ export class NotebookCellOutline implements IOutline { }; this.config = { - treeDataSource: instantiationService.createInstance(NotebookOutlinePaneProvider, () => (this.entries ?? [])), - quickPickDataSource: instantiationService.createInstance(NotebookQuickPickProvider, () => (this.entries ?? [])), - breadcrumbsDataSource: instantiationService.createInstance(NotebookBreadcrumbsProvider, () => (this.activeElement)), + treeDataSource: this._treeDataSource, + quickPickDataSource: this._quickPickDataSource, + breadcrumbsDataSource: this._breadcrumbsDataSource, delegate, renderers, comparator, @@ -477,25 +591,150 @@ export class NotebookCellOutline implements IOutline { }; } - async setFullSymbols(cancelToken: CancellationToken) { - await this._outlineProviderReference?.object?.setFullSymbols(cancelToken); + private initializeOutline() { + // initial setup + this.setDataSources(); + this.setModelListeners(); + + // reset the data sources + model listeners when we get a new notebook model + this._disposables.add(this._editor.onDidChangeModel(() => { + this.setDataSources(); + this.setModelListeners(); + this.computeSymbols(); + })); + + // recompute symbols as document symbol providers are updated in the language features registry + this._disposables.add(this._languageFeaturesService.documentSymbolProvider.onDidChange(() => { + this.delayedComputeSymbols(); + })); + + // recompute active when the selection changes + this._disposables.add(this._editor.onDidChangeSelection(() => { + this.delayedRecomputeActive(); + })); + + // recompute state when filter config changes + this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(NotebookSetting.outlineShowMarkdownHeadersOnly) || + e.affectsConfiguration(NotebookSetting.outlineShowCodeCells) || + e.affectsConfiguration(NotebookSetting.outlineShowCodeCellSymbols) || + e.affectsConfiguration(NotebookSetting.breadcrumbsShowCodeCells) + ) { + this.delayedRecomputeState(); + } + })); + + // recompute state when execution states change + this._disposables.add(this._notebookExecutionStateService.onDidChangeExecution(e => { + if (e.type === NotebookExecutionType.cell && !!this._editor.textModel && e.affectsNotebook(this._editor.textModel?.uri)) { + this.delayedRecomputeState(); + } + })); + + // recompute symbols when the configuration changes (recompute state - and therefore recompute active - is also called within compute symbols) + this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(NotebookSetting.gotoSymbolsAllSymbols) || e.affectsConfiguration(NotebookSetting.outlineShowCodeCellSymbols)) { + this.gotoShowCodeCellSymbols = this._configurationService.getValue(NotebookSetting.gotoSymbolsAllSymbols); + this.outlineShowCodeCellSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); + this.computeSymbols(); + } + })); + + // fire a change event when the theme changes + this._disposables.add(this._themeService.onDidFileIconThemeChange(() => { + this._onDidChange.fire({}); + })); + + // finish with a recompute state + this.recomputeState(); } - get uri(): URI | undefined { - return this._outlineProviderReference?.object?.uri; + /** + * set up the primary data source + three viewing sources for the various outline views + */ + private setDataSources(): void { + const notebookEditor = this._editor.getControl(); + this._outlineDataSourceReference?.dispose(); + this._dataSourceDisposables.clear(); + + if (!notebookEditor?.hasModel()) { + this._outlineDataSourceReference = undefined; + } else { + this._outlineDataSourceReference = this._dataSourceDisposables.add(this._instantiationService.invokeFunction((accessor) => accessor.get(INotebookCellOutlineDataSourceFactory).getOrCreate(notebookEditor))); + // escalate outline data source change events + this._dataSourceDisposables.add(this._outlineDataSourceReference.object.onDidChange(() => { + this._onDidChange.fire({}); + })); + } + + // these fields can be passed undefined outlineDataSources. View Providers all handle it accordingly + this._treeDataSource = this._dataSourceDisposables.add(this._instantiationService.createInstance(NotebookOutlinePaneProvider, this._outlineDataSourceReference)); + this._quickPickDataSource = this._dataSourceDisposables.add(this._instantiationService.createInstance(NotebookQuickPickProvider, this._outlineDataSourceReference)); + this._breadcrumbsDataSource = this._dataSourceDisposables.add(this._instantiationService.createInstance(NotebookBreadcrumbsProvider, this._outlineDataSourceReference)); } - get isEmpty(): boolean { - return this._outlineProviderReference?.object?.isEmpty ?? true; + + /** + * set up the listeners for the outline content, these respond to model changes in the notebook + */ + private setModelListeners(): void { + this._modelDisposables.clear(); + if (!this._editor.textModel) { + return; + } + + // Perhaps this is the first time we're building the outline + if (!this.entries.length) { + this.computeSymbols(); + } + + // recompute state when there are notebook content changes + this._modelDisposables.add(this._editor.textModel.onDidChangeContent(contentChanges => { + if (contentChanges.rawEvents.some(c => + c.kind === NotebookCellsChangeType.ChangeCellContent || + c.kind === NotebookCellsChangeType.ChangeCellInternalMetadata || + c.kind === NotebookCellsChangeType.Move || + c.kind === NotebookCellsChangeType.ModelChange)) { + this.delayedRecomputeState(); + } + })); + } + + private async computeSymbols(cancelToken: CancellationToken = CancellationToken.None) { + if (this._target === OutlineTarget.QuickPick && this.gotoShowCodeCellSymbols) { + await this._outlineDataSourceReference?.object?.computeFullSymbols(cancelToken); + } else if (this._target === OutlineTarget.OutlinePane && this.outlineShowCodeCellSymbols) { + // No need to wait for this, we want the outline to show up quickly. + void this._outlineDataSourceReference?.object?.computeFullSymbols(cancelToken); + } + } + private async delayedComputeSymbols() { + this.delayerRecomputeState.cancel(); + this.delayerRecomputeActive.cancel(); + this.delayerRecomputeSymbols.trigger(() => { this.computeSymbols(); }); + } + + private recomputeState() { this._outlineDataSourceReference?.object?.recomputeState(); } + private delayedRecomputeState() { + this.delayerRecomputeActive.cancel(); // Active is always recomputed after a recomputing the State. + this.delayerRecomputeState.trigger(() => { this.recomputeState(); }); + } + + private recomputeActive() { this._outlineDataSourceReference?.object?.recomputeActive(); } + private delayedRecomputeActive() { + this.delayerRecomputeActive.trigger(() => { this.recomputeActive(); }); } + async reveal(entry: OutlineEntry, options: IEditorOptions, sideBySide: boolean): Promise { + const notebookEditorOptions: INotebookEditorOptions = { + ...options, + override: this._editor.input?.editorId, + cellRevealType: CellRevealType.NearTopIfOutsideViewport, + selection: entry.position, + viewState: undefined, + }; await this._editorService.openEditor({ resource: entry.cell.uri, - options: { - ...options, - override: this._editor.input?.editorId, - cellRevealType: CellRevealType.NearTopIfOutsideViewport, - selection: entry.position - } as INotebookEditorOptions, + options: notebookEditorOptions, }, sideBySide ? SIDE_GROUP : undefined); } @@ -562,10 +801,10 @@ export class NotebookCellOutline implements IOutline { dispose(): void { this._onDidChange.dispose(); - this._dispoables.dispose(); - this._entriesDisposables.dispose(); - this._outlineProviderReference?.dispose(); - this._localDisposables.dispose(); + this._disposables.dispose(); + this._modelDisposables.dispose(); + this._dataSourceDisposables.dispose(); + this._outlineDataSourceReference?.dispose(); } } @@ -576,7 +815,6 @@ export class NotebookOutlineCreator implements IOutlineCreator reg.dispose(); @@ -587,18 +825,7 @@ export class NotebookOutlineCreator implements IOutlineCreator | undefined> { - const outline = this._instantiationService.createInstance(NotebookCellOutline, editor, target); - - const showAllGotoSymbols = this._configurationService.getValue(NotebookSetting.gotoSymbolsAllSymbols); - const showAllOutlineSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); - if (target === OutlineTarget.QuickPick && showAllGotoSymbols) { - await outline.setFullSymbols(cancelToken); - } else if (target === OutlineTarget.OutlinePane && showAllOutlineSymbols) { - // No need to wait for this, we want the outline to show up quickly. - void outline.setFullSymbols(cancelToken); - } - - return outline; + return this._instantiationService.createInstance(NotebookCellOutline, editor, target); } } @@ -677,7 +904,6 @@ registerAction2(class ToggleShowMarkdownHeadersOnly extends Action2 { } }); - registerAction2(class ToggleCodeCellEntries extends Action2 { constructor() { super({ diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts index eb2d57e6..d5899e31 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts @@ -499,7 +499,7 @@ export class CodeActionParticipantUtils { }; for (const codeActionKind of codeActionsOnSave) { - const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, languageFeaturesService, getActionProgress, token); + const actionsToRun = await CodeActionParticipantUtils.getActionsToRun(model, codeActionKind, excludes, languageFeaturesService, getActionProgress, token); if (token.isCancellationRequested) { actionsToRun.dispose(); return; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index 690a05c1..22b0a5e3 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -98,11 +98,11 @@ export class TroubleshootController extends Disposable implements INotebookEdito items.push({ handle: i, items: [ - { + { text: `index: ${i}`, alignment: CellStatusbarAlignment.Left, priority: Number.MAX_SAFE_INTEGER - } + } satisfies INotebookCellStatusBarItem ] }); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts index 1d5d4405..e69da8b4 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts @@ -64,7 +64,7 @@ CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, arg }[]> => { const notebookKernelService = accessor.get(INotebookKernelService); const uri = URI.revive(args.uri as UriComponents); - const kernels = notebookKernelService.getMatchingKernel({ uri, viewType: args.viewType }); + const kernels = notebookKernelService.getMatchingKernel({ uri, notebookType: args.viewType }); return kernels.all.map(provider => ({ id: provider.id, diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts index 64c90739..e9a75693 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts @@ -184,7 +184,7 @@ export function runDeleteAction(editor: IActiveNotebookEditor, cell: ICellViewMo } } -export async function moveCellRange(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise { +export async function moveCellRange(context: INotebookActionContext, direction: 'up' | 'down'): Promise { if (!context.notebookEditor.hasModel()) { return; } @@ -195,9 +195,17 @@ export async function moveCellRange(context: INotebookCellActionContext, directi return; } - const selections = editor.getSelections(); - const modelRanges = expandCellRangesWithHiddenCells(editor, selections); - const range = modelRanges[0]; + let range: ICellRange | undefined = undefined; + + if (context.cell) { + const idx = editor.getCellIndex(context.cell); + range = { start: idx, end: idx + 1 }; + } else { + const selections = editor.getSelections(); + const modelRanges = expandCellRangesWithHiddenCells(editor, selections); + range = modelRanges[0]; + } + if (!range || range.start === range.end) { return; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts index 0db8e6dd..310a367f 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts @@ -7,18 +7,48 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INotebookOutputActionContext, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { NOTEBOOK_CELL_HAS_OUTPUTS } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_CELL_HAS_HIDDEN_OUTPUTS, NOTEBOOK_CELL_HAS_OUTPUTS } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { ILogService } from 'vs/platform/log/common/log'; import { copyCellOutput } from 'vs/workbench/contrib/notebook/browser/contrib/clipboard/cellOutputClipboard'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ICellOutputViewModel, ICellViewModel, INotebookEditor, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export const COPY_OUTPUT_COMMAND_ID = 'notebook.cellOutput.copy'; +registerAction2(class ShowAllOutputsAction extends Action2 { + constructor() { + super({ + id: 'notebook.cellOuput.showEmptyOutputs', + title: localize('notebookActions.showAllOutput', "Show empty outputs"), + menu: { + id: MenuId.NotebookOutputToolbar, + when: ContextKeyExpr.and(NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_HAS_HIDDEN_OUTPUTS) + }, + f1: false, + category: NOTEBOOK_ACTIONS_CATEGORY + }); + } + + run(accessor: ServicesAccessor, context: INotebookOutputActionContext): void { + const cell = context.cell; + if (cell && cell.cellKind === CellKind.Code) { + + for (let i = 1; i < cell.outputsViewModels.length; i++) { + if (!cell.outputsViewModels[i].visible.get()) { + cell.outputsViewModels[i].setVisible(true, true); + (cell as CodeCellViewModel).updateOutputHeight(i, 1, 'command'); + } + } + } + } +}); + registerAction2(class CopyCellOutputAction extends Action2 { constructor() { super({ @@ -95,7 +125,7 @@ function getOutputViewModelFromId(outputId: string, notebookEditor: INotebookEdi if (notebookViewModel) { const codeCells = notebookViewModel.viewCells.filter(cell => cell.cellKind === CellKind.Code) as CodeCellViewModel[]; for (const cell of codeCells) { - const output = cell.outputsViewModels.find(output => output.model.outputId === outputId); + const output = cell.outputsViewModels.find(output => output.model.outputId === outputId || output.model.alternativeOutputId === outputId); if (output) { return output; } @@ -104,3 +134,45 @@ function getOutputViewModelFromId(outputId: string, notebookEditor: INotebookEdi return undefined; } + +export const OPEN_OUTPUT_COMMAND_ID = 'notebook.cellOutput.openInTextEditor'; + +registerAction2(class OpenCellOutputInEditorAction extends Action2 { + constructor() { + super({ + id: OPEN_OUTPUT_COMMAND_ID, + title: localize('notebookActions.openOutputInEditor', "Open Cell Output in Text Editor"), + f1: false, + category: NOTEBOOK_ACTIONS_CATEGORY, + icon: icons.copyIcon, + }); + } + + private getNoteboookEditor(editorService: IEditorService, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): INotebookEditor | undefined { + if (outputContext && 'notebookEditor' in outputContext) { + return outputContext.notebookEditor; + } + return getNotebookEditorFromEditorPane(editorService.activeEditorPane); + } + + async run(accessor: ServicesAccessor, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): Promise { + const notebookEditor = this.getNoteboookEditor(accessor.get(IEditorService), outputContext); + + if (!notebookEditor) { + return; + } + + let outputViewModel: ICellOutputViewModel | undefined; + if (outputContext && 'outputId' in outputContext && typeof outputContext.outputId === 'string') { + outputViewModel = getOutputViewModelFromId(outputContext.outputId, notebookEditor); + } else if (outputContext && 'outputViewModel' in outputContext) { + outputViewModel = outputContext.outputViewModel; + } + + const openerService = accessor.get(IOpenerService); + + if (outputViewModel?.model.outputId && notebookEditor.textModel?.uri) { + openerService.open(CellUri.generateCellOutputUri(notebookEditor.textModel.uri, outputViewModel.model.outputId)); + } + } +}); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 7ed5b3c5..f474b4df 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -15,8 +15,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_HAS_AGENT, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { CELL_TITLE_CELL_GROUP_ID, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions'; @@ -24,7 +24,6 @@ import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBro import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_GENERATED_BY_CHAT, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; - registerAction2(class extends NotebookAction { constructor() { super( @@ -259,9 +258,9 @@ registerAction2(class extends NotebookAction { menu: [ { id: MENU_CELL_CHAT_WIDGET_STATUS, - group: 'inline', + group: '0_main', order: 0, - when: CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages), + when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.Messages), } ], f1: false @@ -287,7 +286,7 @@ registerAction2(class extends NotebookAction { }, menu: { id: MENU_CELL_CHAT_WIDGET_STATUS, - group: 'main', + group: '0_main', order: 1 }, f1: false @@ -367,7 +366,7 @@ registerAction2(class extends NotebookAction { NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), ContextKeyExpr.not(InputFocusedContextKey), - CTX_INLINE_CHAT_HAS_AGENT, + CTX_NOTEBOOK_CHAT_HAS_AGENT, ContextKeyExpr.or( ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true), ContextKeyExpr.equals(`config.${NotebookSetting.cellGenerate}`, true) @@ -384,7 +383,7 @@ registerAction2(class extends NotebookAction { order: -1, when: ContextKeyExpr.and( NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), - CTX_INLINE_CHAT_HAS_AGENT, + CTX_NOTEBOOK_CHAT_HAS_AGENT, ContextKeyExpr.or( ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true), ContextKeyExpr.equals(`config.${NotebookSetting.cellGenerate}`, true) @@ -459,7 +458,7 @@ registerAction2(class extends NotebookAction { order: -1, when: ContextKeyExpr.and( NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), - CTX_INLINE_CHAT_HAS_AGENT, + CTX_NOTEBOOK_CHAT_HAS_AGENT, ContextKeyExpr.or( ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true), ContextKeyExpr.equals(`config.${NotebookSetting.cellGenerate}`, true) @@ -488,7 +487,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), ContextKeyExpr.notEquals('config.notebook.insertToolbarLocation', 'betweenCells'), ContextKeyExpr.notEquals('config.notebook.insertToolbarLocation', 'hidden'), - CTX_INLINE_CHAT_HAS_AGENT, + CTX_NOTEBOOK_CHAT_HAS_AGENT, ContextKeyExpr.or( ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true), ContextKeyExpr.equals(`config.${NotebookSetting.cellGenerate}`, true) @@ -633,7 +632,7 @@ registerAction2(class extends NotebookCellAction { order: 0, when: ContextKeyExpr.and( NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), - CTX_INLINE_CHAT_HAS_AGENT, + CTX_NOTEBOOK_CHAT_HAS_AGENT, NOTEBOOK_CELL_GENERATED_BY_CHAT, ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true) ) diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts index f35264aa..d642cbdc 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -4,37 +4,34 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions'; -import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import 'vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions'; -import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; -import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { CTX_NOTEBOOK_CHAT_HAS_AGENT } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; -class NotebookChatVariables extends Disposable implements IWorkbenchContribution { +class NotebookChatContribution extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.notebookChatVariables'; + static readonly ID = 'workbench.contrib.notebookChatContribution'; + + private readonly _ctxHasProvider: IContextKey; constructor( - @IChatVariablesService private readonly _chatVariableService: IChatVariablesService, - @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService + @IContextKeyService contextKeyService: IContextKeyService, + @IChatAgentService chatAgentService: IChatAgentService ) { super(); - this._register(this._chatVariableService.registerVariable( - { id: '_notebookChatInput', name: '_notebookChatInput', description: '', hidden: true }, - async (_message, _arg, model) => { - const editors = this._notebookEditorService.listNotebookEditors(); - for (const editor of editors) { - const chatController = editor.getContribution(NotebookChatController.id) as NotebookChatController | undefined; - if (chatController?.hasSession(model)) { - return chatController.getSessionInputUri(); - } - } - - return undefined; - } - )); + this._ctxHasProvider = CTX_NOTEBOOK_CHAT_HAS_AGENT.bindTo(contextKeyService); + + const updateNotebookAgentStatus = () => { + const hasNotebookAgent = Boolean(chatAgentService.getDefaultAgent(ChatAgentLocation.Notebook)); + this._ctxHasProvider.set(hasNotebookAgent); + }; + + updateNotebookAgentStatus(); + this._register(chatAgentService.onDidChangeAgents(updateNotebookAgentStatus)); } } -registerWorkbenchContribution2(NotebookChatVariables.ID, NotebookChatVariables, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(NotebookChatContribution.ID, NotebookChatContribution, WorkbenchPhase.BlockRestore); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts index 4c8a6aa0..259b8e83 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts @@ -17,3 +17,5 @@ export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar'); + +export const CTX_NOTEBOOK_CHAT_HAS_AGENT = new RawContextKey('notebookChatAgentRegistered', false, localize('notebookChatAgentRegistered', "Whether a chat agent for notebook is registered")); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 4662ab11..3de91da6 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -26,7 +26,6 @@ import { ICursorStateComputer, ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -37,7 +36,6 @@ import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookViewZone } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -413,16 +411,30 @@ export class NotebookChatController extends Disposable implements INotebookEdito const inlineChatWidget = this._widgetDisposableStore.add(this._instantiationService.createInstance( InlineChatWidget, - ChatAgentLocation.Notebook, { - telemetrySource: 'notebook-generate-cell', - inputMenuId: MenuId.ChatExecute, - widgetMenuId: MENU_INLINE_CHAT_WIDGET, + location: ChatAgentLocation.Notebook, + resolveData: () => { + const sessionInputUri = this.getSessionInputUri(); + if (!sessionInputUri) { + return undefined; + } + return { + type: ChatAgentLocation.Notebook, + sessionInputUri + }; + } + }, + { statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, - rendererOptions: { - renderTextEditsAsSummary: (uri) => { - return isEqual(uri, this._widget?.parentEditor.getModel()?.uri) - || isEqual(uri, this._notebookEditor.textModel?.uri); + chatWidgetViewOptions: { + rendererOptions: { + renderTextEditsAsSummary: (uri) => { + return isEqual(uri, this._widget?.parentEditor.getModel()?.uri) + || isEqual(uri, this._notebookEditor.textModel?.uri); + } + }, + menus: { + telemetrySource: 'notebook-generate-cell' } } } @@ -469,6 +481,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._sessionCtor = createCancelablePromise(async token => { await this._startSession(token); + assertType(this._model.value); + const model = this._model.value; + this._widget?.inlineChatWidget.setChatModel(model); + if (fakeParentEditor.hasModel()) { if (this._widget) { @@ -546,9 +562,6 @@ export class NotebookChatController extends Disposable implements INotebookEdito assertType(this._model.value); assertType(this._strategy); - const model = this._model.value; - this._widget.inlineChatWidget.setChatModel(model); - const lastInput = this._widget.inlineChatWidget.value; this._historyUpdate(lastInput); @@ -664,7 +677,6 @@ export class NotebookChatController extends Disposable implements INotebookEdito store.dispose(); this._ctxHasActiveRequest.set(false); - this._widget.inlineChatWidget.updateProgress(false); this._widget.inlineChatWidget.updateInfo(''); this._widget.inlineChatWidget.updateToolbar(true); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index 3cc7faf8..4a8192b5 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -13,7 +13,7 @@ import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, ICellViewModel, import { INTERACTIVE_WINDOW_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { ICellRange, isICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorCommandsContext } from 'vs/workbench/common/editor'; +import { isEditorCommandsContext } from 'vs/workbench/common/editor'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; @@ -220,9 +220,6 @@ export abstract class NotebookMultiCellAction extends Action2 { private isCellToolbarContext(context?: unknown): context is INotebookCellToolbarActionContext { return !!context && !!(context as INotebookActionContext).notebookEditor && (context as any).$mid === MarshalledId.NotebookCellActionContext; } - private isEditorContext(context?: unknown): boolean { - return !!context && (context as IEditorCommandsContext).groupId !== undefined; - } /** * The action/command args are resolved in following order @@ -233,7 +230,7 @@ export abstract class NotebookMultiCellAction extends Action2 { async run(accessor: ServicesAccessor, ...additionalArgs: any[]): Promise { const context = additionalArgs[0]; const isFromCellToolbar = this.isCellToolbarContext(context); - const isFromEditorToolbar = this.isEditorContext(context); + const isFromEditorToolbar = isEditorCommandsContext(context); const from = isFromCellToolbar ? 'cellToolbar' : (isFromEditorToolbar ? 'editorToolbar' : 'other'); const telemetryService = accessor.get(ITelemetryService); @@ -253,12 +250,14 @@ export abstract class NotebookMultiCellAction extends Action2 { // no parsed args, try handle active editor const editor = getEditorFromArgsOrActivePane(accessor); if (editor) { + const selectedCellRange: ICellRange[] = editor.getSelections().length === 0 ? [editor.getFocus()] : editor.getSelections(); + telemetryService.publicLog2('workbenchActionExecuted', { id: this.desc.id, from: from }); return this.runWithContext(accessor, { ui: false, notebookEditor: editor, - selectedCells: cellRangeToViewCells(editor, editor.getSelections()) + selectedCells: cellRangeToViewCells(editor, selectedCellRange) }); } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index c73973e4..defeb512 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -7,10 +7,15 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Selection } from 'vs/editor/common/core/selection'; +import { CommandExecutor } from 'vs/editor/common/cursor/cursor'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/model'; +import { LineCommentCommand, Type } from 'vs/editor/contrib/comment/browser/lineCommentCommand'; import { localize, localize2 } from 'vs/nls'; import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -24,23 +29,24 @@ import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/ import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { changeCellToKind, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; -import { CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, executeNotebookCondition, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, executeNotebookCondition, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { NotebookChangeTabDisplaySize, NotebookIndentUsingSpaces, NotebookIndentUsingTabs, NotebookIndentationToSpacesAction, NotebookIndentationToTabsAction } from 'vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions'; import { CHANGE_CELL_LANGUAGE, CellEditState, DETECT_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CellEditType, CellKind, ICellEditOperation, NotebookCellExecutionState, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_INPUT_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_IS_FIRST_OUTPUT, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; -import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete'; export const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; export const SELECT_NOTEBOOK_INDENTATION_ID = 'notebook.selectIndentation'; +export const COMMENT_SELECTED_CELLS_ID = 'notebook.commentSelectedCells'; registerAction2(class EditCellAction extends NotebookCellAction { constructor() { @@ -227,7 +233,7 @@ registerAction2(class ClearCellOutputsAction extends NotebookCellAction { }, { id: MenuId.NotebookOutputToolbar, - when: ContextKeyExpr.and(NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE) + when: ContextKeyExpr.and(NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_IS_FIRST_OUTPUT, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON) }, ], keybinding: { @@ -271,7 +277,6 @@ registerAction2(class ClearCellOutputsAction extends NotebookCellAction { } }); - registerAction2(class ClearAllCellOutputsAction extends NotebookAction { constructor() { super({ @@ -337,7 +342,6 @@ registerAction2(class ClearAllCellOutputsAction extends NotebookAction { } }); - interface ILanguagePickInput extends IQuickPickItem { languageId: string; description: string; @@ -621,3 +625,56 @@ registerAction2(class SelectNotebookIndentation extends NotebookAction { return; } }); + +registerAction2(class CommentSelectedCellsAction extends NotebookMultiCellAction { + constructor() { + super({ + id: COMMENT_SELECTED_CELLS_ID, + title: localize('commentSelectedCells', "Comment Selected Cells"), + keybinding: { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + NOTEBOOK_EDITOR_EDITABLE, + ContextKeyExpr.not(InputFocusedContextKey), + ), + primary: KeyMod.CtrlCmd | KeyCode.Slash, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext): Promise { + const languageConfigurationService = accessor.get(ILanguageConfigurationService); + + context.selectedCells.forEach(async cellViewModel => { + const textModel = await cellViewModel.resolveTextModel(); + + const commentsOptions = cellViewModel.commentOptions; + const cellCommentCommand = new LineCommentCommand( + languageConfigurationService, + new Selection(1, 1, textModel.getLineCount(), textModel.getLineMaxColumn(textModel.getLineCount())), // comment the entire cell + textModel.getOptions().tabSize, + Type.Toggle, + commentsOptions.insertSpace ?? true, + commentsOptions.ignoreEmptyLines ?? true, + false + ); + + // store any selections that are in the cell, allows them to be shifted by comments and preserved + const cellEditorSelections = cellViewModel.getSelections(); + const initialTrackedRangesIDs: string[] = cellEditorSelections.map(selection => { + return textModel._setTrackedRange(null, selection, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); + }); + + CommandExecutor.executeCommands(textModel, cellEditorSelections, [cellCommentCommand]); + + const newTrackedSelections = initialTrackedRangesIDs.map(i => { + return textModel._getTrackedRange(i); + }).filter(r => !!r).map((range,) => { + return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); + }); + cellViewModel.setSelections(newTrackedSelections ?? []); + }); // end of cells forEach + } + +}); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index 19f30d41..db602591 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from 'vs/base/common/codicons'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -191,22 +192,23 @@ registerAction2(class SaveMimeTypeDisplayOrder extends Action2 { run(accessor: ServicesAccessor) { const service = accessor.get(INotebookService); - const qp = accessor.get(IQuickInputService).createQuickPick(); + const disposables = new DisposableStore(); + const qp = disposables.add(accessor.get(IQuickInputService).createQuickPick()); qp.placeholder = localize('notebook.placeholder', 'Settings file to save in'); qp.items = [ { target: ConfigurationTarget.USER, label: localize('saveTarget.machine', 'User Settings') }, { target: ConfigurationTarget.WORKSPACE, label: localize('saveTarget.workspace', 'Workspace Settings') }, ]; - qp.onDidAccept(() => { + disposables.add(qp.onDidAccept(() => { const target = qp.selectedItems[0]?.target; if (target !== undefined) { service.saveMimeDisplayOrder(target); } qp.dispose(); - }); + })); - qp.onDidHide(() => qp.dispose()); + disposables.add(qp.onDidHide(() => disposables.dispose())); qp.show(); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions.ts index c2b1d9cf..1e6544b5 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions.ts @@ -6,6 +6,14 @@ import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +/** + * Do not leave at 12, when at 12 and we have whitespace and only one line, + * then there's not enough space for the button `Show Whitespace Differences` + */ +export const fixedEditorPaddingSingleLineCells = { + top: 24, + bottom: 24 +}; export const fixedEditorPadding = { top: 12, bottom: 12 @@ -29,8 +37,7 @@ export const fixedEditorOptions: IEditorOptions = { selectOnLineNumbers: false, wordWrap: 'off', lineNumbers: 'off', - lineDecorationsWidth: 0, - glyphMargin: false, + glyphMargin: true, fixedOverflowWidgets: true, minimap: { enabled: false }, renderValidationDecorations: 'on', diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 59a4d27a..a8f2c46c 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -7,8 +7,8 @@ import * as DOM from 'vs/base/browser/dom'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { DiffElementViewModelBase, getFormattedMetadataJSON, getFormattedOutputJSON, OutputComparison, outputEqual, OUTPUT_EDITOR_HEIGHT_MAGIC, PropertyFoldingState, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; -import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DiffSide, DIFF_CELL_MARGIN, INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { DiffElementCellViewModelBase, getFormattedMetadataJSON, getFormattedOutputJSON, OutputComparison, outputEqual, OUTPUT_EDITOR_HEIGHT_MAGIC, PropertyFoldingState, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel, DiffElementPlaceholderViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DiffSide, DIFF_CELL_MARGIN, INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED, CellDiffPlaceholderRenderTemplate, IDiffCellMarginOverlay, NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -31,18 +31,22 @@ import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestCont import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { fixedDiffEditorOptions, fixedEditorOptions, fixedEditorPadding } from 'vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions'; +import { fixedDiffEditorOptions, fixedEditorOptions, fixedEditorPadding, fixedEditorPaddingSingleLineCells } from 'vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel'; +import { localize } from 'vs/nls'; +import { Emitter } from 'vs/base/common/event'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; export function getOptimizedNestedCodeEditorWidgetOptions(): ICodeEditorWidgetOptions { return { @@ -58,6 +62,29 @@ export function getOptimizedNestedCodeEditorWidgetOptions(): ICodeEditorWidgetOp }; } +export class CellDiffPlaceholderElement extends Disposable { + constructor( + placeholder: DiffElementPlaceholderViewModel, + templateData: CellDiffPlaceholderRenderTemplate, + ) { + super(); + templateData.body.classList.remove('left', 'right', 'full'); + const text = (placeholder.hiddenCells.length === 1) ? + localize('hiddenCell', '{0} hidden cell', placeholder.hiddenCells.length) : + localize('hiddenCells', '{0} hidden cells', placeholder.hiddenCells.length); + templateData.placeholder.innerText = text; + + this._register(DOM.addDisposableListener(templateData.placeholder, 'dblclick', (e: MouseEvent) => { + if (e.button !== 0) { + return; + } + e.preventDefault(); + placeholder.showHiddenCells(); + })); + this._register(templateData.marginOverlay.onAction(() => placeholder.showHiddenCells())); + templateData.marginOverlay.show(); + } +} class PropertyHeader extends Disposable { protected _foldingIndicator!: HTMLElement; @@ -68,14 +95,14 @@ class PropertyHeader extends Disposable { protected _propertyExpanded?: IContextKey; constructor( - readonly cell: DiffElementViewModelBase, + readonly cell: DiffElementCellViewModelBase, readonly propertyHeaderContainer: HTMLElement, readonly notebookEditor: INotebookTextDiffEditor, readonly accessor: { updateInfoRendering: (renderOutput: boolean) => void; - checkIfModified: (cell: DiffElementViewModelBase) => false | { reason: string | undefined }; - getFoldingState: (cell: DiffElementViewModelBase) => PropertyFoldingState; - updateFoldingState: (cell: DiffElementViewModelBase, newState: PropertyFoldingState) => void; + checkIfModified: (cell: DiffElementCellViewModelBase) => false | { reason: string | undefined }; + getFoldingState: (cell: DiffElementCellViewModelBase) => PropertyFoldingState; + updateFoldingState: (cell: DiffElementCellViewModelBase, newState: PropertyFoldingState) => void; unChangedLabel: string; changedLabel: string; prefix: string; @@ -159,23 +186,10 @@ class PropertyHeader extends Disposable { const target = e.event.target as HTMLElement; - if (target.classList.contains('codicon-notebook-collapsed') || target.classList.contains('codicon-notebook-expanded')) { - const parent = target.parentElement as HTMLElement; - - if (!parent) { - return; - } - - if (!parent.classList.contains(this.accessor.prefix)) { - return; - } - - if (!parent.classList.contains('property-folding-indicator')) { - return; - } - - // folding icon - + if (target === this.propertyHeaderContainer || + target === this._foldingIndicator || this._foldingIndicator.contains(target) || + target === metadataStatus || metadataStatus.contains(target) + ) { const cellViewModel = e.target; if (cellViewModel === this.cell) { @@ -239,6 +253,9 @@ abstract class AbstractElementRenderer extends Disposable { protected readonly _outputLocalDisposable = this._register(new DisposableStore()); protected _ignoreMetadata: boolean = false; protected _ignoreOutputs: boolean = false; + protected _cellHeaderContainer!: HTMLElement; + protected _editorContainer!: HTMLElement; + protected _cellHeader!: PropertyHeader; protected _metadataHeaderContainer!: HTMLElement; protected _metadataHeader!: PropertyHeader; protected _metadataInfoContainer!: HTMLElement; @@ -267,7 +284,7 @@ abstract class AbstractElementRenderer extends Disposable { constructor( readonly notebookEditor: INotebookTextDiffEditor, - readonly cell: DiffElementViewModelBase, + readonly cell: DiffElementCellViewModelBase, readonly templateData: CellDiffSingleSideRenderTemplate | CellDiffSideBySideRenderTemplate, readonly style: 'left' | 'right' | 'full', protected readonly instantiationService: IInstantiationService, @@ -280,13 +297,16 @@ abstract class AbstractElementRenderer extends Disposable { protected readonly menuService: IMenuService, protected readonly contextKeyService: IContextKeyService, protected readonly configurationService: IConfigurationService, + protected readonly textConfigurationService: ITextResourceConfigurationService ) { super(); // init this._isDisposed = false; this._metadataEditorDisposeStore = this._register(new DisposableStore()); this._outputEditorDisposeStore = this._register(new DisposableStore()); - this._register(cell.onDidLayoutChange(e => this.layout(e))); + this._register(cell.onDidLayoutChange(e => { + this.layout(e); + })); this._register(cell.onDidLayoutChange(e => this.updateBorders())); this.init(); this.buildBody(); @@ -321,6 +341,9 @@ abstract class AbstractElementRenderer extends Disposable { this.styleContainer(this._diffEditorContainer); this.updateSourceEditor(); + if (this.cell.modified) { + this._register(this.cell.modified.textModel.onDidChangeContent(() => this._cellHeader.refresh())); + } this._ignoreMetadata = this.configurationService.getValue('notebook.diff.ignoreMetadata'); if (this._ignoreMetadata) { @@ -510,6 +533,7 @@ abstract class AbstractElementRenderer extends Disposable { originalEditor: getOptimizedNestedCodeEditorWidgetOptions(), modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() }); + this.layout({ metadataHeight: true }); this._metadataEditorDisposeStore.add(this._metadataEditor); @@ -716,10 +740,11 @@ abstract class AbstractElementRenderer extends Disposable { } abstract class SingleSideDiffElement extends AbstractElementRenderer { - + protected _editor!: CodeEditorWidget; override readonly cell: SingleSideDiffElementViewModel; override readonly templateData: CellDiffSingleSideRenderTemplate; - + abstract get nestedCellViewModel(): DiffNestedCellViewModel; + abstract get readonly(): boolean; constructor( notebookEditor: INotebookTextDiffEditor, cell: SingleSideDiffElementViewModel, @@ -735,6 +760,7 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { menuService: IMenuService, contextKeyService: IContextKeyService, configurationService: IConfigurationService, + textConfigurationService: ITextResourceConfigurationService ) { super( notebookEditor, @@ -750,7 +776,8 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { notificationService, menuService, contextKeyService, - configurationService + configurationService, + textConfigurationService ); this.cell = cell; this.templateData = templateData; @@ -825,9 +852,107 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { })); } + override updateSourceEditor(): void { + this._cellHeaderContainer = this.templateData.cellHeaderContainer; + this._cellHeaderContainer.style.display = 'flex'; + this._cellHeaderContainer.innerText = ''; + this._editorContainer = this.templateData.editorContainer; + this._editorContainer.classList.add('diff'); + + const renderSourceEditor = () => { + if (this.cell.cellFoldingState === PropertyFoldingState.Collapsed) { + this._editorContainer.style.display = 'none'; + this.cell.editorHeight = 0; + return; + } + + const lineCount = this.nestedCellViewModel.textModel.textBuffer.getLineCount(); + const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; + const editorHeight = lineCount * lineHeight + fixedEditorPadding.top + fixedEditorPadding.bottom; + + this._editorContainer.style.height = `${editorHeight}px`; + this._editorContainer.style.display = 'block'; + + if (this._editor) { + const contentHeight = this._editor.getContentHeight(); + if (contentHeight >= 0) { + this.cell.editorHeight = contentHeight; + } + return; + } + + this._editor = this.templateData.sourceEditor; + this._editor.layout( + { + width: (this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN) / 2 - 18, + height: editorHeight + } + ); + this._editor.updateOptions({ readOnly: this.readonly }); + this.cell.editorHeight = editorHeight; + + this._register(this._editor.onDidContentSizeChange((e) => { + if (this.cell.cellFoldingState === PropertyFoldingState.Expanded && e.contentHeightChanged && this.cell.layoutInfo.editorHeight !== e.contentHeight) { + this.cell.editorHeight = e.contentHeight; + } + })); + this._initializeSourceDiffEditor(this.nestedCellViewModel); + }; + + this._cellHeader = this._register(this.instantiationService.createInstance( + PropertyHeader, + this.cell, + this._cellHeaderContainer, + this.notebookEditor, + { + updateInfoRendering: () => renderSourceEditor(), + checkIfModified: (_) => ({ reason: undefined }), + getFoldingState: (cell) => cell.cellFoldingState, + updateFoldingState: (cell, state) => cell.cellFoldingState = state, + unChangedLabel: 'Input', + changedLabel: 'Input', + prefix: 'input', + menuId: MenuId.NotebookDiffCellInputTitle + } + )); + this._cellHeader.buildHeader(); + renderSourceEditor(); + + this._initializeSourceDiffEditor(this.nestedCellViewModel); + } + protected calculateDiagonalFillHeight() { + return this.cell.layoutInfo.cellStatusHeight + this.cell.layoutInfo.editorHeight + this.cell.layoutInfo.editorMargin + this.cell.layoutInfo.metadataStatusHeight + this.cell.layoutInfo.metadataHeight + this.cell.layoutInfo.outputTotalHeight + this.cell.layoutInfo.outputStatusHeight; + } + + private async _initializeSourceDiffEditor(modifiedCell: DiffNestedCellViewModel) { + const modifiedRef = await this.textModelService.createModelReference(modifiedCell.uri); + + if (this._isDisposed) { + return; + } + + const modifiedTextModel = modifiedRef.object.textEditorModel; + this._register(modifiedRef); + + this._editor!.setModel(modifiedTextModel); + + const editorViewState = this.cell.getSourceEditorViewState() as editorCommon.IDiffEditorViewState | null; + if (editorViewState) { + this._editor!.restoreViewState(editorViewState); + } + + const contentHeight = this._editor!.getContentHeight(); + this.cell.editorHeight = contentHeight; + const height = `${this.calculateDiagonalFillHeight()}px`; + if (this._diagonalFill!.style.height !== height) { + this._diagonalFill!.style.height = height; + } + } + _disposeMetadata() { this.cell.metadataStatusHeight = 0; this.cell.metadataHeight = 0; + this.templateData.cellHeaderContainer.style.display = 'none'; this.templateData.metadataHeaderContainer.style.display = 'none'; this.templateData.metadataInfoContainer.style.display = 'none'; this._metadataEditor = undefined; @@ -916,7 +1041,6 @@ abstract class SingleSideDiffElement extends AbstractElementRenderer { } } export class DeletedElement extends SingleSideDiffElement { - private _editor!: CodeEditorWidget; constructor( notebookEditor: INotebookTextDiffEditor, cell: SingleSideDiffElementViewModel, @@ -931,9 +1055,16 @@ export class DeletedElement extends SingleSideDiffElement { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, - + @ITextResourceConfigurationService textConfigurationService: ITextResourceConfigurationService, ) { - super(notebookEditor, cell, templateData, 'left', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'left', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService, textConfigurationService); + } + + get nestedCellViewModel() { + return this.cell.original!; + } + get readonly() { + return true; } styleContainer(container: HTMLElement) { @@ -941,48 +1072,21 @@ export class DeletedElement extends SingleSideDiffElement { container.classList.add('removed'); } - updateSourceEditor(): void { - const originalCell = this.cell.original!; - const lineCount = originalCell.textModel.textBuffer.getLineCount(); - const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + fixedEditorPadding.top + fixedEditorPadding.bottom; - - this._editor = this.templateData.sourceEditor; - this._editor.layout({ - width: (this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN) / 2 - 18, - height: editorHeight - }); - - this.cell.editorHeight = editorHeight; - - this._register(this._editor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this.cell.layoutInfo.editorHeight !== e.contentHeight) { - this.cell.editorHeight = e.contentHeight; - } - })); - - this.textModelService.createModelReference(originalCell.uri).then(ref => { - if (this._isDisposed) { - return; - } - - this._register(ref); - - const textModel = ref.object.textEditorModel; - this._editor.setModel(textModel); - this.cell.editorHeight = this._editor.getContentHeight(); - }); - } - layout(state: IDiffElementLayoutState) { DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { - if (state.editorHeight || state.outerWidth) { + if ((state.editorHeight || state.outerWidth) && this._editor) { + this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; this._editor.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), height: this.cell.layoutInfo.editorHeight }); } + if (state.outerWidth && this._editor) { + this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; + this._editor.layout(); + } + if (state.metadataHeight || state.outerWidth) { this._metadataEditor?.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), @@ -998,13 +1102,14 @@ export class DeletedElement extends SingleSideDiffElement { } if (this._diagonalFill) { - this._diagonalFill.style.height = `${this.cell.layoutInfo.totalHeight - 32}px`; + this._diagonalFill.style.height = `${this.calculateDiagonalFillHeight()}px`; } this.layoutNotebookCell(); }); } + _buildOutputRendererContainer() { if (!this._outputViewContainer) { this._outputViewContainer = DOM.append(this._outputInfoContainer, DOM.$('.output-view-container')); @@ -1068,7 +1173,6 @@ export class DeletedElement extends SingleSideDiffElement { } export class InsertElement extends SingleSideDiffElement { - private _editor!: CodeEditorWidget; constructor( notebookEditor: INotebookTextDiffEditor, cell: SingleSideDiffElementViewModel, @@ -1083,8 +1187,15 @@ export class InsertElement extends SingleSideDiffElement { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, + @ITextResourceConfigurationService textConfigurationService: ITextResourceConfigurationService, ) { - super(notebookEditor, cell, templateData, 'right', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'right', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService, textConfigurationService); + } + get nestedCellViewModel() { + return this.cell.modified!; + } + get readonly() { + return false; } styleContainer(container: HTMLElement): void { @@ -1092,42 +1203,6 @@ export class InsertElement extends SingleSideDiffElement { container.classList.add('inserted'); } - updateSourceEditor(): void { - const modifiedCell = this.cell.modified!; - const lineCount = modifiedCell.textModel.textBuffer.getLineCount(); - const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + fixedEditorPadding.top + fixedEditorPadding.bottom; - - this._editor = this.templateData.sourceEditor; - this._editor.layout( - { - width: (this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN) / 2 - 18, - height: editorHeight - } - ); - this._editor.updateOptions({ readOnly: false }); - this.cell.editorHeight = editorHeight; - - this._register(this._editor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this.cell.layoutInfo.editorHeight !== e.contentHeight) { - this.cell.editorHeight = e.contentHeight; - } - })); - - this.textModelService.createModelReference(modifiedCell.uri).then(ref => { - if (this._isDisposed) { - return; - } - - this._register(ref); - - const textModel = ref.object.textEditorModel; - this._editor.setModel(textModel); - this._editor.restoreViewState(this.cell.getSourceEditorViewState() as editorCommon.ICodeEditorViewState); - this.cell.editorHeight = this._editor.getContentHeight(); - }); - } - _buildOutputRendererContainer() { if (!this._outputViewContainer) { this._outputViewContainer = DOM.append(this._outputInfoContainer, DOM.$('.output-view-container')); @@ -1179,13 +1254,19 @@ export class InsertElement extends SingleSideDiffElement { layout(state: IDiffElementLayoutState) { DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { - if (state.editorHeight || state.outerWidth) { + if ((state.editorHeight || state.outerWidth) && this._editor) { + this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; this._editor.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), height: this.cell.layoutInfo.editorHeight }); } + if (state.outerWidth && this._editor) { + this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; + this._editor.layout(); + } + if (state.metadataHeight || state.outerWidth) { this._metadataEditor?.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), @@ -1203,7 +1284,7 @@ export class InsertElement extends SingleSideDiffElement { this.layoutNotebookCell(); 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`; + this._diagonalFill.style.height = `${this.calculateDiagonalFillHeight()}px`; } }); } @@ -1220,8 +1301,6 @@ export class InsertElement extends SingleSideDiffElement { export class ModifiedElement extends AbstractElementRenderer { private _editor?: DiffEditorWidget; private _editorViewStateChanged: boolean; - private _editorContainer!: HTMLElement; - private _inputToolbarContainer!: HTMLElement; protected _toolbar!: ToolBar; protected _menu!: IMenu; @@ -1242,8 +1321,9 @@ export class ModifiedElement extends AbstractElementRenderer { @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, + @ITextResourceConfigurationService textConfigurationService: ITextResourceConfigurationService, ) { - super(notebookEditor, cell, templateData, 'full', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService); + super(notebookEditor, cell, templateData, 'full', instantiationService, languageService, modelService, textModelService, contextMenuService, keybindingService, notificationService, menuService, contextKeyService, configurationService, textConfigurationService); this.cell = cell; this.templateData = templateData; this._editorViewStateChanged = false; @@ -1256,6 +1336,15 @@ export class ModifiedElement extends AbstractElementRenderer { container.classList.remove('inserted', 'removed'); } + override buildBody(): void { + super.buildBody(); + if (this.cell.displayIconToHideUnmodifiedCells) { + this._register(this.templateData.marginOverlay.onAction(() => this.cell.hideUnchangedCells())); + this.templateData.marginOverlay.show(); + } else { + this.templateData.marginOverlay.hide(); + } + } _disposeMetadata() { this.cell.metadataStatusHeight = 0; this.cell.metadataHeight = 0; @@ -1436,6 +1525,7 @@ export class ModifiedElement extends AbstractElementRenderer { originalEditor: getOptimizedNestedCodeEditorWidgetOptions(), modifiedEditor: getOptimizedNestedCodeEditorWidgetOptions() }); + this._register(this._outputMetadataEditor); const originalOutputMetadataSource = JSON.stringify(this.cell.original.outputs[0].metadata ?? {}, undefined, '\t'); const modifiedOutputMetadataSource = JSON.stringify(this.cell.modified.outputs[0].metadata ?? {}, undefined, '\t'); @@ -1476,7 +1566,11 @@ export class ModifiedElement extends AbstractElementRenderer { this._outputLeftView?.showOutputs(); this._outputRightView?.showOutputs(); - this._outputMetadataEditor?.layout(); + this._outputMetadataEditor?.layout({ + width: this._editor?.getViewWidth() || this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), + height: this.cell.layoutInfo.outputMetadataHeight + }); + this._decorate(); } } @@ -1491,64 +1585,117 @@ export class ModifiedElement extends AbstractElementRenderer { } updateSourceEditor(): void { + this._cellHeaderContainer = this.templateData.cellHeaderContainer; + this._cellHeaderContainer.style.display = 'flex'; + this._cellHeaderContainer.innerText = ''; const modifiedCell = this.cell.modified; - const lineCount = modifiedCell.textModel.textBuffer.getLineCount(); - const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - - const editorHeight = this.cell.layoutInfo.editorHeight !== 0 ? this.cell.layoutInfo.editorHeight : lineCount * lineHeight + fixedEditorPadding.top + fixedEditorPadding.bottom; this._editorContainer = this.templateData.editorContainer; - this._editor = this.templateData.sourceEditor; - this._editorContainer.classList.add('diff'); - this._editor.layout({ - width: this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN, - height: editorHeight - }); + const renderSourceEditor = () => { + if (this.cell.cellFoldingState === PropertyFoldingState.Collapsed) { + this._editorContainer.style.display = 'none'; + this.cell.editorHeight = 0; + return; + } - this._editorContainer.style.height = `${editorHeight}px`; + const lineCount = modifiedCell.textModel.textBuffer.getLineCount(); + const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; + const editorHeight = this.cell.layoutInfo.editorHeight !== 0 ? this.cell.layoutInfo.editorHeight : (lineCount * lineHeight) + fixedEditorPadding.top + fixedEditorPadding.bottom; - this._register(this._editor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this.cell.layoutInfo.editorHeight !== e.contentHeight) { - this.cell.editorHeight = e.contentHeight; + this._editorContainer.style.height = `${editorHeight}px`; + this._editorContainer.style.display = 'block'; + + if (this._editor) { + const contentHeight = this._editor.getContentHeight(); + if (contentHeight >= 0) { + this.cell.editorHeight = contentHeight; + } + return; } - })); - this._initializeSourceDiffEditor(); + this._editor = this.templateData.sourceEditor; + // If there is only 1 line, then ensure we have the necessary padding to display the button for whitespaces. + // E.g. assume we have a cell with 1 line and we add some whitespace, + // Then diff editor displays the button `Show Whitespace Differences`, however with 12 paddings on the top, the + // button can get cut off. + if (lineCount === 1) { + this._editor.updateOptions({ + padding: fixedEditorPaddingSingleLineCells + }); + } + this._editor.layout({ + width: this.notebookEditor.getLayoutInfo().width - 2 * DIFF_CELL_MARGIN, + height: editorHeight + }); + this._register(this._editor.onDidContentSizeChange((e) => { + if (this.cell.cellFoldingState === PropertyFoldingState.Expanded && e.contentHeightChanged && this.cell.layoutInfo.editorHeight !== e.contentHeight) { + this.cell.editorHeight = e.contentHeight; + } + })); + this._initializeSourceDiffEditor(); + }; + + this._cellHeader = this._register(this.instantiationService.createInstance( + PropertyHeader, + this.cell, + this._cellHeaderContainer, + this.notebookEditor, + { + updateInfoRendering: () => renderSourceEditor(), + checkIfModified: (cell) => { + return cell.modified?.textModel.getTextBufferHash() !== cell.original?.textModel.getTextBufferHash() ? { reason: undefined } : false; + }, + getFoldingState: (cell) => cell.cellFoldingState, + updateFoldingState: (cell, state) => cell.cellFoldingState = state, + unChangedLabel: 'Input', + changedLabel: 'Input changed', + prefix: 'input', + menuId: MenuId.NotebookDiffCellInputTitle + } + )); + this._cellHeader.buildHeader(); + renderSourceEditor(); + const scopedContextKeyService = this.contextKeyService.createScoped(this.templateData.inputToolbarContainer); this._register(scopedContextKeyService); const inputChanged = NOTEBOOK_DIFF_CELL_INPUT.bindTo(scopedContextKeyService); + inputChanged.set(this.cell.modified.textModel.getTextBufferHash() !== this.cell.original.textModel.getTextBufferHash()); + + const ignoreWhitespace = NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE.bindTo(scopedContextKeyService); + const ignore = this.textConfigurationService.getValue(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace'); + ignoreWhitespace.set(ignore); - this._inputToolbarContainer = this.templateData.inputToolbarContainer; this._toolbar = this.templateData.toolbar; this._toolbar.context = { cell: this.cell }; - if (this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue()) { - this._inputToolbarContainer.style.display = 'block'; - inputChanged.set(true); - } else { - this._inputToolbarContainer.style.display = 'none'; - inputChanged.set(false); - } - - this._register(this.cell.modified.textModel.onDidChangeContent(() => { - if (this.cell.modified.textModel.getValue() !== this.cell.original.textModel.getValue()) { - this._inputToolbarContainer.style.display = 'block'; - inputChanged.set(true); + const refreshToolbar = () => { + const ignore = this.textConfigurationService.getValue(this.cell.modified.uri, 'diffEditor.ignoreTrimWhitespace'); + ignoreWhitespace.set(ignore); + const hasChanges = this.cell.modified.textModel.getTextBufferHash() !== this.cell.original.textModel.getTextBufferHash(); + inputChanged.set(hasChanges); + + if (hasChanges) { + const actions: IAction[] = []; + const menu = this.menuService.getMenuActions(MenuId.NotebookDiffCellInputTitle, scopedContextKeyService, { shouldForwardArgs: true }); + createAndFillInActionBarActions(menu, actions); + this._toolbar.setActions(actions); } else { - this._inputToolbarContainer.style.display = 'none'; - inputChanged.set(false); + this._toolbar.setActions([]); } - })); + }; - const menu = this.menuService.createMenu(MenuId.NotebookDiffCellInputTitle, scopedContextKeyService); - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, actions); - this._toolbar.setActions(actions); - menu.dispose(); + this._register(this.cell.modified.textModel.onDidChangeContent(() => refreshToolbar())); + this._register(this.textConfigurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(this.cell.modified.uri, 'diffEditor') && + e.affectedKeys.has('diffEditor.ignoreTrimWhitespace')) { + refreshToolbar(); + } + })); + refreshToolbar(); } private async _initializeSourceDiffEditor() { @@ -1569,7 +1716,7 @@ export class ModifiedElement extends AbstractElementRenderer { this._editor!.setModel({ original: textModel, - modified: modifiedTextModel + modified: modifiedTextModel, }); const handleViewStateChange = () => { @@ -1582,6 +1729,7 @@ export class ModifiedElement extends AbstractElementRenderer { } }; + this.updateEditorOptionsForWhitespace(); this._register(this._editor!.getOriginalEditor().onDidChangeCursorSelection(handleViewStateChange)); this._register(this._editor!.getOriginalEditor().onDidScrollChange(handleScrollChange)); this._register(this._editor!.getModifiedEditor().onDidChangeCursorSelection(handleViewStateChange)); @@ -1595,39 +1743,67 @@ export class ModifiedElement extends AbstractElementRenderer { const contentHeight = this._editor!.getContentHeight(); this.cell.editorHeight = contentHeight; } - + private updateEditorOptionsForWhitespace() { + const editor = this._editor; + if (!editor) { + return; + } + const uri = editor.getModel()?.modified.uri || editor.getModel()?.original.uri; + if (!uri) { + return; + } + const ignoreTrimWhitespace = this.textConfigurationService.getValue(uri, 'diffEditor.ignoreTrimWhitespace'); + editor.updateOptions({ ignoreTrimWhitespace }); + + this._register(this.textConfigurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(uri, 'diffEditor') && + e.affectedKeys.has('diffEditor.ignoreTrimWhitespace')) { + const ignoreTrimWhitespace = this.textConfigurationService.getValue(uri, 'diffEditor.ignoreTrimWhitespace'); + editor.updateOptions({ ignoreTrimWhitespace }); + } + })); + } layout(state: IDiffElementLayoutState) { DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { - if (state.editorHeight) { + if (state.editorHeight && this._editor) { this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; - this._editor!.layout({ + this._editor.layout({ width: this._editor!.getViewWidth(), height: this.cell.layoutInfo.editorHeight }); } - if (state.outerWidth) { + if (state.outerWidth && this._editor) { this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; - this._editor!.layout(); + this._editor.layout(); } if (state.metadataHeight || state.outerWidth) { if (this._metadataEditorContainer) { this._metadataEditorContainer.style.height = `${this.cell.layoutInfo.metadataHeight}px`; - this._metadataEditor?.layout(); + this._metadataEditor?.layout({ + width: this._editor?.getViewWidth() || this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), + height: this.cell.layoutInfo.metadataHeight + }); } } if (state.outputTotalHeight || state.outerWidth) { if (this._outputEditorContainer) { this._outputEditorContainer.style.height = `${this.cell.layoutInfo.outputTotalHeight}px`; - this._outputEditor?.layout(); + this._outputEditor?.layout({ + width: this._editor?.getViewWidth() || this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), + height: this.cell.layoutInfo.outputTotalHeight + }); } if (this._outputMetadataContainer) { this._outputMetadataContainer.style.height = `${this.cell.layoutInfo.outputMetadataHeight}px`; this._outputMetadataContainer.style.top = `${this.cell.layoutInfo.outputTotalHeight - this.cell.layoutInfo.outputMetadataHeight}px`; - this._outputMetadataEditor?.layout(); + this._outputMetadataEditor?.layout({ + width: this._editor?.getViewWidth() || this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), + height: this.cell.layoutInfo.outputMetadataHeight + }); } } @@ -1636,6 +1812,12 @@ export class ModifiedElement extends AbstractElementRenderer { } override dispose() { + // The editor isn't disposed yet, it can be re-used. + // However the model can be disposed before the editor & that causes issues. + if (this._editor) { + this._editor.setModel(null); + } + if (this._editor && this._editorViewStateChanged) { this.cell.saveSpirceEditorViewState(this._editor.saveViewState()); } @@ -1643,3 +1825,83 @@ export class ModifiedElement extends AbstractElementRenderer { super.dispose(); } } + + +export class CollapsedCellOverlayWidget extends Disposable implements IDiffCellMarginOverlay { + private readonly _nodes = DOM.h('div.diff-hidden-cells', [ + DOM.h('div.center@content', { style: { display: 'flex' } }, [ + DOM.$('a', { + title: localize('showUnchangedCells', 'Show Unchanged Cells'), + role: 'button', + onclick: () => { this._action.fire(); } + }, + ...renderLabelWithIcons('$(unfold)'))] + ), + ]); + + private readonly _action = this._register(new Emitter()); + public readonly onAction = this._action.event; + constructor( + private readonly container: HTMLElement + ) { + super(); + + this._nodes.root.style.display = 'none'; + container.appendChild(this._nodes.root); + } + + public show() { + this._nodes.root.style.display = 'block'; + } + + public hide() { + this._nodes.root.style.display = 'none'; + } + + public override dispose() { + this.hide(); + this.container.removeChild(this._nodes.root); + DOM.reset(this._nodes.root); + super.dispose(); + } +} + +export class UnchangedCellOverlayWidget extends Disposable implements IDiffCellMarginOverlay { + private readonly _nodes = DOM.h('div.diff-hidden-cells', [ + DOM.h('div.center@content', { style: { display: 'flex' } }, [ + DOM.$('a', { + title: localize('hideUnchangedCells', 'Hide Unchanged Cells'), + role: 'button', + onclick: () => { this._action.fire(); } + }, + ...renderLabelWithIcons('$(fold)') + ), + ] + ), + ]); + + private readonly _action = this._register(new Emitter()); + public readonly onAction = this._action.event; + constructor( + private readonly container: HTMLElement + ) { + super(); + + this._nodes.root.style.display = 'none'; + container.appendChild(this._nodes.root); + } + + public show() { + this._nodes.root.style.display = 'block'; + } + + public hide() { + this._nodes.root.style.display = 'none'; + } + public override dispose() { + this.hide(); + this.container.removeChild(this._nodes.root); + DOM.reset(this._nodes.root); + super.dispose(); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts index 00c89e90..9121e3fb 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { DiffElementViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { DiffElementCellViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { DiffSide, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { ICellOutputViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -33,7 +33,7 @@ export class OutputElement extends Disposable { private _notebookTextModel: NotebookTextModel, private _notebookService: INotebookService, private _quickInputService: IQuickInputService, - private _diffElementViewModel: DiffElementViewModelBase, + private _diffElementViewModel: DiffElementCellViewModelBase, private _diffSide: DiffSide, private _nestedCell: DiffNestedCellViewModel, private _outputContainer: HTMLElement, @@ -155,7 +155,8 @@ export class OutputElement extends Disposable { description: index === currIndex ? nls.localize('curruentActiveMimeType', "Currently Active") : undefined })); - const picker = this._quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const picker = disposables.add(this._quickInputService.createQuickPick()); picker.items = items; picker.activeItems = items.filter(item => !!item.picked); picker.placeholder = items.length !== mimeTypes.length @@ -163,10 +164,10 @@ export class OutputElement extends Disposable { : nls.localize('promptChooseMimeType.placeHolder', "Select mimetype to render for current output"); const pick = await new Promise(resolve => { - picker.onDidAccept(() => { + disposables.add(picker.onDidAccept(() => { resolve(picker.selectedItems.length === 1 ? (picker.selectedItems[0] as IMimeTypeRenderer).index : undefined); - picker.dispose(); - }); + disposables.dispose(); + })); picker.show(); }); @@ -181,7 +182,7 @@ export class OutputElement extends Disposable { this.resizeListener.clear(); const element = this.domNode; if (element) { - element.parentElement?.removeChild(element); + element.remove(); this._notebookEditor.removeInset( this._diffElementViewModel, this._nestedCell, @@ -228,7 +229,7 @@ export class OutputContainer extends Disposable { constructor( private _editor: INotebookTextDiffEditor, private _notebookTextModel: NotebookTextModel, - private _diffElementViewModel: DiffElementViewModelBase, + private _diffElementViewModel: DiffElementCellViewModelBase, private _nestedCellViewModel: DiffNestedCellViewModel, private _diffSide: DiffSide, private _outputContainer: HTMLElement, @@ -259,7 +260,7 @@ export class OutputContainer extends Disposable { // already removed removedKeys.push(key); // remove element from DOM - this._outputContainer.removeChild(value.domNode); + value.domNode.remove(); this._editor.removeInset(this._diffElementViewModel, this._nestedCellViewModel, key, this._diffSide); } }); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts index fd3336fc..ad8e1461 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -17,8 +17,10 @@ import { NotebookDiffEditorEventDispatcher, NotebookDiffViewEventType } from 'vs import { CellDiffViewModelLayoutChangeEvent, DIFF_CELL_MARGIN, DiffSide, IDiffElementLayoutInfo } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { CellLayoutState, IGenericCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { ICellOutput, INotebookTextModel, IOutputDto, IOutputItemDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; export enum PropertyFoldingState { Expanded, @@ -33,15 +35,77 @@ interface ILayoutInfoDelta extends ILayoutInfoDelta0 { recomputeOutput?: boolean; } +export type IDiffElementViewModelBase = DiffElementCellViewModelBase | DiffElementPlaceholderViewModel; + export abstract class DiffElementViewModelBase extends Disposable { - public metadataFoldingState: PropertyFoldingState; - public outputFoldingState: PropertyFoldingState; protected _layoutInfoEmitter = this._register(new Emitter()); onDidLayoutChange = this._layoutInfoEmitter.event; + constructor( + public readonly mainDocumentTextModel: INotebookTextModel, + public readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher, + public readonly initData: { + metadataStatusHeight: number; + outputStatusHeight: number; + fontInfo: FontInfo | undefined; + } + ) { + super(); + + this._register(this.editorEventDispatcher.onDidChangeLayout(e => this._layoutInfoEmitter.fire({ outerWidth: true }))); + } + + abstract layoutChange(): void; + abstract getHeight(lineHeight: number): number; + abstract get totalHeight(): number; +} + +export class DiffElementPlaceholderViewModel extends DiffElementViewModelBase { + readonly type: 'placeholder' = 'placeholder'; + public hiddenCells: DiffElementCellViewModelBase[] = []; + protected _unfoldHiddenCells = this._register(new Emitter()); + onUnfoldHiddenCells = this._unfoldHiddenCells.event; + + constructor( + mainDocumentTextModel: INotebookTextModel, + editorEventDispatcher: NotebookDiffEditorEventDispatcher, + initData: { + metadataStatusHeight: number; + outputStatusHeight: number; + fontInfo: FontInfo | undefined; + } + ) { + super(mainDocumentTextModel, editorEventDispatcher, initData); + + } + get totalHeight() { + return 24 + (2 * DIFF_CELL_MARGIN); + } + getHeight(_: number): number { + return this.totalHeight; + } + override layoutChange(): void { + // + } + showHiddenCells() { + this._unfoldHiddenCells.fire(); + } +} + +export abstract class DiffElementCellViewModelBase extends DiffElementViewModelBase { + public cellFoldingState: PropertyFoldingState; + public metadataFoldingState: PropertyFoldingState; + public outputFoldingState: PropertyFoldingState; protected _stateChangeEmitter = this._register(new Emitter<{ renderOutput: boolean }>()); onDidStateChange = this._stateChangeEmitter.event; protected _layoutInfo!: IDiffElementLayoutInfo; + public displayIconToHideUnmodifiedCells?: boolean; + private _hideUnchangedCells = this._register(new Emitter()); + public onHideUnchangedCells = this._hideUnchangedCells.event; + + hideUnchangedCells() { + this._hideUnchangedCells.fire(); + } set rawOutputHeight(height: number) { this._layout({ rawOutputHeight: Math.min(OUTPUT_EDITOR_HEIGHT_MAGIC, height) }); } @@ -114,45 +178,55 @@ export abstract class DiffElementViewModelBase extends Disposable { return this._layoutInfo; } + get totalHeight() { + return this.layoutInfo.totalHeight; + } + private _sourceEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null; private _outputEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null; private _metadataEditorViewState: editorCommon.ICodeEditorViewState | editorCommon.IDiffEditorViewState | null = null; + public readonly original: DiffNestedCellViewModel | undefined; + public readonly modified: DiffNestedCellViewModel | undefined; constructor( - readonly mainDocumentTextModel: INotebookTextModel, - readonly original: DiffNestedCellViewModel | undefined, - readonly modified: DiffNestedCellViewModel | undefined, + mainDocumentTextModel: INotebookTextModel, + original: NotebookCellTextModel | undefined, + modified: NotebookCellTextModel | undefined, readonly type: 'unchanged' | 'insert' | 'delete' | 'modified', - readonly editorEventDispatcher: NotebookDiffEditorEventDispatcher, - readonly initData: { + editorEventDispatcher: NotebookDiffEditorEventDispatcher, + initData: { metadataStatusHeight: number; outputStatusHeight: number; fontInfo: FontInfo | undefined; - } + }, + notebookService: INotebookService ) { - super(); + super(mainDocumentTextModel, editorEventDispatcher, initData); + this.original = original ? this._register(new DiffNestedCellViewModel(original, notebookService)) : undefined; + this.modified = modified ? this._register(new DiffNestedCellViewModel(modified, notebookService)) : undefined; const editorHeight = this._estimateEditorHeight(initData.fontInfo); + const cellStatusHeight = 25; this._layoutInfo = { width: 0, editorHeight: editorHeight, editorMargin: 0, metadataHeight: 0, + cellStatusHeight, metadataStatusHeight: 25, rawOutputHeight: 0, outputTotalHeight: 0, outputStatusHeight: 25, outputMetadataHeight: 0, bodyMargin: 32, - totalHeight: 82 + editorHeight, + totalHeight: 82 + cellStatusHeight + editorHeight, layoutState: CellLayoutState.Uninitialized }; + this.cellFoldingState = modified?.getTextBufferHash() !== original?.getTextBufferHash() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed; this.metadataFoldingState = PropertyFoldingState.Collapsed; this.outputFoldingState = PropertyFoldingState.Collapsed; - this._register(this.editorEventDispatcher.onDidChangeLayout(e => { - this._layoutInfoEmitter.fire({ outerWidth: true }); - })); + this._register(this.editorEventDispatcher.onDidChangeLayout(e => this._layoutInfoEmitter.fire({ outerWidth: true }))); } layoutChange() { @@ -185,6 +259,7 @@ export abstract class DiffElementViewModelBase extends Disposable { const editorHeight = delta.editorHeight !== undefined ? delta.editorHeight : this._layoutInfo.editorHeight; const editorMargin = delta.editorMargin !== undefined ? delta.editorMargin : this._layoutInfo.editorMargin; const metadataHeight = delta.metadataHeight !== undefined ? delta.metadataHeight : this._layoutInfo.metadataHeight; + const cellStatusHeight = delta.cellStatusHeight !== undefined ? delta.cellStatusHeight : this._layoutInfo.cellStatusHeight; const metadataStatusHeight = delta.metadataStatusHeight !== undefined ? delta.metadataStatusHeight : this._layoutInfo.metadataStatusHeight; const rawOutputHeight = delta.rawOutputHeight !== undefined ? delta.rawOutputHeight : this._layoutInfo.rawOutputHeight; const outputStatusHeight = delta.outputStatusHeight !== undefined ? delta.outputStatusHeight : this._layoutInfo.outputStatusHeight; @@ -194,6 +269,7 @@ export abstract class DiffElementViewModelBase extends Disposable { const totalHeight = editorHeight + editorMargin + + cellStatusHeight + metadataHeight + metadataStatusHeight + outputHeight @@ -205,6 +281,7 @@ export abstract class DiffElementViewModelBase extends Disposable { editorHeight: editorHeight, editorMargin: editorMargin, metadataHeight: metadataHeight, + cellStatusHeight, metadataStatusHeight: metadataStatusHeight, outputTotalHeight: outputHeight, outputStatusHeight: outputStatusHeight, @@ -239,6 +316,11 @@ export abstract class DiffElementViewModelBase extends Disposable { somethingChanged = true; } + if (newLayout.cellStatusHeight !== this._layoutInfo.cellStatusHeight) { + changeEvent.cellStatusHeight = true; + somethingChanged = true; + } + if (newLayout.metadataStatusHeight !== this._layoutInfo.metadataStatusHeight) { changeEvent.metadataStatusHeight = true; somethingChanged = true; @@ -288,6 +370,7 @@ export abstract class DiffElementViewModelBase extends Disposable { const totalHeight = editorHeight + this._layoutInfo.editorMargin + this._layoutInfo.metadataHeight + + this._layoutInfo.cellStatusHeight + this._layoutInfo.metadataStatusHeight + this._layoutInfo.outputTotalHeight + this._layoutInfo.outputStatusHeight @@ -329,6 +412,7 @@ export abstract class DiffElementViewModelBase extends Disposable { this.editorEventDispatcher.emit([{ type: NotebookDiffViewEventType.CellLayoutChanged, source: this._layoutInfo }]); } + abstract checkIfInputModified(): false | { reason: string | undefined }; abstract checkIfOutputsModified(): false | { reason: string | undefined }; abstract checkMetadataIfModified(): false | { reason: string | undefined }; abstract isOutputEmpty(): boolean; @@ -372,7 +456,7 @@ export abstract class DiffElementViewModelBase extends Disposable { } } -export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { +export class SideBySideDiffElementViewModel extends DiffElementCellViewModelBase { get originalDocument() { return this.otherDocumentTextModel; } @@ -381,22 +465,23 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { return this.mainDocumentTextModel; } - override readonly original: DiffNestedCellViewModel; - override readonly modified: DiffNestedCellViewModel; + override readonly original!: DiffNestedCellViewModel; + override readonly modified!: DiffNestedCellViewModel; override readonly type: 'unchanged' | 'modified'; constructor( mainDocumentTextModel: NotebookTextModel, readonly otherDocumentTextModel: NotebookTextModel, - original: DiffNestedCellViewModel, - modified: DiffNestedCellViewModel, + original: NotebookCellTextModel, + modified: NotebookCellTextModel, type: 'unchanged' | 'modified', editorEventDispatcher: NotebookDiffEditorEventDispatcher, initData: { metadataStatusHeight: number; outputStatusHeight: number; fontInfo: FontInfo | undefined; - } + }, + notebookService: INotebookService ) { super( mainDocumentTextModel, @@ -404,12 +489,12 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { modified, type, editorEventDispatcher, - initData); + initData, + notebookService); - this.original = original; - this.modified = modified; this.type = type; + this.cellFoldingState = this.modified.textModel.getValue() !== this.original.textModel.getValue() ? PropertyFoldingState.Expanded : PropertyFoldingState.Collapsed; this.metadataFoldingState = PropertyFoldingState.Collapsed; this.outputFoldingState = PropertyFoldingState.Collapsed; @@ -435,7 +520,9 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { const modifiedMedataRaw = Object.assign({}, this.modified.metadata); const originalCellMetadata = this.original.metadata; for (const key of cellMetadataKeys) { - modifiedMedataRaw[key] = originalCellMetadata[key]; + if (key in originalCellMetadata) { + modifiedMedataRaw[key] = originalCellMetadata[key]; + } } this.modified.textModel.metadata = modifiedMedataRaw; @@ -443,6 +530,14 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { })); } + override checkIfInputModified(): false | { reason: string | undefined } { + if (this.original.textModel.getTextBufferHash() === this.modified.textModel.getTextBufferHash()) { + return false; + } + return { + reason: 'Cell content has changed', + }; + } checkIfOutputsModified() { if (this.mainDocumentTextModel.transientOptions.transientOutputs) { return false; @@ -491,6 +586,7 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { return this._layoutInfo.editorHeight + this._layoutInfo.editorMargin + this._layoutInfo.metadataHeight + + this._layoutInfo.cellStatusHeight + this._layoutInfo.metadataStatusHeight + this._layoutInfo.outputStatusHeight + this._layoutInfo.bodyMargin / 2 @@ -528,7 +624,7 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { } } -export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { +export class SingleSideDiffElementViewModel extends DiffElementCellViewModelBase { get cellViewModel() { return this.type === 'insert' ? this.modified! : this.original!; } @@ -554,17 +650,18 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { constructor( mainDocumentTextModel: NotebookTextModel, readonly otherDocumentTextModel: NotebookTextModel, - original: DiffNestedCellViewModel | undefined, - modified: DiffNestedCellViewModel | undefined, + original: NotebookCellTextModel | undefined, + modified: NotebookCellTextModel | undefined, type: 'insert' | 'delete', editorEventDispatcher: NotebookDiffEditorEventDispatcher, initData: { metadataStatusHeight: number; outputStatusHeight: number; fontInfo: FontInfo | undefined; - } + }, + notebookService: INotebookService ) { - super(mainDocumentTextModel, original, modified, type, editorEventDispatcher, initData); + super(mainDocumentTextModel, original, modified, type, editorEventDispatcher, initData, notebookService); this.type = type; this._register(this.cellViewModel.onDidChangeOutputLayout(() => { @@ -572,6 +669,12 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { })); } + override checkIfInputModified(): false | { reason: string | undefined } { + return { + reason: 'Cell content has changed', + }; + } + getNestedCellViewModel(diffSide: DiffSide): DiffNestedCellViewModel { return this.type === 'insert' ? this.modified! : this.original!; } @@ -599,6 +702,7 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { return this._layoutInfo.editorHeight + this._layoutInfo.editorMargin + this._layoutInfo.metadataHeight + + this._layoutInfo.cellStatusHeight + this._layoutInfo.metadataStatusHeight + this._layoutInfo.outputStatusHeight + this._layoutInfo.bodyMargin / 2 @@ -720,7 +824,12 @@ export function getFormattedMetadataJSON(documentTextModel: INotebookTextModel, language, ...filteredMetadata }; - + // Give preference to the language we have been given. + // Metadata can contain `language` due to round-tripping of cell metadata. + // I.e. we add it here, and then from SCM when we revert the cell, we get this same metadata back with the `language` property. + if (language) { + obj.language = language; + } const metadataSource = toFormattedString(obj, {}); return metadataSource; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css index a0a89a75..3ee2c855 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css @@ -25,6 +25,11 @@ flex-direction: row; } +.notebook-text-diff-editor .cell-placeholder-body { + display: flex; + flex-direction: row; +} + .notebook-text-diff-editor .webview-cover { user-select: initial; -webkit-user-select: initial; @@ -99,6 +104,7 @@ width: 50%; } +.notebook-text-diff-editor .cell-diff-editor-container .input-header-container, .notebook-text-diff-editor .cell-diff-editor-container .output-header-container, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container { display: flex; @@ -107,13 +113,15 @@ cursor: default; } +.notebook-text-diff-editor .cell-diff-editor-container .input-header-container .property-folding-indicator .codicon, .notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-folding-indicator .codicon, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-folding-indicator .codicon { visibility: visible; - padding: 4px 0 0 10px; + padding: 4px 0 0 6px; cursor: pointer; } +.notebook-text-diff-editor .cell-diff-editor-container .input-header-container, .notebook-text-diff-editor .cell-diff-editor-container .output-header-container, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container { display: flex; @@ -121,22 +129,32 @@ align-items: center; } +.notebook-text-diff-editor .cell-diff-editor-container .input-header-container, +.notebook-text-diff-editor .cell-diff-editor-container .output-header-container, +.notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container { + cursor: pointer; +} + +.notebook-text-diff-editor .cell-diff-editor-container .input-header-container .property-toolbar, .notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-toolbar, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-toolbar { margin-left: auto; } +.notebook-text-diff-editor .cell-diff-editor-container .input-header-container .property-status, .notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-status, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-status { font-size: 12px; } +.notebook-text-diff-editor .cell-diff-editor-container .input-header-container .property-status span, .notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-status span, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-status span { - margin: 0 0 0 8px; + margin: 0 0 0 5px; line-height: 21px; } +.notebook-text-diff-editor .cell-diff-editor-container .input-header-container .property-status span.property-description, .notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-status span.property-description, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-status span.property-description { font-style: italic; @@ -292,7 +310,7 @@ width: 15px !important; } -.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .scrollbar.visible { +.monaco-workbench .notebook-text-diff-editor > .monaco-list > .monaco-scrollable-element > .scrollbar.visible { z-index: var(--z-index-notebook-scrollbar); cursor: default; } @@ -357,6 +375,7 @@ .notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .source-container, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .source-container .monaco-editor .margin, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .source-container .monaco-editor .monaco-editor-background, +.notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .input-header-container, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .metadata-editor-container, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .metadata-editor-container .monaco-editor .margin, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.inserted .metadata-editor-container .monaco-editor .monaco-editor-background, @@ -374,6 +393,7 @@ .notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .source-container, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .source-container .monaco-editor .margin, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .source-container .monaco-editor .monaco-editor-background, +.notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .input-header-container, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .metadata-editor-container, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .metadata-editor-container .monaco-editor .margin, .notebook-text-diff-editor .cell-body .cell-diff-editor-container.removed .metadata-editor-container .monaco-editor .monaco-editor-background, @@ -390,3 +410,60 @@ .notebook-text-diff-editor .cell-body .cell-diff-editor-container .source-container .monaco-editor .monaco-editor-background { background: var(--vscode-notebook-cellEditorBackground, var(--vscode-editor-background)); } + +/** Overlay to hide the unchanged cells */ +.notebook-text-diff-editor .cell-body.full div.diff-hidden-cells { + position: absolute; + left: 0; + + font-size: 13px; + line-height: 14px; +} + +.notebook-text-diff-editor .cell-body.full div.diff-hidden-cells .center { + color: var(--vscode-diffEditor-unchangedRegionForeground); + overflow: hidden; + display: block; + white-space: nowrap; + + height: 24px; +} + +.notebook-text-diff-editor .cell-body.full div.diff-hidden-cells .center span.codicon { + vertical-align: middle; +} + +.notebook-text-diff-editor .cell-body.full div.diff-hidden-cells .center a:hover .codicon { + cursor: pointer; +} + +/** Overlay to unhide the unchanged cells */ +.notebook-text-diff-editor .cell-placeholder-body { + background: var(--vscode-diffEditor-unchangedRegionBackground); + color: var(--vscode-diffEditor-unchangedRegionForeground); + min-height: 24px; +} + +.notebook-text-diff-editor .cell-placeholder-body div.diff-hidden-cells .center { + overflow: hidden; + display: block; + text-overflow: ellipsis; + white-space: nowrap; + + height: 24px; +} + +.notebook-text-diff-editor .cell-placeholder-body .text { + /** Add a gap between text and the unfold icon */ + padding-left: 2px; +} + +.notebook-text-diff-editor .cell-placeholder-body div.diff-hidden-cells .center span.codicon, +.notebook-text-diff-editor .cell-placeholder-body .text { + vertical-align: middle; +} + +.notebook-text-diff-editor .cell-placeholder-body div.diff-hidden-cells .center a:hover .codicon { + cursor: pointer; + color: var(--vscode-editorLink-activeForeground) !important; +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index f7cfcf23..61d88e86 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -10,11 +10,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; -import { DiffElementViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; -import { INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { DiffElementCellViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE_KEY, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED, NOTEBOOK_DIFF_HAS_UNCHANGED_CELLS, NOTEBOOK_DIFF_ITEM_DIFF_STATE, NOTEBOOK_DIFF_ITEM_KIND, NOTEBOOK_DIFF_UNCHANGED_CELLS_HIDDEN } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/common/notebookDiffEditorInput'; -import { nextChangeIcon, openAsTextIcon, previousChangeIcon, renderOutputIcon, revertIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { nextChangeIcon, openAsTextIcon, previousChangeIcon, renderOutputIcon, revertIcon, toggleWhitespace } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -22,7 +22,12 @@ import { ICommandActionTitle } from 'vs/platform/action/common/action'; import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { CellEditType, NOTEBOOK_DIFF_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, ICellEditOperation, NOTEBOOK_DIFF_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { NotebookMultiTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor'; +import { Codicon } from 'vs/base/common/codicons'; +import type { URI } from 'vs/base/common/uri'; +import { TextEditorSelectionRevealType, type ITextEditorOptions } from 'vs/platform/editor/common/editor'; // ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID) @@ -32,11 +37,11 @@ registerAction2(class extends Action2 { id: 'notebook.diff.switchToText', icon: openAsTextIcon, title: localize2('notebook.diff.switchToText', 'Open Text Diff Editor'), - precondition: ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), + precondition: ContextKeyExpr.or(ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID)), menu: [{ id: MenuId.EditorTitle, group: 'navigation', - when: ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID) + when: ContextKeyExpr.or(ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID)), }] }); } @@ -45,7 +50,10 @@ registerAction2(class extends Action2 { const editorService = accessor.get(IEditorService); const activeEditor = editorService.activeEditorPane; - if (activeEditor && activeEditor instanceof NotebookTextDiffEditor) { + if (!activeEditor) { + return; + } + if (activeEditor instanceof NotebookTextDiffEditor || activeEditor instanceof NotebookMultiTextDiffEditor) { const diffEditorInput = activeEditor.input as NotebookDiffEditorInput; await editorService.openEditor( @@ -62,12 +70,228 @@ registerAction2(class extends Action2 { } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.diffEditor.showUnchangedCells', + title: localize2('showUnchangedCells', 'Show Unchanged Cells'), + icon: Codicon.unfold, + precondition: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID), ContextKeyExpr.has(NOTEBOOK_DIFF_HAS_UNCHANGED_CELLS.key)), + menu: { + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID), ContextKeyExpr.has(NOTEBOOK_DIFF_HAS_UNCHANGED_CELLS.key), ContextKeyExpr.equals(NOTEBOOK_DIFF_UNCHANGED_CELLS_HIDDEN.key, true)), + id: MenuId.EditorTitle, + order: 22, + group: 'navigation', + }, + }); + } + + run(accessor: ServicesAccessor, ...args: unknown[]): void { + const activeEditor = accessor.get(IEditorService).activeEditorPane; + if (!activeEditor) { + return; + } + if (activeEditor instanceof NotebookMultiTextDiffEditor) { + activeEditor.showUnchanged(); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.diffEditor.hideUnchangedCells', + title: localize2('hideUnchangedCells', 'Hide Unchanged Cells'), + icon: Codicon.fold, + precondition: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID), ContextKeyExpr.has(NOTEBOOK_DIFF_HAS_UNCHANGED_CELLS.key)), + menu: { + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID), ContextKeyExpr.has(NOTEBOOK_DIFF_HAS_UNCHANGED_CELLS.key), ContextKeyExpr.equals(NOTEBOOK_DIFF_UNCHANGED_CELLS_HIDDEN.key, false)), + id: MenuId.EditorTitle, + order: 22, + group: 'navigation', + }, + }); + } + + run(accessor: ServicesAccessor, ...args: unknown[]): void { + const activeEditor = accessor.get(IEditorService).activeEditorPane; + if (!activeEditor) { + return; + } + if (activeEditor instanceof NotebookMultiTextDiffEditor) { + activeEditor.hideUnchanged(); + } + } +}); + +registerAction2(class GoToFileAction extends Action2 { + constructor() { + super({ + id: 'notebook.diffEditor.2.goToCell', + title: localize2('goToCell', 'Go To Cell'), + icon: Codicon.goToFile, + menu: { + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID), ContextKeyExpr.equals(NOTEBOOK_DIFF_ITEM_KIND.key, 'Cell'), ContextKeyExpr.notEquals(NOTEBOOK_DIFF_ITEM_DIFF_STATE.key, 'delete')), + id: MenuId.MultiDiffEditorFileToolbar, + order: 0, + group: 'navigation', + }, + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const uri = args[0] as URI; + const editorService = accessor.get(IEditorService); + const activeEditorPane = editorService.activeEditorPane; + if (!(activeEditorPane instanceof NotebookMultiTextDiffEditor)) { + return; + } + + await editorService.openEditor({ + resource: uri, + options: { + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, + } satisfies ITextEditorOptions, + }); + } +}); + +const revertInput = localize('notebook.diff.cell.revertInput', "Revert Input"); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.diffEditor.2.cell.revertInput', + title: revertInput, + icon: revertIcon, + menu: { + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID), ContextKeyExpr.equals(NOTEBOOK_DIFF_ITEM_KIND.key, 'Cell'), ContextKeyExpr.equals(NOTEBOOK_DIFF_ITEM_DIFF_STATE.key, 'modified')), + id: MenuId.MultiDiffEditorFileToolbar, + order: 2, + group: 'navigation', + }, + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const uri = args[0] as URI; + const editorService = accessor.get(IEditorService); + const activeEditorPane = editorService.activeEditorPane; + if (!(activeEditorPane instanceof NotebookMultiTextDiffEditor)) { + return; + } + + const item = activeEditorPane.getDiffElementViewModel(uri); + if (item && item instanceof SideBySideDiffElementViewModel) { + const modified = item.modified; + const original = item.original; + + if (!original || !modified) { + return; + } + + const bulkEditService = accessor.get(IBulkEditService); + await bulkEditService.apply([ + new ResourceTextEdit(modified.uri, { range: modified.textModel.getFullModelRange(), text: original.textModel.getValue() }), + ], { quotableLabel: 'Revert Notebook Cell Content Change' }); + } + } +}); + +const revertOutputs = localize('notebook.diff.cell.revertOutputs', "Revert Outputs"); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: 'notebook.diffEditor.2.cell.revertOutputs', + title: revertOutputs, + icon: revertIcon, + f1: false, + menu: { + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID), ContextKeyExpr.equals(NOTEBOOK_DIFF_ITEM_KIND.key, 'Output'), ContextKeyExpr.equals(NOTEBOOK_DIFF_ITEM_DIFF_STATE.key, 'modified')), + id: MenuId.MultiDiffEditorFileToolbar, + order: 2, + group: 'navigation', + }, + } + ); + } + async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const uri = args[0] as URI; + const editorService = accessor.get(IEditorService); + const activeEditorPane = editorService.activeEditorPane; + if (!(activeEditorPane instanceof NotebookMultiTextDiffEditor)) { + return; + } + + const item = activeEditorPane.getDiffElementViewModel(uri); + if (item && item instanceof SideBySideDiffElementViewModel) { + const original = item.original; + + const modifiedCellIndex = item.modifiedDocument.cells.findIndex(cell => cell.handle === item.modified.handle); + if (modifiedCellIndex === -1) { + return; + } + + item.mainDocumentTextModel.applyEdits([{ + editType: CellEditType.Output, index: modifiedCellIndex, outputs: original.outputs + }], true, undefined, () => undefined, undefined, true); + } + } +}); + +const revertMetadata = localize('notebook.diff.cell.revertMetadata', "Revert Metadata"); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: 'notebook.diffEditor.2.cell.revertMetadata', + title: revertMetadata, + icon: revertIcon, + f1: false, + menu: { + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID), ContextKeyExpr.equals(NOTEBOOK_DIFF_ITEM_KIND.key, 'Metadata'), ContextKeyExpr.equals(NOTEBOOK_DIFF_ITEM_DIFF_STATE.key, 'modified')), + id: MenuId.MultiDiffEditorFileToolbar, + order: 2, + group: 'navigation', + }, + } + ); + } + async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const uri = args[0] as URI; + const editorService = accessor.get(IEditorService); + const activeEditorPane = editorService.activeEditorPane; + if (!(activeEditorPane instanceof NotebookMultiTextDiffEditor)) { + return; + } + + const item = activeEditorPane.getDiffElementViewModel(uri); + if (item && item instanceof SideBySideDiffElementViewModel) { + const original = item.original; + + const modifiedCellIndex = item.modifiedDocument.cells.findIndex(cell => cell.handle === item.modified.handle); + if (modifiedCellIndex === -1) { + return; + } + + item.mainDocumentTextModel.applyEdits([{ + editType: CellEditType.Metadata, index: modifiedCellIndex, metadata: original.metadata + }], true, undefined, () => undefined, undefined, true); + } + } +}); + + registerAction2(class extends Action2 { constructor() { super( { id: 'notebook.diff.cell.revertMetadata', - title: localize('notebook.diff.cell.revertMetadata', "Revert Metadata"), + title: revertMetadata, icon: revertIcon, f1: false, menu: { @@ -78,19 +302,29 @@ registerAction2(class extends Action2 { } ); } - run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { + run(accessor: ServicesAccessor, context?: { cell: DiffElementCellViewModelBase }) { if (!context) { return; } + if (!(context.cell instanceof SideBySideDiffElementViewModel)) { + return; + } + const original = context.cell.original; const modified = context.cell.modified; - if (!original || !modified) { + const modifiedCellIndex = context.cell.mainDocumentTextModel.cells.indexOf(modified.textModel); + if (modifiedCellIndex === -1) { return; } - modified.textModel.metadata = original.metadata; + const rawEdits: ICellEditOperation[] = [{ editType: CellEditType.Metadata, index: modifiedCellIndex, metadata: original.metadata }]; + if (context.cell.original.language && context.cell.modified.language !== context.cell.original.language) { + rawEdits.push({ editType: CellEditType.CellLanguage, index: modifiedCellIndex, language: context.cell.original.language }); + } + + context.cell.modifiedDocument.applyEdits(rawEdits, true, undefined, () => undefined, undefined, true); } }); @@ -133,7 +367,7 @@ registerAction2(class extends Action2 { } ); } - run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { + run(accessor: ServicesAccessor, context?: { cell: DiffElementCellViewModelBase }) { if (!context) { return; } @@ -158,7 +392,7 @@ registerAction2(class extends Action2 { } ); } - run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { + run(accessor: ServicesAccessor, context?: { cell: DiffElementCellViewModelBase }) { if (!context) { return; } @@ -182,24 +416,56 @@ registerAction2(class extends Action2 { }); +registerAction2(class extends Action2 { + constructor() { + super( + { + id: 'notebook.toggle.diff.cell.ignoreTrimWhitespace', + title: localize('ignoreTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"), + icon: toggleWhitespace, + f1: false, + menu: { + id: MenuId.NotebookDiffCellInputTitle, + when: NOTEBOOK_DIFF_CELL_INPUT, + order: 1, + }, + precondition: NOTEBOOK_DIFF_CELL_INPUT, + toggled: ContextKeyExpr.equals(NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE_KEY, false), + } + ); + } + run(accessor: ServicesAccessor, context?: { cell: DiffElementCellViewModelBase }) { + const cell = context?.cell; + if (!cell?.modified) { + return; + } + const uri = cell.modified.uri; + const configService = accessor.get(ITextResourceConfigurationService); + const key = 'diffEditor.ignoreTrimWhitespace'; + const val = configService.getValue(uri, key); + configService.updateValue(uri, key, !val); + } +}); + registerAction2(class extends Action2 { constructor() { super( { id: 'notebook.diff.cell.revertInput', - title: localize('notebook.diff.cell.revertInput', "Revert Input"), + title: revertInput, icon: revertIcon, f1: false, menu: { id: MenuId.NotebookDiffCellInputTitle, - when: NOTEBOOK_DIFF_CELL_INPUT + when: NOTEBOOK_DIFF_CELL_INPUT, + order: 2 }, precondition: NOTEBOOK_DIFF_CELL_INPUT } ); } - run(accessor: ServicesAccessor, context?: { cell: DiffElementViewModelBase }) { + run(accessor: ServicesAccessor, context?: { cell: DiffElementCellViewModelBase }) { if (!context) { return; } @@ -253,7 +519,7 @@ registerAction2(class extends ToggleRenderAction { constructor() { super('notebook.diff.showOutputs', localize2('notebook.diff.showOutputs', 'Show Outputs Differences'), - ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), + ContextKeyExpr.or(ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID)), ContextKeyExpr.notEquals('config.notebook.diff.ignoreOutputs', true), 2, true, @@ -266,7 +532,7 @@ registerAction2(class extends ToggleRenderAction { constructor() { super('notebook.diff.showMetadata', localize2('notebook.diff.showMetadata', 'Show Metadata Differences'), - ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), + ContextKeyExpr.or(ActiveEditorContext.isEqualTo(NotebookTextDiffEditor.ID), ActiveEditorContext.isEqualTo(NotebookMultiTextDiffEditor.ID)), ContextKeyExpr.notEquals('config.notebook.diff.ignoreMetadata', true), 1, undefined, diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index 29dc0277..5cd6a59f 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -14,9 +14,9 @@ import { getDefaultNotebookCreationOptions } from 'vs/workbench/contrib/notebook import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookDiffEditorInput } from '../../common/notebookDiffEditorInput'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { DiffElementViewModelBase, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { DiffElementCellViewModelBase, IDiffElementViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CellDiffSideBySideRenderer, CellDiffSingleSideRenderer, NotebookCellTextDiffListDelegate, NotebookTextDiffList } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffList'; +import { CellDiffPlaceholderRenderer, CellDiffSideBySideRenderer, CellDiffSingleSideRenderer, NotebookCellTextDiffListDelegate, NotebookTextDiffList } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffList'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { diffDiagonalFill, editorBackground, focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; @@ -25,14 +25,12 @@ import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/ed import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { CellEditState, ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, INotebookEditorCreationOptions, INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor, INotebookDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { CellUri, INotebookDiffEditorModel, INotebookDiffResult, NOTEBOOK_DIFF_EDITOR_ID, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, INotebookDiffEditorModel, NOTEBOOK_DIFF_EDITOR_ID, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { URI } from 'vs/base/common/uri'; -import { IDiffChange, IDiffResult } from 'vs/base/common/diff/diff'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { SequencerByKey } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -41,13 +39,13 @@ import { BackLayerWebView, INotebookDelegateForWebview } from 'vs/workbench/cont import { NotebookDiffEditorEventDispatcher, NotebookDiffLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; -import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { cellIndexesToRanges, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { NotebookDiffOverviewRuler } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler'; import { registerZIndex, ZIndex } from 'vs/platform/layout/browser/zIndexRegistry'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { NotebookDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; const $ = DOM.$; @@ -96,14 +94,14 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private _overviewRulerContainer!: HTMLElement; private _overviewRuler!: NotebookDiffOverviewRuler; private _dimension: DOM.Dimension | null = null; - private _diffElementViewModels: DiffElementViewModelBase[] = []; + private notebookDiffViewModel?: INotebookDiffViewModel; private _list!: NotebookTextDiffList; private _modifiedWebview: BackLayerWebView | null = null; private _originalWebview: BackLayerWebView | null = null; private _webviewTransparentCover: HTMLElement | null = null; private _fontInfo: FontInfo | undefined; - private readonly _onMouseUp = this._register(new Emitter<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase }>()); + private readonly _onMouseUp = this._register(new Emitter<{ readonly event: MouseEvent; readonly target: IDiffElementViewModelBase }>()); public readonly onMouseUp = this._onMouseUp.event; private readonly _onDidScroll = this._register(new Emitter()); readonly onDidScroll: Event = this._onDidScroll.event; @@ -151,11 +149,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, - @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, - @ICodeEditorService codeEditorService: ICodeEditorService + @INotebookService private readonly notebookService: INotebookService, ) { super(NotebookTextDiffEditor.ID, group, telemetryService, themeService, storageService); - this._notebookOptions = new NotebookOptions(this.window, this.configurationService, notebookExecutionStateService, codeEditorService, false); + this._notebookOptions = instantiationService.createInstance(NotebookOptions, this.window, false, undefined); this._register(this._notebookOptions); this._revealFirst = true; } @@ -281,6 +278,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const renderers = [ this.instantiationService.createInstance(CellDiffSingleSideRenderer, this), this.instantiationService.createInstance(CellDiffSideBySideRenderer, this), + this.instantiationService.createInstance(CellDiffPlaceholderRenderer, this), ]; this._listViewContainer = DOM.append(this._rootElement, DOM.$('.notebook-diff-list-view')); @@ -335,9 +333,11 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD ); this._register(this._list); - this._register(this._list.onMouseUp(e => { if (e.element) { + if (typeof e.index === 'number') { + this._list.setFocus([e.index]); + } this._onMouseUp.fire({ event: e.browserEvent, target: e.element }); } })); @@ -379,7 +379,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._overviewRuler = this._register(this.instantiationService.createInstance(NotebookDiffOverviewRuler, this, NotebookTextDiffEditor.ENTIRE_DIFF_OVERVIEW_WIDTH, this._overviewRulerContainer)); } - private _updateOutputsOffsetsInWebview(scrollTop: number, scrollHeight: number, activeWebview: BackLayerWebView, getActiveNestedCell: (diffElement: DiffElementViewModelBase) => DiffNestedCellViewModel | undefined, diffSide: DiffSide) { + private _updateOutputsOffsetsInWebview(scrollTop: number, scrollHeight: number, activeWebview: BackLayerWebView, getActiveNestedCell: (diffElement: DiffElementCellViewModelBase) => DiffNestedCellViewModel | undefined, diffSide: DiffSide) { activeWebview.element.style.height = `${scrollHeight}px`; if (activeWebview.insetMapping) { @@ -473,6 +473,9 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._modifiedWebview?.element.remove(); this._modifiedWebview = null; + this.notebookDiffViewModel?.dispose(); + this.notebookDiffViewModel = undefined; + this._modifiedResourceDisposableStore.clear(); this._list.clear(); @@ -486,13 +489,13 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } if (this._modifiedWebview) { - this._updateOutputsOffsetsInWebview(this._list.scrollTop, this._list.scrollHeight, this._modifiedWebview, (diffElement: DiffElementViewModelBase) => { + this._updateOutputsOffsetsInWebview(this._list.scrollTop, this._list.scrollHeight, this._modifiedWebview, (diffElement: DiffElementCellViewModelBase) => { return diffElement.modified; }, DiffSide.Modified); } if (this._originalWebview) { - this._updateOutputsOffsetsInWebview(this._list.scrollTop, this._list.scrollHeight, this._originalWebview, (diffElement: DiffElementViewModelBase) => { + this._updateOutputsOffsetsInWebview(this._list.scrollTop, this._list.scrollHeight, this._originalWebview, (diffElement: DiffElementCellViewModelBase) => { return diffElement.original; }, DiffSide.Original); } @@ -506,6 +509,16 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._localStore.add(this._eventDispatcher.onDidChangeCellLayout(() => { updateInsets(); })); + + if (this._model) { + const vm = this.notebookDiffViewModel = this._register(new NotebookDiffViewModel(this._model, this.notebookEditorWorkerService, this.instantiationService, this.configurationService, this._eventDispatcher!, this.notebookService, this.fontInfo)); + this._localStore.add(this.notebookDiffViewModel.onDidChangeItems(e => { + this._list.splice(e.start, e.deleteCount, e.elements); + if (this.isOverviewRulerEnabled()) { + this._overviewRuler.updateViewModels(vm.items, this._eventDispatcher); + } + })); + } } private async _createModifiedWebview(id: string, viewType: string, resource: URI): Promise { @@ -547,34 +560,25 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } async updateLayout(token: CancellationToken, selections?: number[]) { - if (!this._model) { + if (!this._model || !this.notebookDiffViewModel) { return; } - const diffResult = await this.notebookEditorWorkerService.computeDiff(this._model.original.resource, this._model.modified.resource); - + const diffResult = await this.notebookDiffViewModel.computeDiff(token); if (token.isCancellationRequested) { // after await the editor might be disposed. return; } - NotebookTextDiffEditor.prettyChanges(this._model, diffResult.cellsDiff); - const { viewModels, firstChangeIndex } = NotebookTextDiffEditor.computeDiff(this.instantiationService, this.configurationService, this._model, this._eventDispatcher!, diffResult, this.fontInfo); - const isSame = this._isViewModelTheSame(viewModels); - - if (!isSame) { + if (diffResult) { this._originalWebview?.removeInsets([...this._originalWebview?.insetMapping.keys()]); this._modifiedWebview?.removeInsets([...this._modifiedWebview?.insetMapping.keys()]); - this._setViewModel(viewModels); - } - - // this._diffElementViewModels = viewModels; - // this._list.splice(0, this._list.length, this._diffElementViewModels); - if (this._revealFirst && firstChangeIndex !== -1 && firstChangeIndex < this._list.length) { - this._revealFirst = false; - this._list.setFocus([firstChangeIndex]); - this._list.reveal(firstChangeIndex, 0.3); + if (this._revealFirst && diffResult.firstChangeIndex !== -1 && diffResult.firstChangeIndex < this._list.length) { + this._revealFirst = false; + this._list.setFocus([diffResult.firstChangeIndex]); + this._list.reveal(diffResult.firstChangeIndex, 0.3); + } } if (selections) { @@ -582,200 +586,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } } - private _isViewModelTheSame(viewModels: DiffElementViewModelBase[]) { - let isSame = true; - if (this._diffElementViewModels.length === viewModels.length) { - for (let i = 0; i < viewModels.length; i++) { - const a = this._diffElementViewModels[i]; - const b = viewModels[i]; - - if (a.original?.textModel.getHashValue() !== b.original?.textModel.getHashValue() - || a.modified?.textModel.getHashValue() !== b.modified?.textModel.getHashValue()) { - isSame = false; - break; - } - } - } else { - isSame = false; - } - - return isSame; - } - - private _setViewModel(viewModels: DiffElementViewModelBase[]) { - this._diffElementViewModels = viewModels; - this._list.splice(0, this._list.length, this._diffElementViewModels); - if (this.isOverviewRulerEnabled()) { - this._overviewRuler.updateViewModels(this._diffElementViewModels, this._eventDispatcher); - } - } - - /** - * making sure that swapping cells are always translated to `insert+delete`. - */ - static prettyChanges(model: INotebookDiffEditorModel, diffResult: IDiffResult) { - const changes = diffResult.changes; - for (let i = 0; i < diffResult.changes.length - 1; i++) { - // then we know there is another change after current one - const curr = changes[i]; - const next = changes[i + 1]; - const x = curr.originalStart; - const y = curr.modifiedStart; - - if ( - curr.originalLength === 1 - && curr.modifiedLength === 0 - && next.originalStart === x + 2 - && next.originalLength === 0 - && next.modifiedStart === y + 1 - && next.modifiedLength === 1 - && model.original.notebook.cells[x].getHashValue() === model.modified.notebook.cells[y + 1].getHashValue() - && model.original.notebook.cells[x + 1].getHashValue() === model.modified.notebook.cells[y].getHashValue() - ) { - // this is a swap - curr.originalStart = x; - curr.originalLength = 0; - curr.modifiedStart = y; - curr.modifiedLength = 1; - - next.originalStart = x + 1; - next.originalLength = 1; - next.modifiedStart = y + 2; - next.modifiedLength = 0; - - i++; - } - } - } - - static computeDiff(instantiationService: IInstantiationService, configurationService: IConfigurationService, model: INotebookDiffEditorModel, eventDispatcher: NotebookDiffEditorEventDispatcher, diffResult: INotebookDiffResult, fontInfo: FontInfo | undefined) { - const cellChanges = diffResult.cellsDiff.changes; - const diffElementViewModels: DiffElementViewModelBase[] = []; - const originalModel = model.original.notebook; - const modifiedModel = model.modified.notebook; - let originalCellIndex = 0; - let modifiedCellIndex = 0; - - let firstChangeIndex = -1; - const initData = { - metadataStatusHeight: configurationService.getValue('notebook.diff.ignoreMetadata') ? 0 : 25, - outputStatusHeight: configurationService.getValue('notebook.diff.ignoreOutputs') || !!(modifiedModel.transientOptions.transientOutputs) ? 0 : 25, - fontInfo - }; - - for (let i = 0; i < cellChanges.length; i++) { - const change = cellChanges[i]; - // common cells - - for (let j = 0; j < change.originalStart - originalCellIndex; j++) { - const originalCell = originalModel.cells[originalCellIndex + j]; - const modifiedCell = modifiedModel.cells[modifiedCellIndex + j]; - if (originalCell.getHashValue() === modifiedCell.getHashValue()) { - diffElementViewModels.push(new SideBySideDiffElementViewModel( - model.modified.notebook, - model.original.notebook, - instantiationService.createInstance(DiffNestedCellViewModel, originalCell), - instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), - 'unchanged', - eventDispatcher, - initData - )); - } else { - if (firstChangeIndex === -1) { - firstChangeIndex = diffElementViewModels.length; - } - - diffElementViewModels.push(new SideBySideDiffElementViewModel( - model.modified.notebook, - model.original.notebook, - instantiationService.createInstance(DiffNestedCellViewModel, originalCell), - instantiationService.createInstance(DiffNestedCellViewModel, modifiedCell), - 'modified', - eventDispatcher, - initData - )); - } - } - - const modifiedLCS = NotebookTextDiffEditor.computeModifiedLCS(instantiationService, change, originalModel, modifiedModel, eventDispatcher, initData); - if (modifiedLCS.length && firstChangeIndex === -1) { - firstChangeIndex = diffElementViewModels.length; - } - - diffElementViewModels.push(...modifiedLCS); - originalCellIndex = change.originalStart + change.originalLength; - modifiedCellIndex = change.modifiedStart + change.modifiedLength; - } - - for (let i = originalCellIndex; i < originalModel.cells.length; i++) { - diffElementViewModels.push(new SideBySideDiffElementViewModel( - model.modified.notebook, - model.original.notebook, - instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[i]), - instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[i - originalCellIndex + modifiedCellIndex]), - 'unchanged', - eventDispatcher, - initData - )); - } - - return { - viewModels: diffElementViewModels, - firstChangeIndex - }; - } - - static computeModifiedLCS(instantiationService: IInstantiationService, change: IDiffChange, originalModel: NotebookTextModel, modifiedModel: NotebookTextModel, eventDispatcher: NotebookDiffEditorEventDispatcher, initData: { - metadataStatusHeight: number; - outputStatusHeight: number; - fontInfo: FontInfo | undefined; - }) { - const result: DiffElementViewModelBase[] = []; - // modified cells - const modifiedLen = Math.min(change.originalLength, change.modifiedLength); - - for (let j = 0; j < modifiedLen; j++) { - const isTheSame = originalModel.cells[change.originalStart + j].equal(modifiedModel.cells[change.modifiedStart + j]); - result.push(new SideBySideDiffElementViewModel( - modifiedModel, - originalModel, - instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[change.originalStart + j]), - instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[change.modifiedStart + j]), - isTheSame ? 'unchanged' : 'modified', - eventDispatcher, - initData - )); - } - - for (let j = modifiedLen; j < change.originalLength; j++) { - // deletion - result.push(new SingleSideDiffElementViewModel( - originalModel, - modifiedModel, - instantiationService.createInstance(DiffNestedCellViewModel, originalModel.cells[change.originalStart + j]), - undefined, - 'delete', - eventDispatcher, - initData - )); - } - - for (let j = modifiedLen; j < change.modifiedLength; j++) { - // insertion - result.push(new SingleSideDiffElementViewModel( - modifiedModel, - originalModel, - undefined, - instantiationService.createInstance(DiffNestedCellViewModel, modifiedModel.cells[change.modifiedStart + j]), - 'insert', - eventDispatcher, - initData - )); - } - - return result; - } - scheduleOutputHeightAck(cellInfo: IDiffCellInfo, outputId: string, height: number) { const diffElement = cellInfo.diffElement; // const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; @@ -799,11 +609,11 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }, 10); } - private pendingLayouts = new WeakMap(); + private pendingLayouts = new WeakMap(); - layoutNotebookCell(cell: DiffElementViewModelBase, height: number) { - const relayout = (cell: DiffElementViewModelBase, height: number) => { + layoutNotebookCell(cell: DiffElementCellViewModelBase, height: number) { + const relayout = (cell: DiffElementCellViewModelBase, height: number) => { this._list.updateElementHeight2(cell, height); }; @@ -836,6 +646,9 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } previousChange(): void { + if (!this.notebookDiffViewModel) { + return; + } let currFocus = this._list.getFocus()[0]; if (isNaN(currFocus) || currFocus < 0) { @@ -844,9 +657,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD // find the index of previous change let prevChangeIndex = currFocus - 1; + const currentViewModels = this.notebookDiffViewModel.items; while (prevChangeIndex >= 0) { - const vm = this._diffElementViewModels[prevChangeIndex]; - if (vm.type !== 'unchanged') { + const vm = currentViewModels[prevChangeIndex]; + if (vm.type !== 'unchanged' && vm.type !== 'placeholder') { break; } @@ -858,7 +672,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._list.reveal(prevChangeIndex); } else { // go to the last one - const index = findLastIdx(this._diffElementViewModels, vm => vm.type !== 'unchanged'); + const index = findLastIdx(currentViewModels, vm => vm.type !== 'unchanged' && vm.type !== 'placeholder'); if (index >= 0) { this._list.setFocus([index]); this._list.reveal(index); @@ -867,6 +681,9 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } nextChange(): void { + if (!this.notebookDiffViewModel) { + return; + } let currFocus = this._list.getFocus()[0]; if (isNaN(currFocus) || currFocus < 0) { @@ -875,21 +692,22 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD // find the index of next change let nextChangeIndex = currFocus + 1; - while (nextChangeIndex < this._diffElementViewModels.length) { - const vm = this._diffElementViewModels[nextChangeIndex]; - if (vm.type !== 'unchanged') { + const currentViewModels = this.notebookDiffViewModel.items; + while (nextChangeIndex < currentViewModels.length) { + const vm = currentViewModels[nextChangeIndex]; + if (vm.type !== 'unchanged' && vm.type !== 'placeholder') { break; } nextChangeIndex++; } - if (nextChangeIndex < this._diffElementViewModels.length) { + if (nextChangeIndex < currentViewModels.length) { this._list.setFocus([nextChangeIndex]); this._list.reveal(nextChangeIndex); } else { // go to the first one - const index = this._diffElementViewModels.findIndex(vm => vm.type !== 'unchanged'); + const index = currentViewModels.findIndex(vm => vm.type !== 'unchanged' && vm.type !== 'placeholder'); if (index >= 0) { this._list.setFocus([index]); this._list.reveal(index); @@ -897,7 +715,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } } - createOutput(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void { + createOutput(cellDiffViewModel: DiffElementCellViewModelBase, cellViewModel: DiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void { this._insetModifyQueueByOutputId.queue(output.source.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; if (!activeWebview) { @@ -934,7 +752,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD throw new Error('Not implemented'); } - removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { + removeInset(cellDiffViewModel: DiffElementCellViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { this._insetModifyQueueByOutputId.queue(displayOutput.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; if (!activeWebview) { @@ -949,7 +767,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }); } - showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { + showInset(cellDiffViewModel: DiffElementCellViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { this._insetModifyQueueByOutputId.queue(displayOutput.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; if (!activeWebview) { @@ -973,7 +791,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }); } - hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, output: ICellOutputViewModel) { + hideInset(cellDiffViewModel: DiffElementCellViewModelBase, cellViewModel: DiffNestedCellViewModel, output: ICellOutputViewModel) { this._modifiedWebview?.hideInset(output); this._originalWebview?.hideInset(output); } @@ -1002,8 +820,8 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._modifiedResourceDisposableStore.clear(); this._list?.splice(0, this._list?.length || 0); this._model = null; - this._diffElementViewModels.forEach(vm => vm.dispose()); - this._diffElementViewModels = []; + this.notebookDiffViewModel?.dispose(); + this.notebookDiffViewModel = undefined; } deltaCellOutputContainerClassNames(diffSide: DiffSide, cellId: string, added: string[], removed: string[]) { @@ -1028,56 +846,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }; } - getCellOutputLayoutInfo(nestedCell: DiffNestedCellViewModel) { - if (!this._model) { - throw new Error('Editor is not attached to model yet'); - } - const documentModel = CellUri.parse(nestedCell.uri); - if (!documentModel) { - throw new Error('Nested cell in the diff editor has wrong Uri'); - } - - const belongToOriginalDocument = this._model.original.notebook.uri.toString() === documentModel.notebook.toString(); - const viewModel = this._diffElementViewModels.find(element => { - const textModel = belongToOriginalDocument ? element.original : element.modified; - if (!textModel) { - return false; - } - - if (textModel.uri.toString() === nestedCell.uri.toString()) { - return true; - } - - return false; - }); - - if (!viewModel) { - throw new Error('Nested cell in the diff editor does not match any diff element'); - } - - if (viewModel.type === 'unchanged') { - return this.getLayoutInfo(); - } - - if (viewModel.type === 'insert' || viewModel.type === 'delete') { - return { - width: this._dimension!.width / 2, - height: this._dimension!.height / 2, - fontInfo: this.fontInfo - }; - } - - if (viewModel.checkIfOutputsModified()) { - return { - width: this._dimension!.width / 2, - height: this._dimension!.height / 2, - fontInfo: this.fontInfo - }; - } else { - return this.getLayoutInfo(); - } - } - layout(dimension: DOM.Dimension, _position: DOM.IDomPosition): void { this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600); this._rootElement.classList.toggle('narrow-width', dimension.width < 600); @@ -1137,4 +905,6 @@ registerThemingParticipant((theme, collector) => { `); collector.addRule(`.notebook-text-diff-editor .cell-body { margin: ${DIFF_CELL_MARGIN}px; }`); + // We do not want a left margin, as we add an overlay for expanind the collapsed/hidden cells. + collector.addRule(`.notebook-text-diff-editor .cell-placeholder-body { margin: ${DIFF_CELL_MARGIN}px 0; }`); }); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index 031c2afd..321be79d 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { CellLayoutState, ICellOutputViewModel, ICommonCellInfo, IGenericCellViewModel, IInsetRenderOutput } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { DiffElementViewModelBase } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { DiffElementCellViewModelBase, IDiffElementViewModelBase } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { Event } from 'vs/base/common/event'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -16,6 +16,8 @@ import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookO import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; export enum DiffSide { Original = 0, @@ -23,24 +25,24 @@ export enum DiffSide { } export interface IDiffCellInfo extends ICommonCellInfo { - diffElement: DiffElementViewModelBase; + diffElement: DiffElementCellViewModelBase; } export interface INotebookTextDiffEditor { notebookOptions: NotebookOptions; readonly textModel?: NotebookTextModel; - onMouseUp: Event<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase }>; + onMouseUp: Event<{ readonly event: MouseEvent; readonly target: IDiffElementViewModelBase }>; onDidScroll: Event; onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel; output: ICellOutputViewModel }>; getOverflowContainerDomNode(): HTMLElement; getLayoutInfo(): NotebookLayoutInfo; getScrollTop(): number; getScrollHeight(): number; - layoutNotebookCell(cell: DiffElementViewModelBase, height: number): void; - createOutput(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void; - showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide): void; - removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel, diffSide: DiffSide): void; - hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel): void; + layoutNotebookCell(cell: DiffElementCellViewModelBase, height: number): void; + createOutput(cellDiffViewModel: DiffElementCellViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void; + showInset(cellDiffViewModel: DiffElementCellViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide): void; + removeInset(cellDiffViewModel: DiffElementCellViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel, diffSide: DiffSide): void; + hideInset(cellDiffViewModel: DiffElementCellViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel): void; /** * Trigger the editor to scroll from scroll event programmatically */ @@ -66,12 +68,22 @@ export interface CellDiffCommonRenderTemplate { readonly bottomBorder: HTMLElement; } +export interface CellDiffPlaceholderRenderTemplate { + readonly container: HTMLElement; + readonly placeholder: HTMLElement; + readonly body: HTMLElement; + readonly marginOverlay: IDiffCellMarginOverlay; + readonly elementDisposables: DisposableStore; +} + export interface CellDiffSingleSideRenderTemplate extends CellDiffCommonRenderTemplate { readonly container: HTMLElement; readonly body: HTMLElement; readonly diffEditorContainer: HTMLElement; readonly diagonalFill: HTMLElement; readonly elementDisposables: DisposableStore; + readonly cellHeaderContainer: HTMLElement; + readonly editorContainer: HTMLElement; readonly sourceEditor: CodeEditorWidget; readonly metadataHeaderContainer: HTMLElement; readonly metadataInfoContainer: HTMLElement; @@ -79,12 +91,18 @@ export interface CellDiffSingleSideRenderTemplate extends CellDiffCommonRenderTe readonly outputInfoContainer: HTMLElement; } +export interface IDiffCellMarginOverlay extends IDisposable { + onAction: Event; + show(): void; + hide(): void; +} export interface CellDiffSideBySideRenderTemplate extends CellDiffCommonRenderTemplate { readonly container: HTMLElement; readonly body: HTMLElement; readonly diffEditorContainer: HTMLElement; readonly elementDisposables: DisposableStore; + readonly cellHeaderContainer: HTMLElement; readonly sourceEditor: DiffEditorWidget; readonly editorContainer: HTMLElement; readonly inputToolbarContainer: HTMLElement; @@ -93,6 +111,7 @@ export interface CellDiffSideBySideRenderTemplate extends CellDiffCommonRenderTe readonly metadataInfoContainer: HTMLElement; readonly outputHeaderContainer: HTMLElement; readonly outputInfoContainer: HTMLElement; + readonly marginOverlay: IDiffCellMarginOverlay; } export interface IDiffElementLayoutInfo { @@ -101,6 +120,7 @@ export interface IDiffElementLayoutInfo { editorHeight: number; editorMargin: number; metadataHeight: number; + cellStatusHeight: number; metadataStatusHeight: number; rawOutputHeight: number; outputMetadataHeight: number; @@ -121,6 +141,31 @@ export interface CellDiffViewModelLayoutChangeEvent extends IDiffElementSelfLayo } export const DIFF_CELL_MARGIN = 16; -export const NOTEBOOK_DIFF_CELL_INPUT = new RawContextKey('notebookDiffCellInputChanged', false); -export const NOTEBOOK_DIFF_CELL_PROPERTY = new RawContextKey('notebookDiffCellPropertyChanged', false); -export const NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED = new RawContextKey('notebookDiffCellPropertyExpanded', false); +export const NOTEBOOK_DIFF_CELL_INPUT = new RawContextKey('notebook.diffEditor.cell.inputChanged', false); +export const NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE_KEY = 'notebook.diffEditor.cell.ignoreWhitespace'; +export const NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE = new RawContextKey(NOTEBOOK_DIFF_CELL_IGNORE_WHITESPACE_KEY, false); +export const NOTEBOOK_DIFF_CELL_PROPERTY = new RawContextKey('notebook.diffEditor.cell.property.changed', false); +export const NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED = new RawContextKey('notebook.diffEditor.cell.property.expanded', false); +export const NOTEBOOK_DIFF_CELLS_COLLAPSED = new RawContextKey('notebook.diffEditor.allCollapsed', undefined, localize('notebook.diffEditor.allCollapsed', "Whether all cells in notebook diff editor are collapsed")); +export const NOTEBOOK_DIFF_HAS_UNCHANGED_CELLS = new RawContextKey('notebook.diffEditor.hasUnchangedCells', undefined, localize('notebook.diffEditor.hasUnchangedCells', "Whether there are unchanged cells in the notebook diff editor")); +export const NOTEBOOK_DIFF_UNCHANGED_CELLS_HIDDEN = new RawContextKey('notebook.diffEditor.unchangedCellsAreHidden', undefined, localize('notebook.diffEditor.unchangedCellsAreHidden', "Whether the unchanged cells in the notebook diff editor are hidden")); +export const NOTEBOOK_DIFF_ITEM_KIND = new RawContextKey('notebook.diffEditor.item.kind', undefined, localize('notebook.diffEditor.item.kind', "The kind of item in the notebook diff editor, Cell, Metadata or Output")); +export const NOTEBOOK_DIFF_ITEM_DIFF_STATE = new RawContextKey('notebook.diffEditor.item.state', undefined, localize('notebook.diffEditor.item.state', "The diff state of item in the notebook diff editor, delete, insert, modified or unchanged")); + +export interface INotebookDiffViewModelUpdateEvent { + readonly start: number; + readonly deleteCount: number; + readonly elements: readonly IDiffElementViewModelBase[]; +} + +export interface INotebookDiffViewModel extends IDisposable { + readonly items: readonly IDiffElementViewModelBase[]; + onDidChangeItems: Event; + /** + * Computes the differences and generates the viewmodel. + * If view models are generated, then the onDidChangeItems is triggered and will have a return value. + * Else returns `undefined` + * @param token + */ + computeDiff(token: CancellationToken): Promise<{ firstChangeIndex: number } | undefined>; +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index a3cfecd2..55910ef9 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -14,9 +14,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { DiffElementViewModelBase, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; -import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DIFF_CELL_MARGIN, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; -import { DeletedElement, getOptimizedNestedCodeEditorWidgetOptions, InsertElement, ModifiedElement } from 'vs/workbench/contrib/notebook/browser/diff/diffComponents'; +import { DiffElementPlaceholderViewModel, IDiffElementViewModelBase, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { CellDiffPlaceholderRenderTemplate, CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DIFF_CELL_MARGIN, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { CellDiffPlaceholderElement, CollapsedCellOverlayWidget, DeletedElement, getOptimizedNestedCodeEditorWidgetOptions, InsertElement, ModifiedElement, UnchangedCellOverlayWidget } from 'vs/workbench/contrib/notebook/browser/diff/diffComponents'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; @@ -30,8 +30,9 @@ import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { fixedDiffEditorOptions, fixedEditorOptions } from 'vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { localize } from 'vs/nls'; -export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { +export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { private readonly lineHeight: number; constructor( @@ -42,15 +43,15 @@ export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { + static readonly TEMPLATE_ID = 'cell_diff_placeholder'; + + constructor( + readonly notebookEditor: INotebookTextDiffEditor, + @IInstantiationService protected readonly instantiationService: IInstantiationService + ) { } + + get templateId() { + return CellDiffPlaceholderRenderer.TEMPLATE_ID; + } + + renderTemplate(container: HTMLElement): CellDiffPlaceholderRenderTemplate { + const body = DOM.$('.cell-placeholder-body'); + DOM.append(container, body); + + const elementDisposables = new DisposableStore(); + const marginOverlay = new CollapsedCellOverlayWidget(body); + const contents = DOM.append(body, DOM.$('.contents')); + const placeholder = DOM.append(contents, DOM.$('span.text', { title: localize('notebook.diff.hiddenCells.expandAll', 'Double click to show') })); + + return { + body, + container, + placeholder, + marginOverlay, + elementDisposables + }; + } + renderElement(element: DiffElementPlaceholderViewModel, index: number, templateData: CellDiffPlaceholderRenderTemplate, height: number | undefined): void { + templateData.body.classList.remove('left', 'right', 'full'); + templateData.elementDisposables.add(this.instantiationService.createInstance(CellDiffPlaceholderElement, element, templateData)); + } + + disposeTemplate(templateData: CellDiffPlaceholderRenderTemplate): void { + templateData.container.innerText = ''; + } + + disposeElement(element: DiffElementPlaceholderViewModel, index: number, templateData: CellDiffPlaceholderRenderTemplate): void { + templateData.elementDisposables.clear(); } } + export class CellDiffSingleSideRenderer implements IListRenderer { static readonly TEMPLATE_ID = 'cell_diff_single'; @@ -82,8 +129,9 @@ export class CellDiffSingleSideRenderer implements IListRenderer extends MouseController { } } -export class NotebookTextDiffList extends WorkbenchList implements IDisposable, IStyleController { +export class NotebookTextDiffList extends WorkbenchList implements IDisposable, IStyleController { private styleElement?: HTMLStyleElement; get rowsContainer(): HTMLElement { @@ -313,21 +365,21 @@ export class NotebookTextDiffList extends WorkbenchList, - renderers: IListRenderer[], + delegate: IListVirtualDelegate, + renderers: IListRenderer[], contextKeyService: IContextKeyService, - options: IWorkbenchListOptions, + options: IWorkbenchListOptions, @IListService listService: IListService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService) { super(listUser, container, delegate, renderers, options, contextKeyService, listService, configurationService, instantiationService); } - protected override createMouseController(options: IListOptions): MouseController { + protected override createMouseController(options: IListOptions): MouseController { return new NotebookMouseController(this); } - getCellViewScrollTop(element: DiffElementViewModelBase): number { + getCellViewScrollTop(element: IDiffElementViewModelBase): number { const index = this.indexOf(element); // if (index === undefined || index < 0 || index >= this.length) { // this._getViewIndexUpperBound(element); @@ -354,7 +406,7 @@ export class NotebookTextDiffList extends WorkbenchList; private readonly _overviewViewportDomElement: FastDomNode; - private _diffElementViewModels: DiffElementViewModelBase[] = []; + private _diffElementViewModels: readonly IDiffElementViewModelBase[] = []; private _lanes = 2; private _insertColor: Color | null; @@ -94,7 +94,7 @@ export class NotebookDiffOverviewRuler extends Themable { this._layoutNow(); } - updateViewModels(elements: DiffElementViewModelBase[], eventDispatcher: NotebookDiffEditorEventDispatcher | undefined) { + updateViewModels(elements: readonly IDiffElementViewModelBase[], eventDispatcher: NotebookDiffEditorEventDispatcher | undefined) { this._disposables.clear(); this._diffElementViewModels = elements; @@ -126,7 +126,7 @@ export class NotebookDiffOverviewRuler extends Themable { private _layoutNow() { const layoutInfo = this.notebookEditor.getLayoutInfo(); const height = layoutInfo.height; - const contentHeight = this._diffElementViewModels.map(view => view.layoutInfo.totalHeight).reduce((a, b) => a + b, 0); + const contentHeight = this._diffElementViewModels.map(view => view.totalHeight).reduce((a, b) => a + b, 0); const ratio = PixelRatio.getInstance(DOM.getWindow(this._domNode.domNode)).value; this._domNode.setWidth(this.width); this._domNode.setHeight(height); @@ -182,8 +182,7 @@ export class NotebookDiffOverviewRuler extends Themable { for (let i = 0; i < this._diffElementViewModels.length; i++) { const element = this._diffElementViewModels[i]; - const cellHeight = Math.round((element.layoutInfo.totalHeight / scrollHeight) * ratio * height); - + const cellHeight = Math.round((element.totalHeight / scrollHeight) * ratio * height); switch (element.type) { case 'insert': ctx.fillStyle = this._insertColorHex; @@ -203,6 +202,7 @@ export class NotebookDiffOverviewRuler extends Themable { break; } + currentFrom += cellHeight; } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts new file mode 100644 index 00000000..dd2b3f12 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel.ts @@ -0,0 +1,545 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDiffResult, IDiffChange } from 'vs/base/common/diff/diff'; +import { Emitter, type IValueWithChangeEvent } from 'vs/base/common/event'; +import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import type { URI } from 'vs/base/common/uri'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import type { ContextKeyValue } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; +import { DiffElementCellViewModelBase, DiffElementPlaceholderViewModel, IDiffElementViewModelBase, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; +import { INotebookDiffViewModel, INotebookDiffViewModelUpdateEvent, NOTEBOOK_DIFF_ITEM_DIFF_STATE, NOTEBOOK_DIFF_ITEM_KIND } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CellUri, INotebookDiffEditorModel, INotebookDiffResult } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; + +export class NotebookDiffViewModel extends Disposable implements INotebookDiffViewModel, IValueWithChangeEvent { + private readonly placeholderAndRelatedCells = new Map(); + private readonly _items: IDiffElementViewModelBase[] = []; + get items(): readonly IDiffElementViewModelBase[] { + return this._items; + } + private readonly _onDidChangeItems = this._register(new Emitter()); + public readonly onDidChangeItems = this._onDidChangeItems.event; + private readonly disposables = this._register(new DisposableStore()); + private _onDidChange = this._register(new Emitter()); + private diffEditorItems: NotebookMultiDiffEditorItem[] = []; + public onDidChange = this._onDidChange.event; + get value(): readonly NotebookMultiDiffEditorItem[] { + return this.diffEditorItems + .filter(item => item.type !== 'placeholder') + .filter(item => { + if (this._includeUnchanged) { + return true; + } + if (item instanceof NotebookMultiDiffEditorCellItem) { + return item.type === 'unchanged' && item.containerType === 'unchanged' ? false : true; + } + if (item instanceof NotebookMultiDiffEditorMetadataItem) { + return item.type === 'unchanged' && item.containerType === 'unchanged' ? false : true; + } + if (item instanceof NotebookMultiDiffEditorOutputItem) { + return item.type === 'unchanged' && item.containerType === 'unchanged' ? false : true; + } + return true; + }) + .filter(item => item instanceof NotebookMultiDiffEditorOutputItem ? !this.hideOutput : true) + .filter(item => item instanceof NotebookMultiDiffEditorMetadataItem ? !this.hideCellMetadata : true); + } + + private _hasUnchangedCells?: boolean; + public get hasUnchangedCells() { + return this._hasUnchangedCells === true; + } + private _includeUnchanged?: boolean; + public get includeUnchanged() { + return this._includeUnchanged === true; + } + public set includeUnchanged(value) { + this._includeUnchanged = value; + this._onDidChange.fire(); + } + private hideOutput?: boolean; + private hideCellMetadata?: boolean; + + private originalCellViewModels: DiffElementCellViewModelBase[] = []; + constructor(private readonly model: INotebookDiffEditorModel, + private readonly notebookEditorWorkerService: INotebookEditorWorkerService, + private readonly instantiationService: IInstantiationService, + private readonly configurationService: IConfigurationService, + private readonly eventDispatcher: NotebookDiffEditorEventDispatcher, + private readonly notebookService: INotebookService, + private readonly fontInfo?: FontInfo, + private readonly excludeUnchangedPlaceholder?: boolean, + ) { + super(); + this.hideOutput = this.model.modified.notebook.transientOptions.transientOutputs || this.configurationService.getValue('notebook.diff.ignoreOutputs'); + this.hideCellMetadata = this.configurationService.getValue('notebook.diff.ignoreMetadata'); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + let triggerChange = false; + if (e.affectsConfiguration('notebook.diff.ignoreMetadata')) { + const newValue = this.configurationService.getValue('notebook.diff.ignoreMetadata'); + + if (newValue !== undefined && this.hideCellMetadata !== newValue) { + this.hideCellMetadata = newValue; + triggerChange = true; + } + } + + if (e.affectsConfiguration('notebook.diff.ignoreOutputs')) { + const newValue = this.configurationService.getValue('notebook.diff.ignoreOutputs'); + + if (newValue !== undefined && this.hideOutput !== (newValue || this.model.modified.notebook.transientOptions.transientOutputs)) { + this.hideOutput = newValue || !!(this.model.modified.notebook.transientOptions.transientOutputs); + triggerChange = true; + } + } + if (triggerChange) { + this._onDidChange.fire(); + } + })); + } + override dispose() { + this.clear(); + super.dispose(); + } + private clear() { + this.disposables.clear(); + dispose(Array.from(this.placeholderAndRelatedCells.keys())); + this.placeholderAndRelatedCells.clear(); + dispose(this.originalCellViewModels); + this.originalCellViewModels = []; + dispose(this._items); + this._items.splice(0, this._items.length); + } + + async computeDiff(token: CancellationToken): Promise<{ firstChangeIndex: number } | undefined> { + const diffResult = await this.notebookEditorWorkerService.computeDiff(this.model.original.resource, this.model.modified.resource); + if (token.isCancellationRequested) { + // after await the editor might be disposed. + return; + } + + prettyChanges(this.model, diffResult.cellsDiff); + + const { cellDiffInfo, firstChangeIndex } = computeDiff(this.model, diffResult); + if (isEqual(cellDiffInfo, this.originalCellViewModels, this.model)) { + return; + } else { + this.updateViewModels(cellDiffInfo); + this.updateDiffEditorItems(); + return { firstChangeIndex }; + } + } + + private updateDiffEditorItems() { + this.diffEditorItems = []; + const originalSourceUri = this.model.original.resource!; + const modifiedSourceUri = this.model.modified.resource!; + this._hasUnchangedCells = false; + this.items.forEach(item => { + switch (item.type) { + case 'delete': { + this.diffEditorItems.push(new NotebookMultiDiffEditorCellItem(item.original!.uri, undefined, item.type, item.type)); + const originalMetadata = CellUri.generateCellPropertyUri(originalSourceUri, item.original!.handle, Schemas.vscodeNotebookCellMetadata); + this.diffEditorItems.push(new NotebookMultiDiffEditorMetadataItem(originalMetadata, undefined, item.type, item.type)); + const originalOutput = CellUri.generateCellPropertyUri(originalSourceUri, item.original!.handle, Schemas.vscodeNotebookCellOutput); + this.diffEditorItems.push(new NotebookMultiDiffEditorOutputItem(originalOutput, undefined, item.type, item.type)); + break; + } + case 'insert': { + this.diffEditorItems.push(new NotebookMultiDiffEditorCellItem(undefined, item.modified!.uri, item.type, item.type)); + const modifiedMetadata = CellUri.generateCellPropertyUri(modifiedSourceUri, item.modified!.handle, Schemas.vscodeNotebookCellMetadata); + this.diffEditorItems.push(new NotebookMultiDiffEditorMetadataItem(undefined, modifiedMetadata, item.type, item.type)); + const modifiedOutput = CellUri.generateCellPropertyUri(modifiedSourceUri, item.modified!.handle, Schemas.vscodeNotebookCellOutput); + this.diffEditorItems.push(new NotebookMultiDiffEditorOutputItem(undefined, modifiedOutput, item.type, item.type)); + break; + } + case 'modified': { + const cellType = item.checkIfInputModified() ? item.type : 'unchanged'; + const containerChanged = (item.checkIfInputModified() || item.checkMetadataIfModified() || item.checkIfOutputsModified()) ? item.type : 'unchanged'; + this.diffEditorItems.push(new NotebookMultiDiffEditorCellItem(item.original!.uri, item.modified!.uri, cellType, containerChanged)); + const originalMetadata = CellUri.generateCellPropertyUri(originalSourceUri, item.original!.handle, Schemas.vscodeNotebookCellMetadata); + const modifiedMetadata = CellUri.generateCellPropertyUri(modifiedSourceUri, item.modified!.handle, Schemas.vscodeNotebookCellMetadata); + this.diffEditorItems.push(new NotebookMultiDiffEditorMetadataItem(originalMetadata, modifiedMetadata, item.checkMetadataIfModified() ? item.type : 'unchanged', containerChanged)); + const originalOutput = CellUri.generateCellPropertyUri(originalSourceUri, item.original!.handle, Schemas.vscodeNotebookCellOutput); + const modifiedOutput = CellUri.generateCellPropertyUri(modifiedSourceUri, item.modified!.handle, Schemas.vscodeNotebookCellOutput); + this.diffEditorItems.push(new NotebookMultiDiffEditorOutputItem(originalOutput, modifiedOutput, item.checkIfOutputsModified() ? item.type : 'unchanged', containerChanged)); + break; + } + case 'unchanged': { + this._hasUnchangedCells = true; + this.diffEditorItems.push(new NotebookMultiDiffEditorCellItem(item.original!.uri, item.modified!.uri, item.type, item.type)); + const originalMetadata = CellUri.generateCellPropertyUri(originalSourceUri, item.original!.handle, Schemas.vscodeNotebookCellMetadata); + const modifiedMetadata = CellUri.generateCellPropertyUri(modifiedSourceUri, item.modified!.handle, Schemas.vscodeNotebookCellMetadata); + this.diffEditorItems.push(new NotebookMultiDiffEditorMetadataItem(originalMetadata, modifiedMetadata, item.type, item.type)); + const originalOutput = CellUri.generateCellPropertyUri(originalSourceUri, item.original!.handle, Schemas.vscodeNotebookCellOutput); + const modifiedOutput = CellUri.generateCellPropertyUri(modifiedSourceUri, item.modified!.handle, Schemas.vscodeNotebookCellOutput); + this.diffEditorItems.push(new NotebookMultiDiffEditorOutputItem(originalOutput, modifiedOutput, item.type, item.type)); + break; + } + } + }); + + this._onDidChange.fire(); + } + + private updateViewModels(cellDiffInfo: CellDiffInfo[]) { + const cellViewModels = createDiffViewModels(this.instantiationService, this.configurationService, this.model, this.eventDispatcher, cellDiffInfo, this.fontInfo, this.notebookService); + const oldLength = this._items.length; + this.clear(); + this._items.splice(0, oldLength); + + let placeholder: DiffElementPlaceholderViewModel | undefined = undefined; + this.originalCellViewModels = cellViewModels; + cellViewModels.forEach((vm, index) => { + if (vm.type === 'unchanged' && !this.excludeUnchangedPlaceholder) { + if (!placeholder) { + vm.displayIconToHideUnmodifiedCells = true; + placeholder = new DiffElementPlaceholderViewModel(vm.mainDocumentTextModel, vm.editorEventDispatcher, vm.initData); + this._items.push(placeholder); + const placeholderItem = placeholder; + + this.disposables.add(placeholderItem.onUnfoldHiddenCells(() => { + const hiddenCellViewModels = this.placeholderAndRelatedCells.get(placeholderItem); + if (!Array.isArray(hiddenCellViewModels)) { + return; + } + const start = this._items.indexOf(placeholderItem); + this._items.splice(start, 1, ...hiddenCellViewModels); + this._onDidChangeItems.fire({ start, deleteCount: 1, elements: hiddenCellViewModels }); + })); + this.disposables.add(vm.onHideUnchangedCells(() => { + const hiddenCellViewModels = this.placeholderAndRelatedCells.get(placeholderItem); + if (!Array.isArray(hiddenCellViewModels)) { + return; + } + const start = this._items.indexOf(vm); + this._items.splice(start, hiddenCellViewModels.length, placeholderItem); + this._onDidChangeItems.fire({ start, deleteCount: hiddenCellViewModels.length, elements: [placeholderItem] }); + })); + } + const hiddenCellViewModels = this.placeholderAndRelatedCells.get(placeholder) || []; + hiddenCellViewModels.push(vm); + this.placeholderAndRelatedCells.set(placeholder, hiddenCellViewModels); + placeholder.hiddenCells.push(vm); + } else { + placeholder = undefined; + this._items.push(vm); + } + }); + + this._onDidChangeItems.fire({ start: 0, deleteCount: oldLength, elements: this._items }); + } +} + + +/** + * making sure that swapping cells are always translated to `insert+delete`. + */ +export function prettyChanges(model: INotebookDiffEditorModel, diffResult: IDiffResult) { + const changes = diffResult.changes; + for (let i = 0; i < diffResult.changes.length - 1; i++) { + // then we know there is another change after current one + const curr = changes[i]; + const next = changes[i + 1]; + const x = curr.originalStart; + const y = curr.modifiedStart; + + if ( + curr.originalLength === 1 + && curr.modifiedLength === 0 + && next.originalStart === x + 2 + && next.originalLength === 0 + && next.modifiedStart === y + 1 + && next.modifiedLength === 1 + && model.original.notebook.cells[x].getHashValue() === model.modified.notebook.cells[y + 1].getHashValue() + && model.original.notebook.cells[x + 1].getHashValue() === model.modified.notebook.cells[y].getHashValue() + ) { + // this is a swap + curr.originalStart = x; + curr.originalLength = 0; + curr.modifiedStart = y; + curr.modifiedLength = 1; + + next.originalStart = x + 1; + next.originalLength = 1; + next.modifiedStart = y + 2; + next.modifiedLength = 0; + + i++; + } + } +} + +type CellDiffInfo = { + originalCellIndex: number; + modifiedCellIndex: number; + type: 'unchanged' | 'modified'; +} | +{ + originalCellIndex: number; + type: 'delete'; +} | +{ + modifiedCellIndex: number; + type: 'insert'; +}; +function computeDiff(model: INotebookDiffEditorModel, diffResult: INotebookDiffResult) { + const cellChanges = diffResult.cellsDiff.changes; + const cellDiffInfo: CellDiffInfo[] = []; + const originalModel = model.original.notebook; + const modifiedModel = model.modified.notebook; + let originalCellIndex = 0; + let modifiedCellIndex = 0; + + let firstChangeIndex = -1; + + for (let i = 0; i < cellChanges.length; i++) { + const change = cellChanges[i]; + // common cells + + for (let j = 0; j < change.originalStart - originalCellIndex; j++) { + const originalCell = originalModel.cells[originalCellIndex + j]; + const modifiedCell = modifiedModel.cells[modifiedCellIndex + j]; + if (originalCell.getHashValue() === modifiedCell.getHashValue()) { + cellDiffInfo.push({ + originalCellIndex: originalCellIndex + j, + modifiedCellIndex: modifiedCellIndex + j, + type: 'unchanged' + }); + } else { + if (firstChangeIndex === -1) { + firstChangeIndex = cellDiffInfo.length; + } + cellDiffInfo.push({ + originalCellIndex: originalCellIndex + j, + modifiedCellIndex: modifiedCellIndex + j, + type: 'modified' + }); + } + } + + const modifiedLCS = computeModifiedLCS(change, originalModel, modifiedModel); + if (modifiedLCS.length && firstChangeIndex === -1) { + firstChangeIndex = cellDiffInfo.length; + } + + cellDiffInfo.push(...modifiedLCS); + originalCellIndex = change.originalStart + change.originalLength; + modifiedCellIndex = change.modifiedStart + change.modifiedLength; + } + + for (let i = originalCellIndex; i < originalModel.cells.length; i++) { + cellDiffInfo.push({ + originalCellIndex: i, + modifiedCellIndex: i - originalCellIndex + modifiedCellIndex, + type: 'unchanged' + }); + } + + return { + cellDiffInfo, + firstChangeIndex + }; +} +function isEqual(cellDiffInfo: CellDiffInfo[], viewModels: DiffElementCellViewModelBase[], model: INotebookDiffEditorModel) { + if (cellDiffInfo.length !== viewModels.length) { + return false; + } + const originalModel = model.original.notebook; + const modifiedModel = model.modified.notebook; + for (let i = 0; i < viewModels.length; i++) { + const a = cellDiffInfo[i]; + const b = viewModels[i]; + if (a.type !== b.type) { + return false; + } + switch (a.type) { + case 'delete': { + if (originalModel.cells[a.originalCellIndex].handle !== b.original?.handle) { + return false; + } + continue; + } + case 'insert': { + if (modifiedModel.cells[a.modifiedCellIndex].handle !== b.modified?.handle) { + return false; + } + continue; + } + default: { + if (originalModel.cells[a.originalCellIndex].handle !== b.original?.handle) { + return false; + } + if (modifiedModel.cells[a.modifiedCellIndex].handle !== b.modified?.handle) { + return false; + } + continue; + } + } + } + + return true; +} + +function createDiffViewModels(instantiationService: IInstantiationService, configurationService: IConfigurationService, model: INotebookDiffEditorModel, eventDispatcher: NotebookDiffEditorEventDispatcher, computedCellDiffs: CellDiffInfo[], fontInfo: FontInfo | undefined, notebookService: INotebookService) { + const originalModel = model.original.notebook; + const modifiedModel = model.modified.notebook; + const initData = { + metadataStatusHeight: configurationService.getValue('notebook.diff.ignoreMetadata') ? 0 : 25, + outputStatusHeight: configurationService.getValue('notebook.diff.ignoreOutputs') || !!(modifiedModel.transientOptions.transientOutputs) ? 0 : 25, + fontInfo + }; + + return computedCellDiffs.map(diff => { + switch (diff.type) { + case 'delete': { + return new SingleSideDiffElementViewModel( + originalModel, + modifiedModel, + originalModel.cells[diff.originalCellIndex], + undefined, + 'delete', + eventDispatcher, + initData, + notebookService + ); + } + case 'insert': { + return new SingleSideDiffElementViewModel( + modifiedModel, + originalModel, + undefined, + modifiedModel.cells[diff.modifiedCellIndex], + 'insert', + eventDispatcher, + initData, + notebookService + ); + } + case 'modified': { + return new SideBySideDiffElementViewModel( + model.modified.notebook, + model.original.notebook, + originalModel.cells[diff.originalCellIndex], + modifiedModel.cells[diff.modifiedCellIndex], + 'modified', + eventDispatcher, + initData, + notebookService + ); + } + case 'unchanged': { + return new SideBySideDiffElementViewModel( + model.modified.notebook, + model.original.notebook, + originalModel.cells[diff.originalCellIndex], + modifiedModel.cells[diff.modifiedCellIndex], + 'unchanged', eventDispatcher, + initData, + notebookService + ); + } + } + }); +} + +function computeModifiedLCS(change: IDiffChange, originalModel: NotebookTextModel, modifiedModel: NotebookTextModel) { + const result: CellDiffInfo[] = []; + // modified cells + const modifiedLen = Math.min(change.originalLength, change.modifiedLength); + + for (let j = 0; j < modifiedLen; j++) { + const isTheSame = originalModel.cells[change.originalStart + j].equal(modifiedModel.cells[change.modifiedStart + j]); + result.push({ + originalCellIndex: change.originalStart + j, + modifiedCellIndex: change.modifiedStart + j, + type: isTheSame ? 'unchanged' : 'modified' + }); + } + + for (let j = modifiedLen; j < change.originalLength; j++) { + // deletion + result.push({ + originalCellIndex: change.originalStart + j, + type: 'delete' + }); + } + + for (let j = modifiedLen; j < change.modifiedLength; j++) { + result.push({ + modifiedCellIndex: change.modifiedStart + j, + type: 'insert' + }); + } + + return result; +} + + +export abstract class NotebookMultiDiffEditorItem extends MultiDiffEditorItem { + constructor( + originalUri: URI | undefined, + modifiedUri: URI | undefined, + goToFileUri: URI | undefined, + public readonly type: IDiffElementViewModelBase['type'], + public readonly containerType: IDiffElementViewModelBase['type'], + public kind: 'Cell' | 'Metadata' | 'Output', + contextKeys?: Record, + ) { + super(originalUri, modifiedUri, goToFileUri, contextKeys); + } +} + +class NotebookMultiDiffEditorCellItem extends NotebookMultiDiffEditorItem { + constructor( + originalUri: URI | undefined, + modifiedUri: URI | undefined, + type: IDiffElementViewModelBase['type'], + containerType: IDiffElementViewModelBase['type'], + ) { + super(originalUri, modifiedUri, modifiedUri || originalUri, type, containerType, 'Cell', { + [NOTEBOOK_DIFF_ITEM_KIND.key]: 'Cell', + [NOTEBOOK_DIFF_ITEM_DIFF_STATE.key]: type + }); + } +} + +class NotebookMultiDiffEditorMetadataItem extends NotebookMultiDiffEditorItem { + constructor( + originalUri: URI | undefined, + modifiedUri: URI | undefined, + type: IDiffElementViewModelBase['type'], + containerType: IDiffElementViewModelBase['type'], + ) { + super(originalUri, modifiedUri, modifiedUri || originalUri, type, containerType, 'Metadata', { + [NOTEBOOK_DIFF_ITEM_KIND.key]: 'Metadata', + [NOTEBOOK_DIFF_ITEM_DIFF_STATE.key]: type + }); + } +} + +class NotebookMultiDiffEditorOutputItem extends NotebookMultiDiffEditorItem { + constructor( + originalUri: URI | undefined, + modifiedUri: URI | undefined, + type: IDiffElementViewModelBase['type'], + containerType: IDiffElementViewModelBase['type'], + ) { + super(originalUri, modifiedUri, modifiedUri || originalUri, type, containerType, 'Output', { + [NOTEBOOK_DIFF_ITEM_KIND.key]: 'Output', + [NOTEBOOK_DIFF_ITEM_DIFF_STATE.key]: type + }); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor.ts new file mode 100644 index 00000000..42740fee --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor.ts @@ -0,0 +1,283 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { IWorkbenchUIElementFactory, type IResourceLabel } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IEditorOpenContext } from 'vs/workbench/common/editor'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; +import { PixelRatio } from 'vs/base/browser/pixelRatio'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { CellUri, INotebookDiffEditorModel, NOTEBOOK_MULTI_DIFF_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements'; +import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { NotebookMultiDiffEditorInput, NotebookMultiDiffEditorWidgetInput } from 'vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditorInput'; +import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import type { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; +import { INotebookDocumentService } from 'vs/workbench/services/notebook/common/notebookDocumentService'; +import { localize } from 'vs/nls'; +import { Schemas } from 'vs/base/common/network'; +import { getIconClassesForLanguageId } from 'vs/editor/common/services/getIconClasses'; +import { NotebookDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel'; +import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; +import { NOTEBOOK_DIFF_CELLS_COLLAPSED, NOTEBOOK_DIFF_HAS_UNCHANGED_CELLS, NOTEBOOK_DIFF_UNCHANGED_CELLS_HIDDEN } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import type { DocumentDiffItemViewModel, MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; +import type { URI } from 'vs/base/common/uri'; +import { type IDiffElementViewModelBase } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { autorun, transaction } from 'vs/base/common/observable'; + +export class NotebookMultiTextDiffEditor extends EditorPane { + private _multiDiffEditorWidget?: MultiDiffEditorWidget; + static readonly ID: string = NOTEBOOK_MULTI_DIFF_EDITOR_ID; + private _fontInfo: FontInfo | undefined; + protected _scopeContextKeyService!: IContextKeyService; + private readonly modelSpecificResources = this._register(new DisposableStore()); + private _model?: INotebookDiffEditorModel; + private viewModel?: NotebookDiffViewModel; + private widgetViewModel?: MultiDiffEditorViewModel; + get textModel() { + return this._model?.modified.notebook; + } + private _notebookOptions: NotebookOptions; + get notebookOptions() { + return this._notebookOptions; + } + private readonly ctxAllCollapsed = this._parentContextKeyService.createKey(NOTEBOOK_DIFF_CELLS_COLLAPSED.key, false); + private readonly ctxHasUnchangedCells = this._parentContextKeyService.createKey(NOTEBOOK_DIFF_HAS_UNCHANGED_CELLS.key, false); + private readonly ctxHiddenUnchangedCells = this._parentContextKeyService.createKey(NOTEBOOK_DIFF_UNCHANGED_CELLS_HIDDEN.key, true); + + constructor( + group: IEditorGroup, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, + @INotebookEditorWorkerService private readonly notebookEditorWorkerService: INotebookEditorWorkerService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @INotebookService private readonly notebookService: INotebookService, + ) { + super(NotebookMultiTextDiffEditor.ID, group, telemetryService, themeService, storageService); + this._notebookOptions = instantiationService.createInstance(NotebookOptions, this.window, false, undefined); + this._register(this._notebookOptions); + } + + private get fontInfo() { + if (!this._fontInfo) { + this._fontInfo = this.createFontInfo(); + } + + return this._fontInfo; + } + override layout(dimension: DOM.Dimension, position?: DOM.IDomPosition): void { + this._multiDiffEditorWidget!.layout(dimension); + } + + private createFontInfo() { + const editorOptions = this.configurationService.getValue('editor'); + return FontMeasurements.readFontInfo(this.window, BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.window).value)); + } + + protected createEditor(parent: HTMLElement): void { + this._multiDiffEditorWidget = this._register(this.instantiationService.createInstance( + MultiDiffEditorWidget, + parent, + this.instantiationService.createInstance(WorkbenchUIElementFactory), + )); + + this._register(this._multiDiffEditorWidget.onDidChangeActiveControl(() => { + this._onDidChangeControl.fire(); + })); + } + override async setInput(input: NotebookMultiDiffEditorInput, options: IMultiDiffEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + super.setInput(input, options, context, token); + const model = await input.resolve(); + if (this._model !== model) { + this._detachModel(); + this._model = model; + } + const eventDispatcher = this.modelSpecificResources.add(new NotebookDiffEditorEventDispatcher()); + this.viewModel = this.modelSpecificResources.add(new NotebookDiffViewModel(model, this.notebookEditorWorkerService, this.instantiationService, this.configurationService, eventDispatcher, this.notebookService, undefined, true)); + await this.viewModel.computeDiff(this.modelSpecificResources.add(new CancellationTokenSource()).token); + this.ctxHasUnchangedCells.set(this.viewModel.hasUnchangedCells); + this.ctxHasUnchangedCells.set(this.viewModel.hasUnchangedCells); + + const widgetInput = this.modelSpecificResources.add(NotebookMultiDiffEditorWidgetInput.createInput(this.viewModel, this.instantiationService)); + this.widgetViewModel = this.modelSpecificResources.add(await widgetInput.getViewModel()); + + const itemsWeHaveSeen = new WeakSet(); + this.modelSpecificResources.add(autorun(reader => { + /** @description NotebookDiffEditor => Collapse unmodified items */ + if (!this.widgetViewModel || !this.viewModel) { + return; + } + const items = this.widgetViewModel.items.read(reader); + const diffItems = this.viewModel.value; + if (items.length !== diffItems.length) { + return; + } + + // If cell has not changed, but metadata or output has changed, then collapse the cell & keep output/metadata expanded. + // Similarly if the cell has changed, but the metadata or output has not, then expand the cell, but collapse output/metadata. + transaction((tx) => { + items.forEach(item => { + // We do not want to mess with UI state if users change it, hence no need to collapse again. + if (itemsWeHaveSeen.has(item)) { + return; + } + itemsWeHaveSeen.add(item); + const diffItem = diffItems.find(d => d.modifiedUri?.toString() === item.modifiedUri?.toString() && d.originalUri?.toString() === item.originalUri?.toString()); + if (diffItem && diffItem.type === 'unchanged') { + item.collapsed.set(true, tx); + } + }); + }); + })); + + + this._multiDiffEditorWidget!.setViewModel(this.widgetViewModel); + } + + private _detachModel() { + this.viewModel = undefined; + this.modelSpecificResources.clear(); + } + _generateFontFamily(): string { + return this.fontInfo.fontFamily ?? `"SF Mono", Monaco, Menlo, Consolas, "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace`; + } + override setOptions(options: IMultiDiffEditorOptions | undefined): void { + super.setOptions(options); + } + + override getControl() { + return this._multiDiffEditorWidget!.getActiveControl(); + } + + override focus(): void { + super.focus(); + + this._multiDiffEditorWidget?.getActiveControl()?.focus(); + } + + override hasFocus(): boolean { + return this._multiDiffEditorWidget?.getActiveControl()?.hasTextFocus() || super.hasFocus(); + } + + override clearInput(): void { + super.clearInput(); + this._multiDiffEditorWidget!.setViewModel(undefined); + this.modelSpecificResources.clear(); + this.viewModel = undefined; + this.widgetViewModel = undefined; + } + + public expandAll() { + if (this.widgetViewModel) { + this.widgetViewModel.expandAll(); + this.ctxAllCollapsed.set(false); + } + } + public collapseAll() { + if (this.widgetViewModel) { + this.widgetViewModel.collapseAll(); + this.ctxAllCollapsed.set(true); + } + } + + public hideUnchanged() { + if (this.viewModel) { + this.viewModel.includeUnchanged = false; + this.ctxHiddenUnchangedCells.set(true); + } + } + + public showUnchanged() { + if (this.viewModel) { + this.viewModel.includeUnchanged = true; + this.ctxHiddenUnchangedCells.set(false); + } + } + + public getDiffElementViewModel(uri: URI): IDiffElementViewModelBase | undefined { + if (uri.scheme === Schemas.vscodeNotebookCellOutput || uri.scheme === Schemas.vscodeNotebookCellOutputDiff || + uri.scheme === Schemas.vscodeNotebookCellMetadata || uri.scheme === Schemas.vscodeNotebookCellMetadataDiff + ) { + const data = CellUri.parseCellPropertyUri(uri, uri.scheme); + if (data) { + uri = CellUri.generate(data.notebook, data.handle); + } + } + return this.viewModel?.items.find(c => { + switch (c.type) { + case 'delete': + return c.original?.uri.toString() === uri.toString(); + case 'insert': + return c.modified?.uri.toString() === uri.toString(); + case 'modified': + case 'unchanged': + return c.modified?.uri.toString() === uri.toString() || c.original?.uri.toString() === uri.toString(); + default: + return; + } + }); + } +} + + +class WorkbenchUIElementFactory implements IWorkbenchUIElementFactory { + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @INotebookDocumentService private readonly notebookDocumentService: INotebookDocumentService, + @INotebookService private readonly notebookService: INotebookService + ) { } + + createResourceLabel(element: HTMLElement): IResourceLabel { + const label = this._instantiationService.createInstance(ResourceLabel, element, {}); + const that = this; + return { + setUri(uri, options = {}) { + if (!uri) { + label.element.clear(); + } else { + let name = ''; + let description = ''; + let extraClasses: string[] | undefined = undefined; + + if (uri.scheme === Schemas.vscodeNotebookCell) { + const notebookDocument = uri.scheme === Schemas.vscodeNotebookCell ? that.notebookDocumentService.getNotebook(uri) : undefined; + const cellIndex = Schemas.vscodeNotebookCell ? that.notebookDocumentService.getNotebook(uri)?.getCellIndex(uri) : undefined; + if (notebookDocument && cellIndex !== undefined) { + name = localize('notebookCellLabel', "Cell {0}", `${cellIndex + 1}`); + const nb = notebookDocument ? that.notebookService.getNotebookTextModel(notebookDocument?.uri) : undefined; + const cellLanguage = nb && cellIndex !== undefined ? nb.cells[cellIndex].language : undefined; + extraClasses = cellLanguage ? getIconClassesForLanguageId(cellLanguage) : undefined; + } + } else if (uri.scheme === Schemas.vscodeNotebookCellMetadata || uri.scheme === Schemas.vscodeNotebookCellMetadataDiff) { + description = localize('notebookCellMetadataLabel', "Metadata"); + } else if (uri.scheme === Schemas.vscodeNotebookCellOutput || uri.scheme === Schemas.vscodeNotebookCellOutputDiff) { + description = localize('notebookCellOutputLabel', "Output"); + } + + label.element.setResource({ name, description }, { strikethrough: options.strikethrough, forceLabel: true, hideIcon: !extraClasses, extraClasses }); + } + }, + dispose() { + label.dispose(); + } + }; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditorInput.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditorInput.ts new file mode 100644 index 00000000..e0f66116 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditorInput.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; +import { IMultiDiffSourceResolverService, IResolvedMultiDiffSource, type IMultiDiffSourceResolver } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; +import { NotebookDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel'; +import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/common/notebookDiffEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; + +export const NotebookMultiDiffEditorScheme = 'multi-cell-notebook-diff-editor'; + +export class NotebookMultiDiffEditorInput extends NotebookDiffEditorInput { + static override readonly ID: string = 'workbench.input.multiDiffNotebookInput'; + static override create(instantiationService: IInstantiationService, resource: URI, name: string | undefined, description: string | undefined, originalResource: URI, viewType: string) { + const original = NotebookEditorInput.getOrCreate(instantiationService, originalResource, undefined, viewType); + const modified = NotebookEditorInput.getOrCreate(instantiationService, resource, undefined, viewType); + return instantiationService.createInstance(NotebookMultiDiffEditorInput, name, description, original, modified, viewType); + } +} + +export class NotebookMultiDiffEditorWidgetInput extends MultiDiffEditorInput implements IMultiDiffSourceResolver { + public static createInput(notebookDiffViewModel: NotebookDiffViewModel, instantiationService: IInstantiationService): NotebookMultiDiffEditorWidgetInput { + const multiDiffSource = URI.parse(`${NotebookMultiDiffEditorScheme}:${new Date().getMilliseconds().toString() + Math.random().toString()}`); + return instantiationService.createInstance( + NotebookMultiDiffEditorWidgetInput, + multiDiffSource, + notebookDiffViewModel + ); + } + constructor( + multiDiffSource: URI, + private readonly notebookDiffViewModel: NotebookDiffViewModel, + @ITextModelService _textModelService: ITextModelService, + @ITextResourceConfigurationService _textResourceConfigurationService: ITextResourceConfigurationService, + @IInstantiationService _instantiationService: IInstantiationService, + @IMultiDiffSourceResolverService _multiDiffSourceResolverService: IMultiDiffSourceResolverService, + @ITextFileService _textFileService: ITextFileService, + ) { + super(multiDiffSource, undefined, undefined, true, _textModelService, _textResourceConfigurationService, _instantiationService, _multiDiffSourceResolverService, _textFileService); + this._register(_multiDiffSourceResolverService.registerResolver(this)); + } + + canHandleUri(uri: URI): boolean { + return uri.toString() === this.multiDiffSource.toString(); + } + + async resolveDiffSource(_: URI): Promise { + return { resources: this.notebookDiffViewModel }; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/patched-vscode/src/vs/workbench/contrib/notebook/browser/media/notebook.css index d659b3cf..daa4088d 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -353,6 +353,8 @@ .cell-comment-container.review-widget { border-left: 1px solid var(--vscode-peekView-border); border-right: 1px solid var(--vscode-peekView-border); + /* Restore text-wrap to default value to avoid inheriting nowrap from monaco-list. */ + text-wrap: initial; } .cell-comment-container.review-widget > .head { @@ -446,6 +448,13 @@ background-color: var(--vscode-notebook-symbolHighlightBackground) !important; } +/** Cell Multi-Selection highlight */ +.nb-multicursor-selection { + min-width: 2px; + background-color: fuchsia; +} + + /** Cell Search Range selection highlight */ .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.nb-multiCellHighlight .cell-focus-indicator, .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row.nb-multiCellHighlight { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css b/patched-vscode/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css index 4577677c..68974e01 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/media/notebookCellChat.css @@ -188,14 +188,6 @@ user-select: text; } -.monaco-workbench .notebookOverlay .cell-chat-part .inline-chat .markdownMessage .message[state="cropped"] { - -webkit-line-clamp: var(--vscode-inline-chat-cropped, 3); -} - -.monaco-workbench .notebookOverlay .cell-chat-part .inline-chat .markdownMessage .message[state="expanded"] { - -webkit-line-clamp: var(--vscode-inline-chat-expanded, 10); -} - .monaco-workbench .notebookOverlay .cell-chat-part .inline-chat .status .label A { color: var(--vscode-textLink-foreground); cursor: pointer; @@ -352,4 +344,3 @@ .monaco-workbench .notebookOverlay .cell-chat-part .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent:hover { opacity: 1; } - diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 0f9026c0..090aed7a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -29,7 +29,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd import { NotebookEditorInput, NotebookEditorInputOptions } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/services/notebookServiceImpl'; -import { CellKind, CellUri, IResolvedNotebookEditorModel, NotebookWorkingCopyTypeIdentifier, NotebookSetting, ICellOutput, ICell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, IResolvedNotebookEditorModel, NotebookWorkingCopyTypeIdentifier, NotebookSetting, ICellOutput, ICell, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; @@ -44,7 +44,7 @@ import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/brows import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Event } from 'vs/base/common/event'; -import { getFormattedMetadataJSON, getStreamOutputData } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { getFormattedMetadataJSON, getFormattedOutputJSON, getStreamOutputData } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { NotebookModelResolverServiceImpl } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl'; import { INotebookKernelHistoryService, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl'; @@ -57,7 +57,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/browser/services/notebookRendererMessagingServiceImpl'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; -import { INotebookCellOutlineProviderFactory, NotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory'; +import { INotebookCellOutlineDataSourceFactory, NotebookCellOutlineDataSourceFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory'; // Editor Controller import 'vs/workbench/contrib/notebook/browser/controller/coreActions'; @@ -97,6 +97,7 @@ import 'vs/workbench/contrib/notebook/browser/contrib/debug/notebookDebugDecorat import 'vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress'; import 'vs/workbench/contrib/notebook/browser/contrib/kernelDetection/notebookKernelDetection'; import 'vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics'; +import 'vs/workbench/contrib/notebook/browser/contrib/multicursor/notebookMulticursor'; // Diff Editor Contribution import 'vs/workbench/contrib/notebook/browser/diff/notebookDiffActions'; @@ -123,6 +124,8 @@ import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/access import { NotebookAccessibilityHelp } from 'vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp'; import { NotebookAccessibleView } from 'vs/workbench/contrib/notebook/browser/notebookAccessibleView'; import { DefaultFormatter } from 'vs/workbench/contrib/format/browser/formatActionsMultiple'; +import { NotebookMultiTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditor'; +import { NotebookMultiDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditorInput'; /*--------------------------------------------------------------------------------------------- */ @@ -148,7 +151,19 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane ] ); +Registry.as(EditorExtensions.EditorPane).registerEditorPane( + EditorPaneDescriptor.create( + NotebookMultiTextDiffEditor, + NotebookMultiTextDiffEditor.ID, + 'Notebook Diff Editor' + ), + [ + new SyncDescriptor(NotebookMultiDiffEditorInput) + ] +); + class NotebookDiffEditorSerializer implements IEditorSerializer { + constructor(@IConfigurationService private readonly _configurationService: IConfigurationService) { } canSerialize(): boolean { return true; } @@ -176,8 +191,11 @@ class NotebookDiffEditorSerializer implements IEditorSerializer { return undefined; } - const input = NotebookDiffEditorInput.create(instantiationService, resource, name, undefined, originalResource, viewType); - return input; + if (this._configurationService.getValue('notebook.experimental.enableNewDiffEditor')) { + return NotebookMultiDiffEditorInput.create(instantiationService, resource, name, undefined, originalResource, viewType); + } else { + return NotebookDiffEditorInput.create(instantiationService, resource, name, undefined, originalResource, viewType); + } } static canResolveBackup(editorInput: EditorInput, backupResource: URI): boolean { @@ -187,8 +205,8 @@ class NotebookDiffEditorSerializer implements IEditorSerializer { } type SerializedNotebookEditorData = { resource: URI; preferredResource: URI; viewType: string; options?: NotebookEditorInputOptions }; class NotebookEditorSerializer implements IEditorSerializer { - canSerialize(): boolean { - return true; + canSerialize(input: EditorInput): boolean { + return input.typeId === NotebookEditorInput.ID; } serialize(input: EditorInput): string { assertType(input instanceof NotebookEditorInput); @@ -420,15 +438,24 @@ class CellInfoContentProvider { let result: ITextModel | null = null; const mode = this._languageService.createById('json'); - + const disposables = new DisposableStore(); for (const cell of ref.object.notebook.cells) { if (cell.handle === data.handle) { + const cellIndex = ref.object.notebook.cells.indexOf(cell); const metadataSource = getFormattedMetadataJSON(ref.object.notebook, cell.metadata, cell.language); result = this._modelService.createModel( metadataSource, mode, resource ); + this._disposables.push(disposables.add(ref.object.notebook.onDidChangeContent(e => { + if (result && e.rawEvents.some(event => (event.kind === NotebookCellsChangeType.ChangeCellMetadata || event.kind === NotebookCellsChangeType.ChangeCellLanguage) && event.index === cellIndex)) { + const value = getFormattedMetadataJSON(ref.object.notebook, cell.metadata, cell.language); + if (result.getValue() !== value) { + result.setValue(getFormattedMetadataJSON(ref.object.notebook, cell.metadata, cell.language)); + } + } + }))); break; } } @@ -439,6 +466,7 @@ class CellInfoContentProvider { } const once = result.onWillDispose(() => { + disposables.dispose(); once.dispose(); ref.dispose(); }); @@ -493,6 +521,40 @@ class CellInfoContentProvider { return result; } + async provideOutputsTextContent(resource: URI): Promise { + const existing = this._modelService.getModel(resource); + if (existing) { + return existing; + } + + const data = CellUri.parseCellPropertyUri(resource, Schemas.vscodeNotebookCellOutput); + if (!data) { + return null; + } + + const ref = await this._notebookModelResolverService.resolve(data.notebook); + const cell = ref.object.notebook.cells.find(cell => cell.handle === data.handle); + + if (!cell) { + ref.dispose(); + return null; + } + + const mode = this._languageService.createById('json'); + const model = this._modelService.createModel(getFormattedOutputJSON(cell.outputs || []), mode, resource, true); + const cellModelListener = Event.any(cell.onDidChangeOutputs ?? Event.None, cell.onDidChangeOutputItems ?? Event.None)(() => { + model.setValue(getFormattedOutputJSON(cell.outputs || [])); + }); + + const once = model.onWillDispose(() => { + once.dispose(); + cellModelListener.dispose(); + ref.dispose(); + }); + + return model; + } + async provideOutputTextContent(resource: URI): Promise { const existing = this._modelService.getModel(resource); if (existing) { @@ -501,7 +563,7 @@ class CellInfoContentProvider { const data = CellUri.parseCellOutputUri(resource); if (!data) { - return null; + return this.provideOutputsTextContent(resource); } const ref = await this._notebookModelResolverService.resolve(data.notebook); @@ -644,7 +706,7 @@ class SimpleNotebookWorkingCopyEditorHandler extends Disposable implements IWork private handlesSync(workingCopy: IWorkingCopyIdentifier): string /* viewType */ | undefined { const viewType = this._getViewType(workingCopy); - if (!viewType || viewType === 'interactive') { + if (!viewType || viewType === 'interactive' || extname(workingCopy.resource) === '.replNotebook') { return undefined; } @@ -726,7 +788,7 @@ registerSingleton(INotebookExecutionStateService, NotebookExecutionStateService, registerSingleton(INotebookRendererMessagingService, NotebookRendererMessagingService, InstantiationType.Delayed); registerSingleton(INotebookKeymapService, NotebookKeymapService, InstantiationType.Delayed); registerSingleton(INotebookLoggingService, NotebookLoggingService, InstantiationType.Delayed); -registerSingleton(INotebookCellOutlineProviderFactory, NotebookCellOutlineProviderFactory, InstantiationType.Delayed); +registerSingleton(INotebookCellOutlineDataSourceFactory, NotebookCellOutlineDataSourceFactory, InstantiationType.Delayed); const schemas: IJSONSchemaMap = {}; function isConfigurationPropertySchema(x: IConfigurationPropertySchema | { [path: string]: IConfigurationPropertySchema }): x is IConfigurationPropertySchema { @@ -1004,18 +1066,6 @@ configurationRegistry.registerConfiguration({ tags: ['notebookLayout'], default: false }, - [NotebookSetting.codeActionsOnSave]: { - markdownDescription: nls.localize('notebook.codeActionsOnSave', 'Run a series of Code Actions for a notebook on save. Code Actions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `"notebook.source.organizeImports": "explicit"`'), - type: 'object', - additionalProperties: { - type: ['string', 'boolean'], - enum: ['explicit', 'never', true, false], - // enum: ['explicit', 'always', 'never'], -- autosave support needs to be built first - // nls.localize('always', 'Always triggers Code Actions on save, including autosave, focus, and window change events.'), - enumDescriptions: [nls.localize('explicit', 'Triggers Code Actions only when explicitly saved.'), nls.localize('never', 'Never triggers Code Actions on save.'), nls.localize('explicitBoolean', 'Triggers Code Actions only when explicitly saved. This value will be deprecated in favor of "explicit".'), nls.localize('neverBoolean', 'Triggers Code Actions only when explicitly saved. This value will be deprecated in favor of "never".')], - }, - default: {} - }, [NotebookSetting.formatOnCellExecution]: { markdownDescription: nls.localize('notebook.formatOnCellExecution', "Format a notebook cell upon execution. A formatter must be available."), type: 'boolean', @@ -1055,11 +1105,6 @@ configurationRegistry.registerConfiguration({ }, tags: ['notebookLayout'] }, - [NotebookSetting.findScope]: { - markdownDescription: nls.localize('notebook.experimental.find.scope.enabled', "Enables the user to search within a selection of cells in the notebook. When enabled, the user will see a \"Find in Cell Selection\" icon in the notebook find widget."), - type: 'boolean', - default: false, - }, [NotebookSetting.remoteSaving]: { markdownDescription: nls.localize('notebook.remoteSaving', "Enables the incremental saving of notebooks between processes and across Remote connections. When enabled, only the changes to the notebook are sent to the extension host, improving performance for large notebooks and slow network connections."), type: 'boolean', @@ -1077,11 +1122,6 @@ configurationRegistry.registerConfiguration({ ], default: 'fullCell' }, - [NotebookSetting.cellChat]: { - markdownDescription: nls.localize('notebook.cellChat', "Enable experimental floating chat widget in notebooks."), - type: 'boolean', - default: false - }, [NotebookSetting.cellGenerate]: { markdownDescription: nls.localize('notebook.cellGenerate', "Enable experimental generate action to create code cell with inline chat enabled."), type: 'boolean', diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts index b146d0f7..1068c933 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; -import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { localize } from 'vs/nls'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IVisibleEditorPane } from 'vs/workbench/common/editor'; @@ -16,47 +16,42 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService export class NotebookAccessibilityHelp implements IAccessibleViewImplentation { readonly priority = 105; readonly name = 'notebook'; - readonly when = NOTEBOOK_IS_ACTIVE_EDITOR; + readonly when = NOTEBOOK_EDITOR_FOCUSED; readonly type: AccessibleViewType = AccessibleViewType.Help; getProvider(accessor: ServicesAccessor) { const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor() || accessor.get(IEditorService).activeEditorPane; - if (activeEditor) { - return runAccessibilityHelpAction(accessor, activeEditor); + if (!activeEditor) { + return; } - return; + return getAccessibilityHelpProvider(accessor, activeEditor); } - dispose() { } } - - export function getAccessibilityHelpText(): string { return [ localize('notebook.overview', 'The notebook view is a collection of code and markdown cells. Code cells can be executed and will produce output directly below the cell.'), - localize('notebook.cell.edit', 'The Edit Cell command will focus on the cell input.'), - localize('notebook.cell.quitEdit', 'The Quit Edit command will set focus on the cell container. The default (Escape) key may need to be pressed twice first exit the virtual cursor if active.'), - localize('notebook.cell.focusInOutput', 'The Focus Output command will set focus in the cell\'s output.'), - localize('notebook.focusNextEditor', 'The Focus Next Cell Editor command will set focus in the next cell\'s editor.'), - localize('notebook.focusPreviousEditor', 'The Focus Previous Cell Editor command will set focus in the previous cell\'s editor.'), + localize('notebook.cell.edit', 'The Edit Cell command{0} will focus on the cell input.', ''), + localize('notebook.cell.quitEdit', 'The Quit Edit command{0} will set focus on the cell container. The default (Escape) key may need to be pressed twice first exit the virtual cursor if active.', ''), + localize('notebook.cell.focusInOutput', 'The Focus Output command{0} will set focus in the cell\'s output.', ''), + localize('notebook.focusNextEditor', 'The Focus Next Cell Editor command{0} will set focus in the next cell\'s editor.', ''), + localize('notebook.focusPreviousEditor', 'The Focus Previous Cell Editor command{0} will set focus in the previous cell\'s editor.', ''), localize('notebook.cellNavigation', 'The up and down arrows will also move focus between cells while focused on the outer cell container.'), - localize('notebook.cell.executeAndFocusContainer', 'The Execute Cell command executes the cell that currently has focus.',), - localize('notebook.cell.insertCodeCellBelowAndFocusContainer', 'The Insert Cell Above/Below commands will create new empty code cells'), + localize('notebook.cell.executeAndFocusContainer', 'The Execute Cell command{0} executes the cell that currently has focus.', ''), + localize('notebook.cell.insertCodeCellBelowAndFocusContainer', 'The Insert Cell Above{0} and Below{1} commands will create new empty code cells.', '', ''), localize('notebook.changeCellType', 'The Change Cell to Code/Markdown commands are used to switch between cell types.') - ].join('\n\n'); + ].join('\n'); } -export function runAccessibilityHelpAction(accessor: ServicesAccessor, editor: ICodeEditor | IVisibleEditorPane) { +export function getAccessibilityHelpProvider(accessor: ServicesAccessor, editor: ICodeEditor | IVisibleEditorPane) { const helpText = getAccessibilityHelpText(); - return { - id: AccessibleViewProviderId.Notebook, - verbositySettingKey: AccessibilityVerbositySettingId.Notebook, - provideContent: () => helpText, - onClose: () => { - editor.focus(); - }, - options: { type: AccessibleViewType.Help } - }; + return new AccessibleContentProvider( + AccessibleViewProviderId.Notebook, + { type: AccessibleViewType.Help }, + () => helpText, + () => editor.focus(), + AccessibilityVerbositySettingId.Notebook, + ); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityProvider.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityProvider.ts index 948db4cd..33a38169 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityProvider.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityProvider.ts @@ -46,7 +46,7 @@ export class NotebookAccessibilityProvider extends Disposable implements IListAc getAriaLabel(element: CellViewModel) { const event = Event.filter(this.onDidAriaLabelChange, e => e === element); - return observableFromEvent(event, () => { + return observableFromEvent(this, event, () => { const viewModel = this.viewModel(); if (!viewModel) { return ''; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts index daa9bae8..8771a492 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.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 { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -18,13 +18,12 @@ export class NotebookAccessibleView implements IAccessibleViewImplentation { readonly when = ContextKeyExpr.and(NOTEBOOK_OUTPUT_FOCUSED, ContextKeyExpr.equals('resourceExtname', '.ipynb')); getProvider(accessor: ServicesAccessor) { const editorService = accessor.get(IEditorService); - return showAccessibleOutput(editorService); + return getAccessibleOutputProvider(editorService); } - dispose() { } } -export function showAccessibleOutput(editorService: IEditorService) { +export function getAccessibleOutputProvider(editorService: IEditorService) { const activePane = editorService.activeEditorPane; const notebookEditor = getNotebookEditorFromEditorPane(activePane); const notebookViewModel = notebookEditor?.getViewModel(); @@ -73,15 +72,15 @@ export function showAccessibleOutput(editorService: IEditorService) { return; } - return { - id: AccessibleViewProviderId.Notebook, - verbositySettingKey: AccessibilityVerbositySettingId.Notebook, - provideContent(): string { return outputContent; }, - onClose() { + return new AccessibleContentProvider( + AccessibleViewProviderId.Notebook, + { type: AccessibleViewType.View }, + () => { return outputContent; }, + () => { notebookEditor?.setFocus(selections[0]); activePane?.focus(); }, - options: { type: AccessibleViewType.View } - }; + AccessibilityVerbositySettingId.Notebook, + ); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index f474d202..49d3306e 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -22,15 +22,16 @@ import { IEditorPane, IEditorPaneWithSelection } from 'vs/workbench/common/edito import { CellViewModelStateChangeEvent, NotebookCellStateChangedEvent, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, ICellOutput, INotebookCellStatusBarItem, INotebookRendererInfo, INotebookSearchOptions, IOrderedMimeType, NotebookCellInternalMetadata, NotebookCellMetadata, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, ICellOutput, INotebookCellStatusBarItem, INotebookRendererInfo, INotebookFindOptions, IOrderedMimeType, NotebookCellInternalMetadata, NotebookCellMetadata, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { isCompositeNotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { cellRangesToIndexes, ICellRange, reduceCellRanges } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { IWebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IEditorCommentsOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IObservable } from 'vs/base/common/observable'; //#region Shared commands export const EXPAND_CELL_INPUT_COMMAND_ID = 'notebook.cell.expandCellInput'; @@ -108,6 +109,8 @@ export interface ICellOutputViewModel extends IDisposable { pickedMimeType: IOrderedMimeType | undefined; hasMultiMimeType(): boolean; readonly onDidResetRenderer: Event; + readonly visible: IObservable; + setVisible(visible: boolean, force?: boolean): void; resetRenderer(): void; toRawJSON(): any; } @@ -172,47 +175,47 @@ export enum CellLayoutState { Measured } -export interface CodeCellLayoutInfo { +/** LayoutInfo of the parts that are shared between all cell types. */ +export interface CellLayoutInfo { + readonly layoutState: CellLayoutState; readonly fontInfo: FontInfo | null; readonly chatHeight: number; - readonly editorHeight: number; readonly editorWidth: number; - readonly estimatedHasHorizontalScrolling: boolean; + readonly editorHeight: number; readonly statusBarHeight: number; + readonly commentOffset: number; readonly commentHeight: number; + readonly bottomToolbarOffset: number; readonly totalHeight: number; +} + +export interface CellLayoutChangeEvent { + readonly font?: FontInfo; + readonly outerWidth?: number; + readonly commentHeight?: boolean; +} + +export interface CodeCellLayoutInfo extends CellLayoutInfo { + readonly estimatedHasHorizontalScrolling: boolean; readonly outputContainerOffset: number; readonly outputTotalHeight: number; readonly outputShowMoreContainerHeight: number; readonly outputShowMoreContainerOffset: number; - readonly bottomToolbarOffset: number; - readonly layoutState: CellLayoutState; readonly codeIndicatorHeight: number; readonly outputIndicatorHeight: number; } -export interface CodeCellLayoutChangeEvent { +export interface CodeCellLayoutChangeEvent extends CellLayoutChangeEvent { readonly source?: string; readonly chatHeight?: boolean; readonly editorHeight?: boolean; - readonly commentHeight?: boolean; readonly outputHeight?: boolean; readonly outputShowMoreContainerHeight?: number; readonly totalHeight?: boolean; - readonly outerWidth?: number; - readonly font?: FontInfo; } -export interface MarkupCellLayoutInfo { - readonly fontInfo: FontInfo | null; - readonly chatHeight: number; - readonly editorWidth: number; - readonly editorHeight: number; - readonly statusBarHeight: number; +export interface MarkupCellLayoutInfo extends CellLayoutInfo { readonly previewHeight: number; - readonly bottomToolbarOffset: number; - readonly totalHeight: number; - readonly layoutState: CellLayoutState; readonly foldHintHeight: number; } @@ -220,9 +223,7 @@ export enum CellLayoutContext { Fold } -export interface MarkupCellLayoutChangeEvent { - readonly font?: FontInfo; - readonly outerWidth?: number; +export interface MarkupCellLayoutChangeEvent extends CellLayoutChangeEvent { readonly editorHeight?: number; readonly previewHeight?: number; totalHeight?: number; @@ -238,7 +239,7 @@ export interface ICellViewModel extends IGenericCellViewModel { readonly model: NotebookCellTextModel; readonly id: string; readonly textBuffer: IReadonlyTextBuffer; - readonly layoutInfo: { totalHeight: number; bottomToolbarOffset: number; editorWidth: number; editorHeight: number; statusBarHeight: number; chatHeight: number }; + readonly layoutInfo: CellLayoutInfo; readonly onDidChangeLayout: Event; readonly onDidChangeCellStatusBarItems: Event; readonly onCellDecorationsChanged: Event<{ added: INotebookCellDecorationOptions[]; removed: INotebookCellDecorationOptions[] }>; @@ -255,7 +256,9 @@ export interface ICellViewModel extends IGenericCellViewModel { readonly mime: string; cellKind: CellKind; lineNumbers: 'on' | 'off' | 'inherit'; + commentOptions: IEditorCommentsOptions; chatHeight: number; + commentHeight: number; focusMode: CellFocusMode; focusedOutputId?: string | undefined; outputIsHovered: boolean; @@ -269,6 +272,7 @@ export interface ICellViewModel extends IGenericCellViewModel { hasModel(): this is IEditableCellViewModel; resolveTextModel(): Promise; getSelections(): Selection[]; + setSelections(selections: Selection[]): void; getSelectionsStartPosition(): IPosition[] | undefined; getCellDecorations(): INotebookCellDecorationOptions[]; getCellStatusBarItems(): INotebookCellStatusBarItem[]; @@ -358,6 +362,7 @@ export interface INotebookEditorOptions extends ITextEditorOptions { readonly isReadOnly?: boolean; readonly viewState?: INotebookEditorViewState; readonly indexedCellOptions?: { index: number; selection?: IRange }; + readonly label?: string; } export type INotebookEditorContributionCtor = IConstructorSignature; @@ -383,6 +388,7 @@ export interface INotebookEditorCreationOptions { }; readonly options?: NotebookOptions; readonly codeWindow?: CodeWindow; + readonly forRepl?: boolean; } export interface INotebookWebviewMessage { @@ -448,7 +454,7 @@ export interface INotebookViewCellsUpdateEvent { export interface INotebookViewModel { notebookDocument: NotebookTextModel; - viewCells: ICellViewModel[]; + readonly viewCells: ICellViewModel[]; layoutInfo: NotebookLayoutInfo | null; onDidChangeViewCells: Event; onDidChangeSelection: Event; @@ -480,7 +486,9 @@ export interface INotebookEditor { readonly onDidFocusWidget: Event; readonly onDidBlurWidget: Event; readonly onDidScroll: Event; + readonly onDidChangeLayout: Event; readonly onDidChangeActiveCell: Event; + readonly onDidChangeActiveEditor: Event; readonly onDidChangeActiveKernel: Event; readonly onMouseUp: Event; readonly onMouseDown: Event; @@ -496,9 +504,14 @@ export interface INotebookEditor { readonly isDisposed: boolean; readonly activeKernel: INotebookKernel | undefined; readonly scrollTop: number; + readonly scrollBottom: number; readonly scopedContextKeyService: IContextKeyService; + /** + * Required for Composite Editor check. The interface should not be changed. + */ readonly activeCodeEditor: ICodeEditor | undefined; readonly codeEditors: [ICellViewModel, ICodeEditor][]; + readonly activeCellAndCodeEditor: [ICellViewModel, ICodeEditor] | undefined; //#endregion getLength(): number; @@ -517,6 +530,7 @@ export interface INotebookEditor { getEditorViewState(): INotebookEditorViewState; restoreListViewState(viewState: INotebookEditorViewState | undefined): void; + getBaseCellEditorOptions(language: string): IBaseCellEditorOptions; /** * Focus the active cell in notebook cell list @@ -737,7 +751,7 @@ export interface INotebookEditor { getCellIndex(cell: ICellViewModel): number | undefined; getNextVisibleCellIndex(index: number): number | undefined; getPreviousVisibleCellIndex(index: number): number | undefined; - find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup?: boolean, shouldGetSearchPreviewInfo?: boolean, ownerID?: string): Promise; + find(query: string, options: INotebookFindOptions, token: CancellationToken, skipWarmup?: boolean, shouldGetSearchPreviewInfo?: boolean, ownerID?: string): Promise; findHighlightCurrent(matchIndex: number, ownerID?: string): Promise; findUnHighlightCurrent(matchIndex: number, ownerID?: string): Promise; findStop(ownerID?: string): void; @@ -777,7 +791,6 @@ export interface INotebookEditorDelegate extends INotebookEditor { readonly creationOptions: INotebookEditorCreationOptions; readonly onDidChangeOptions: Event; readonly onDidChangeDecorations: Event; - getBaseCellEditorOptions(language: string): IBaseCellEditorOptions; createMarkupPreview(cell: ICellViewModel): Promise; unhideMarkupPreviews(cells: readonly ICellViewModel[]): Promise; hideMarkupPreviews(cells: readonly ICellViewModel[]): Promise; @@ -878,7 +891,9 @@ export function getNotebookEditorFromEditorPane(editorPane?: IEditorPane): INote const input = editorPane.input; - if (input && isCompositeNotebookEditorInput(input)) { + const isCompositeNotebook = input && isCompositeNotebookEditorInput(input); + + if (isCompositeNotebook) { return (editorPane.getControl() as { notebookEditor: INotebookEditor | undefined } | undefined)?.notebookEditor; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 473e8a11..848dea70 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -215,7 +215,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane, I // we need to hide it before getting a new widget this._widget.value?.onWillHide(); - this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group, input, undefined, this._pagePosition?.dimension, this.window); + this._widget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group.id, input, undefined, this._pagePosition?.dimension, this.window); if (this._rootElement && this._widget.value!.getDomNode()) { this._rootElement.setAttribute('aria-flowto', this._widget.value!.getDomNode().id || ''); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 2f83ab69..74d9a08c 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -74,7 +74,7 @@ import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser import { NotebookOverviewRuler } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler'; import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellEditType, CellKind, INotebookSearchOptions, RENDERER_NOT_AVAILABLE, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, INotebookFindOptions, NotebookFindScopeType, RENDERER_NOT_AVAILABLE, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -99,9 +99,10 @@ import { NotebookStickyScroll } from 'vs/workbench/contrib/notebook/browser/view import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { PreventDefaultContextMenuItemsContextKeyName } from 'vs/workbench/contrib/webview/browser/webview.contribution'; import { NotebookAccessibilityProvider } from 'vs/workbench/contrib/notebook/browser/notebookAccessibilityProvider'; +import { NotebookHorizontalTracker } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker'; +import { NotebookCellEditorPool } from 'vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool'; const $ = DOM.$; @@ -151,6 +152,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; private readonly _onDidScroll = this._register(new Emitter()); readonly onDidScroll: Event = this._onDidScroll.event; + private readonly _onDidChangeLayout = this._register(new Emitter()); + readonly onDidChangeLayout: Event = this._onDidChangeLayout.event; private readonly _onDidChangeActiveCell = this._register(new Emitter()); readonly onDidChangeActiveCell: Event = this._onDidChangeActiveCell.event; private readonly _onDidChangeFocus = this._register(new Emitter()); @@ -200,6 +203,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _dndController: CellDragAndDropController | null = null; private _listTopCellToolbar: ListTopCellToolbar | null = null; private _renderedEditors: Map = new Map(); + private _editorPool!: NotebookCellEditorPool; private _viewContext: ViewContext; private _notebookViewModel: NotebookViewModel | undefined; private readonly _localStore: DisposableStore = this._register(new DisposableStore()); @@ -260,6 +264,19 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._renderedEditors.get(focused); } + get activeCellAndCodeEditor(): [ICellViewModel, ICodeEditor] | undefined { + if (this._isDisposed) { + return; + } + + const [focused] = this._list.getFocusedElements(); + const editor = this._renderedEditors.get(focused); + if (!editor) { + return; + } + return [focused, editor]; + } + get codeEditors(): [ICellViewModel, ICodeEditor][] { return [...this._renderedEditors]; } @@ -272,6 +289,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly isEmbedded: boolean; private _readOnly: boolean; + private readonly _inRepl: boolean; public readonly scopedContextKeyService: IContextKeyService; private readonly instantiationService: IInstantiationService; @@ -302,7 +320,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD @IEditorProgressService private editorProgressService: IEditorProgressService, @INotebookLoggingService private readonly logService: INotebookLoggingService, @IKeybindingService private readonly keybindingService: IKeybindingService, - @ICodeEditorService codeEditorService: ICodeEditorService ) { super(); @@ -310,21 +327,27 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this.isEmbedded = creationOptions.isEmbedded ?? false; this._readOnly = creationOptions.isReadOnly ?? false; + this._inRepl = creationOptions.forRepl ?? false; - this._notebookOptions = creationOptions.options ?? new NotebookOptions(this.creationOptions?.codeWindow ?? mainWindow, this.configurationService, notebookExecutionStateService, codeEditorService, this._readOnly); + this._overlayContainer = document.createElement('div'); + this.scopedContextKeyService = this._register(contextKeyService.createScoped(this._overlayContainer)); + this.instantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); + + this._notebookOptions = creationOptions.options ?? + this.instantiationService.createInstance(NotebookOptions, this.creationOptions?.codeWindow ?? mainWindow, this._readOnly, undefined); this._register(this._notebookOptions); const eventDispatcher = this._register(new NotebookEventDispatcher()); this._viewContext = new ViewContext( this._notebookOptions, eventDispatcher, language => this.getBaseCellEditorOptions(language)); + this._register(this._viewContext.eventDispatcher.onDidChangeLayout(() => { + this._onDidChangeLayout.fire(); + })); this._register(this._viewContext.eventDispatcher.onDidChangeCellState(e => { this._onDidChangeCellState.fire(e); })); - this._overlayContainer = document.createElement('div'); - this.scopedContextKeyService = this._register(contextKeyService.createScoped(this._overlayContainer)); - this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); this._register(_notebookService.onDidChangeOutputRenderers(() => { this._updateOutputRenderers(); @@ -608,6 +631,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._list.scrollableElement.appendChild(this._notebookOverviewRulerContainer); this._registerNotebookOverviewRuler(); + this._register(this.instantiationService.createInstance(NotebookHorizontalTracker, this, this._list.scrollableElement)); + this._overflowContainer = document.createElement('div'); this._overflowContainer.classList.add('notebook-overflow-widget-container', 'monaco-editor'); DOM.append(parent, this._overflowContainer); @@ -894,11 +919,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _createCellList(): void { this._body.classList.add('cell-list-container'); - this._dndController = this._register(new CellDragAndDropController(this, this._body)); const getScopedContextKeyService = (container: HTMLElement) => this._list.contextKeyService.createScoped(container); + this._editorPool = this._register(this.instantiationService.createInstance(NotebookCellEditorPool, this, getScopedContextKeyService)); const renderers = [ - this.instantiationService.createInstance(CodeCellRenderer, this, this._renderedEditors, this._dndController, getScopedContextKeyService), + this.instantiationService.createInstance(CodeCellRenderer, this, this._renderedEditors, this._editorPool, this._dndController, getScopedContextKeyService), this.instantiationService.createInstance(MarkupCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService), ]; @@ -1063,27 +1088,22 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } private _registerNotebookStickyScroll() { - this._notebookStickyScroll = this._register(this.instantiationService.createInstance(NotebookStickyScroll, this._notebookStickyScrollContainer, this, this._list)); - - const localDisposableStore = this._register(new DisposableStore()); + this._notebookStickyScroll = this._register(this.instantiationService.createInstance(NotebookStickyScroll, this._notebookStickyScrollContainer, this, this._list, (sizeDelta) => { + if (this.isDisposed) { + return; + } - this._register(this._notebookStickyScroll.onDidChangeNotebookStickyScroll((sizeDelta) => { - const d = localDisposableStore.add(DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.getDomNode()), () => { - if (this.isDisposed) { - return; + if (this._dimension && this._isVisible) { + if (sizeDelta > 0) { // delta > 0 ==> sticky is growing, cell list shrinking + this.layout(this._dimension); + this.setScrollTop(this.scrollTop + sizeDelta); + } else if (sizeDelta < 0) { // delta < 0 ==> sticky is shrinking, cell list growing + this.setScrollTop(this.scrollTop + sizeDelta); + this.layout(this._dimension); } + } - if (this._dimension && this._isVisible) { - if (sizeDelta > 0) { // delta > 0 ==> sticky is growing, cell list shrinking - this.layout(this._dimension); - this.setScrollTop(this.scrollTop + sizeDelta); - } else if (sizeDelta < 0) { // delta < 0 ==> sticky is shrinking, cell list growing - this.setScrollTop(this.scrollTop + sizeDelta); - this.layout(this._dimension); - } - } - localDisposableStore.delete(d); - })); + this._onDidScroll.fire(); })); } @@ -1435,7 +1455,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks) { this._ensureWebview(this.getId(), textModel.viewType, textModel.uri); - this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._viewContext, this.getLayoutInfo(), { isReadOnly: this._readOnly }); + this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._viewContext, this.getLayoutInfo(), { isReadOnly: this._readOnly, inRepl: this._inRepl }); this._viewContext.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); this.notebookOptions.updateOptions(this._readOnly); @@ -1826,6 +1846,19 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return; } + const whenContainerStylesLoaded = this.layoutService.whenContainerStylesLoaded(DOM.getWindow(this.getDomNode())); + if (whenContainerStylesLoaded) { + // In floating windows, we need to ensure that the + // container is ready for us to compute certain + // layout related properties. + whenContainerStylesLoaded.then(() => this.layoutNotebook(dimension, shadowElement, position)); + } else { + this.layoutNotebook(dimension, shadowElement, position); + } + + } + + private layoutNotebook(dimension: DOM.Dimension, shadowElement?: HTMLElement, position?: DOM.IDomPosition) { if (shadowElement) { this.updateShadowElement(shadowElement, dimension, position); } @@ -2085,6 +2118,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._list.scrollTop; } + get scrollBottom() { + return this._list.scrollTop + this._list.getRenderHeight(); + } + getAbsoluteTopOfElement(cell: ICellViewModel) { return this._list.getCellViewScrollTop(cell); } @@ -2552,7 +2589,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return Promise.all(requests); } - async find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup: boolean = false, shouldGetSearchPreviewInfo = false, ownerID?: string): Promise { + async find(query: string, options: INotebookFindOptions, token: CancellationToken, skipWarmup: boolean = false, shouldGetSearchPreviewInfo = false, ownerID?: string): Promise { if (!this._notebookViewModel) { return []; } @@ -2563,7 +2600,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const findMatches = this._notebookViewModel.find(query, options).filter(match => match.length > 0); - if (!options.includeMarkupPreview && !options.includeOutput) { + if ((!options.includeMarkupPreview && !options.includeOutput) || options.findScope?.findScopeType === NotebookFindScopeType.Text) { this._webview?.findStop(ownerID); return findMatches; } @@ -2587,11 +2624,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return []; } - const selectedRanges = options.selectedRanges?.map(range => this._notebookViewModel?.validateRange(range)).filter(range => !!range); - const selectedIndexes = cellRangesToIndexes(selectedRanges ?? []); - const findIds: string[] = selectedIndexes.map(index => this._notebookViewModel?.viewCells[index].id ?? ''); + let findIds: string[] = []; + if (options.findScope && options.findScope.findScopeType === NotebookFindScopeType.Cells && options.findScope.selectedCellRanges) { + const selectedIndexes = cellRangesToIndexes(options.findScope.selectedCellRanges); + findIds = selectedIndexes.map(index => this._notebookViewModel?.viewCells[index].id ?? ''); + } - const webviewMatches = await this._webview.find(query, { caseSensitive: options.caseSensitive, wholeWord: options.wholeWord, includeMarkup: !!options.includeMarkupPreview, includeOutput: !!options.includeOutput, shouldGetSearchPreviewInfo, ownerID, findIds: options.searchInRanges ? findIds : [] }); + const webviewMatches = await this._webview.find(query, { caseSensitive: options.caseSensitive, wholeWord: options.wholeWord, includeMarkup: !!options.includeMarkupPreview, includeOutput: !!options.includeOutput, shouldGetSearchPreviewInfo, ownerID, findIds: findIds }); if (token.isCancellationRequested) { return []; @@ -3210,54 +3249,19 @@ export const notebookCellBorder = registerColor('notebook.cellBorderColor', { hcLight: PANEL_BORDER }, nls.localize('notebook.cellBorderColor', "The border color for notebook cells.")); -export const focusedEditorBorderColor = registerColor('notebook.focusedEditorBorder', { - light: focusBorder, - dark: focusBorder, - hcDark: focusBorder, - hcLight: focusBorder -}, nls.localize('notebook.focusedEditorBorder', "The color of the notebook cell editor border.")); - -export const cellStatusIconSuccess = registerColor('notebookStatusSuccessIcon.foreground', { - light: debugIconStartForeground, - dark: debugIconStartForeground, - hcDark: debugIconStartForeground, - hcLight: debugIconStartForeground -}, nls.localize('notebookStatusSuccessIcon.foreground', "The error icon color of notebook cells in the cell status bar.")); - -export const runningCellRulerDecorationColor = registerColor('notebookEditorOverviewRuler.runningCellForeground', { - light: debugIconStartForeground, - dark: debugIconStartForeground, - hcDark: debugIconStartForeground, - hcLight: debugIconStartForeground -}, nls.localize('notebookEditorOverviewRuler.runningCellForeground', "The color of the running cell decoration in the notebook editor overview ruler.")); - -export const cellStatusIconError = registerColor('notebookStatusErrorIcon.foreground', { - light: errorForeground, - dark: errorForeground, - hcDark: errorForeground, - hcLight: errorForeground -}, nls.localize('notebookStatusErrorIcon.foreground', "The error icon color of notebook cells in the cell status bar.")); - -export const cellStatusIconRunning = registerColor('notebookStatusRunningIcon.foreground', { - light: foreground, - dark: foreground, - hcDark: foreground, - hcLight: foreground -}, nls.localize('notebookStatusRunningIcon.foreground', "The running icon color of notebook cells in the cell status bar.")); - -export const notebookOutputContainerBorderColor = registerColor('notebook.outputContainerBorderColor', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, nls.localize('notebook.outputContainerBorderColor', "The border color of the notebook output container.")); +export const focusedEditorBorderColor = registerColor('notebook.focusedEditorBorder', focusBorder, nls.localize('notebook.focusedEditorBorder', "The color of the notebook cell editor border.")); -export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, nls.localize('notebook.outputContainerBackgroundColor', "The color of the notebook output container background.")); +export const cellStatusIconSuccess = registerColor('notebookStatusSuccessIcon.foreground', debugIconStartForeground, nls.localize('notebookStatusSuccessIcon.foreground', "The error icon color of notebook cells in the cell status bar.")); + +export const runningCellRulerDecorationColor = registerColor('notebookEditorOverviewRuler.runningCellForeground', debugIconStartForeground, nls.localize('notebookEditorOverviewRuler.runningCellForeground', "The color of the running cell decoration in the notebook editor overview ruler.")); + +export const cellStatusIconError = registerColor('notebookStatusErrorIcon.foreground', errorForeground, nls.localize('notebookStatusErrorIcon.foreground', "The error icon color of notebook cells in the cell status bar.")); + +export const cellStatusIconRunning = registerColor('notebookStatusRunningIcon.foreground', foreground, nls.localize('notebookStatusRunningIcon.foreground', "The running icon color of notebook cells in the cell status bar.")); + +export const notebookOutputContainerBorderColor = registerColor('notebook.outputContainerBorderColor', null, nls.localize('notebook.outputContainerBorderColor', "The border color of the notebook output container.")); + +export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', null, nls.localize('notebook.outputContainerBackgroundColor', "The color of the notebook output container background.")); // TODO@rebornix currently also used for toolbar border, if we keep all of this, pick a generic name export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeparator', { @@ -3267,12 +3271,7 @@ export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeparat hcLight: contrastBorder }, nls.localize('notebook.cellToolbarSeparator', "The color of the separator in the cell bottom toolbar")); -export const focusedCellBackground = registerColor('notebook.focusedCellBackground', { - dark: null, - light: null, - hcDark: null, - hcLight: null -}, nls.localize('focusedCellBackground', "The background color of a cell when the cell is focused.")); +export const focusedCellBackground = registerColor('notebook.focusedCellBackground', null, nls.localize('focusedCellBackground', "The background color of a cell when the cell is focused.")); export const selectedCellBackground = registerColor('notebook.selectedCellBackground', { dark: listInactiveSelectionBackground, @@ -3303,19 +3302,9 @@ export const inactiveSelectedCellBorder = registerColor('notebook.inactiveSelect hcLight: focusBorder }, nls.localize('notebook.inactiveSelectedCellBorder', "The color of the cell's borders when multiple cells are selected.")); -export const focusedCellBorder = registerColor('notebook.focusedCellBorder', { - dark: focusBorder, - light: focusBorder, - hcDark: focusBorder, - hcLight: focusBorder -}, nls.localize('notebook.focusedCellBorder', "The color of the cell's focus indicator borders when the cell is focused.")); +export const focusedCellBorder = registerColor('notebook.focusedCellBorder', focusBorder, nls.localize('notebook.focusedCellBorder', "The color of the cell's focus indicator borders when the cell is focused.")); -export const inactiveFocusedCellBorder = registerColor('notebook.inactiveFocusedCellBorder', { - dark: notebookCellBorder, - light: notebookCellBorder, - hcDark: notebookCellBorder, - hcLight: notebookCellBorder -}, nls.localize('notebook.inactiveFocusedCellBorder', "The color of the cell's top and bottom border when a cell is focused while the primary focus is outside of the editor.")); +export const inactiveFocusedCellBorder = registerColor('notebook.inactiveFocusedCellBorder', notebookCellBorder, nls.localize('notebook.inactiveFocusedCellBorder', "The color of the cell's top and bottom border when a cell is focused while the primary focus is outside of the editor.")); export const cellStatusBarItemHover = registerColor('notebook.cellStatusBarItemHoverBackground', { light: new Color(new RGBA(0, 0, 0, 0.08)), @@ -3324,33 +3313,13 @@ export const cellStatusBarItemHover = registerColor('notebook.cellStatusBarItemH hcLight: new Color(new RGBA(0, 0, 0, 0.08)), }, nls.localize('notebook.cellStatusBarItemHoverBackground', "The background color of notebook cell status bar items.")); -export const cellInsertionIndicator = registerColor('notebook.cellInsertionIndicator', { - light: focusBorder, - dark: focusBorder, - hcDark: focusBorder, - hcLight: focusBorder -}, nls.localize('notebook.cellInsertionIndicator', "The color of the notebook cell insertion indicator.")); - -export const listScrollbarSliderBackground = registerColor('notebookScrollbarSlider.background', { - dark: scrollbarSliderBackground, - light: scrollbarSliderBackground, - hcDark: scrollbarSliderBackground, - hcLight: scrollbarSliderBackground -}, nls.localize('notebookScrollbarSliderBackground', "Notebook scrollbar slider background color.")); - -export const listScrollbarSliderHoverBackground = registerColor('notebookScrollbarSlider.hoverBackground', { - dark: scrollbarSliderHoverBackground, - light: scrollbarSliderHoverBackground, - hcDark: scrollbarSliderHoverBackground, - hcLight: scrollbarSliderHoverBackground -}, nls.localize('notebookScrollbarSliderHoverBackground', "Notebook scrollbar slider background color when hovering.")); - -export const listScrollbarSliderActiveBackground = registerColor('notebookScrollbarSlider.activeBackground', { - dark: scrollbarSliderActiveBackground, - light: scrollbarSliderActiveBackground, - hcDark: scrollbarSliderActiveBackground, - hcLight: scrollbarSliderActiveBackground -}, nls.localize('notebookScrollbarSliderActiveBackground', "Notebook scrollbar slider background color when clicked on.")); +export const cellInsertionIndicator = registerColor('notebook.cellInsertionIndicator', focusBorder, nls.localize('notebook.cellInsertionIndicator', "The color of the notebook cell insertion indicator.")); + +export const listScrollbarSliderBackground = registerColor('notebookScrollbarSlider.background', scrollbarSliderBackground, nls.localize('notebookScrollbarSliderBackground', "Notebook scrollbar slider background color.")); + +export const listScrollbarSliderHoverBackground = registerColor('notebookScrollbarSlider.hoverBackground', scrollbarSliderHoverBackground, nls.localize('notebookScrollbarSliderHoverBackground', "Notebook scrollbar slider background color when hovering.")); + +export const listScrollbarSliderActiveBackground = registerColor('notebookScrollbarSlider.activeBackground', scrollbarSliderActiveBackground, nls.localize('notebookScrollbarSliderActiveBackground', "Notebook scrollbar slider background color when clicked on.")); export const cellSymbolHighlight = registerColor('notebook.symbolHighlightBackground', { dark: Color.fromHex('#ffffff0b'), diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts index 20ffe386..17f72439 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookIcons.ts @@ -29,6 +29,7 @@ export const collapsedIcon = registerIcon('notebook-collapsed', Codicon.chevronR export const expandedIcon = registerIcon('notebook-expanded', Codicon.chevronDown, localize('expandedIcon', 'Icon to annotate an expanded section in notebook editors.')); export const openAsTextIcon = registerIcon('notebook-open-as-text', Codicon.fileCode, localize('openAsTextIcon', 'Icon to open the notebook in a text editor.')); export const revertIcon = registerIcon('notebook-revert', Codicon.discard, localize('revertIcon', 'Icon to revert in notebook editors.')); +export const toggleWhitespace = registerIcon('notebook-diff-cell-toggle-whitespace', Codicon.whitespace, localize('toggleWhitespace', 'Icon for the toggle whitespace action in the diff editor.')); export const renderOutputIcon = registerIcon('notebook-render-output', Codicon.preview, localize('renderOutputIcon', 'Icon to render output in diff editor.')); export const mimetypeIcon = registerIcon('notebook-mimetype', Codicon.code, localize('mimetypeIcon', 'Icon for a mime type in notebook editors.')); export const copyIcon = registerIcon('notebook-copy', Codicon.copy, localize('copyIcon', 'Icon to copy content to clipboard')); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 46ce122a..c83be8c8 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -137,11 +137,11 @@ export class NotebookOptions extends Disposable { constructor( readonly targetWindow: CodeWindow, - private readonly configurationService: IConfigurationService, - private readonly notebookExecutionStateService: INotebookExecutionStateService, - private readonly codeEditorService: ICodeEditorService, private isReadonly: boolean, - private readonly overrides?: { cellToolbarInteraction: string; globalToolbar: boolean; stickyScrollEnabled: boolean; dragAndDropEnabled: boolean } + private readonly overrides: { cellToolbarInteraction: string; globalToolbar: boolean; stickyScrollEnabled: boolean; dragAndDropEnabled: boolean } | undefined, + @IConfigurationService private readonly configurationService: IConfigurationService, + @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, ) { super(); const showCellStatusBar = this.configurationService.getValue(NotebookSetting.showCellStatusBar); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts index ec20db8d..993acf3c 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { CodeWindow } from 'vs/base/browser/window'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookEditor, INotebookEditorCreationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -22,7 +21,7 @@ export interface IBorrowValue { export interface INotebookEditorService { _serviceBrand: undefined; - retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, dimension?: Dimension, codeWindow?: CodeWindow): IBorrowValue; + retrieveWidget(accessor: ServicesAccessor, groupId: number, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, dimension?: Dimension, codeWindow?: CodeWindow): IBorrowValue; retrieveExistingWidgetFromURI(resource: URI): IBorrowValue | undefined; retrieveAllExistingWidgets(): IBorrowValue[]; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index 4be07da6..fb5349bd 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -9,7 +9,7 @@ import { getDefaultNotebookCreationOptions, NotebookEditorWidget } from 'vs/work import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { isCompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { isCompositeNotebookEditorInput, isNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { INotebookEditor, INotebookEditorCreationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Emitter } from 'vs/base/common/event'; @@ -19,6 +19,8 @@ import { URI } from 'vs/base/common/uri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { InteractiveWindowOpen } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; export class NotebookEditorWidgetService implements INotebookEditorService { @@ -29,43 +31,45 @@ export class NotebookEditorWidgetService implements INotebookEditorService { private readonly _disposables = new DisposableStore(); private readonly _notebookEditors = new Map(); + private readonly groupListener = new Map(); + private readonly _onNotebookEditorAdd = new Emitter(); private readonly _onNotebookEditorsRemove = new Emitter(); readonly onDidAddNotebookEditor = this._onNotebookEditorAdd.event; readonly onDidRemoveNotebookEditor = this._onNotebookEditorsRemove.event; - private readonly _borrowableEditors = new Map>(); + private readonly _borrowableEditors = new Map>(); constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { - - const groupListener = new Map(); const onNewGroup = (group: IEditorGroup) => { const { id } = group; const listeners: IDisposable[] = []; listeners.push(group.onDidCloseEditor(e => { - const widgets = this._borrowableEditors.get(group.id); - if (!widgets) { + const widgetMap = this._borrowableEditors.get(group.id); + if (!widgetMap) { return; } const inputs = e.editor instanceof NotebookEditorInput ? [e.editor] : (isCompositeNotebookEditorInput(e.editor) ? e.editor.editorInputs : []); inputs.forEach(input => { - const value = widgets.get(input.resource); - if (!value) { + const widgets = widgetMap.get(input.resource); + const index = widgets?.findIndex(widget => widget.editorType === input.typeId); + if (!widgets || index === undefined || index === -1) { return; } + const value = widgets.splice(index, 1)[0]; value.token = undefined; this._disposeWidget(value.widget); - widgets.delete(input.resource); value.widget = (undefined); // unset the widget so that others that still hold a reference don't harm us }); })); listeners.push(group.onWillMoveEditor(e => { - if (e.editor instanceof NotebookEditorInput) { + if (isNotebookEditorInput(e.editor)) { this._allowWidgetMove(e.editor, e.groupId, e.target); } @@ -75,24 +79,26 @@ export class NotebookEditorWidgetService implements INotebookEditorService { }); } })); - groupListener.set(id, listeners); + this.groupListener.set(id, listeners); }; this._disposables.add(editorGroupService.onDidAddGroup(onNewGroup)); editorGroupService.whenReady.then(() => editorGroupService.groups.forEach(onNewGroup)); // group removed -> clean up listeners, clean up widgets this._disposables.add(editorGroupService.onDidRemoveGroup(group => { - const listeners = groupListener.get(group.id); + const listeners = this.groupListener.get(group.id); if (listeners) { listeners.forEach(listener => listener.dispose()); - groupListener.delete(group.id); + this.groupListener.delete(group.id); } const widgets = this._borrowableEditors.get(group.id); this._borrowableEditors.delete(group.id); if (widgets) { - for (const value of widgets.values()) { - value.token = undefined; - this._disposeWidget(value.widget); + for (const values of widgets.values()) { + for (const value of values) { + value.token = undefined; + this._disposeWidget(value.widget); + } } } })); @@ -115,6 +121,10 @@ export class NotebookEditorWidgetService implements INotebookEditorService { this._disposables.dispose(); this._onNotebookEditorAdd.dispose(); this._onNotebookEditorsRemove.dispose(); + this.groupListener.forEach((listeners) => { + listeners.forEach(listener => listener.dispose()); + }); + this.groupListener.clear(); } // --- group-based editor borrowing... @@ -134,18 +144,25 @@ export class NotebookEditorWidgetService implements INotebookEditorService { return; } - const targetWidget = this._borrowableEditors.get(targetID)?.get(input.resource); - if (targetWidget) { - // not needed + const target = this._borrowableEditors.get(targetID)?.get(input.resource)?.findIndex(widget => widget.editorType === input.typeId); + if (target !== undefined && target !== -1) { + // not needed, a separate widget is already there return; } - const widget = this._borrowableEditors.get(sourceID)?.get(input.resource); + const widget = this._borrowableEditors.get(sourceID)?.get(input.resource)?.find(widget => widget.editorType === input.typeId); if (!widget) { throw new Error('no widget at source group'); } + // don't allow the widget to be retrieved at its previous location any more - this._borrowableEditors.get(sourceID)?.delete(input.resource); + const sourceWidgets = this._borrowableEditors.get(sourceID)?.get(input.resource); + if (sourceWidgets) { + const indexToRemove = sourceWidgets.findIndex(widget => widget.editorType === input.typeId); + if (indexToRemove !== -1) { + sourceWidgets.splice(indexToRemove, 1); + } + } // allow the widget to be retrieved at its new location let targetMap = this._borrowableEditors.get(targetID); @@ -153,14 +170,16 @@ export class NotebookEditorWidgetService implements INotebookEditorService { targetMap = new ResourceMap(); this._borrowableEditors.set(targetID, targetMap); } - targetMap.set(input.resource, widget); + const widgetsAtTarget = targetMap.get(input.resource) ?? []; + widgetsAtTarget?.push(widget); + targetMap.set(input.resource, widgetsAtTarget); } retrieveExistingWidgetFromURI(resource: URI): IBorrowValue | undefined { for (const widgetInfo of this._borrowableEditors.values()) { - const widget = widgetInfo.get(resource); - if (widget) { - return this._createBorrowValue(widget.token!, widget); + const widgets = widgetInfo.get(resource); + if (widgets && widgets.length > 0) { + return this._createBorrowValue(widgets[0].token!, widgets[0]); } } return undefined; @@ -169,34 +188,35 @@ export class NotebookEditorWidgetService implements INotebookEditorService { retrieveAllExistingWidgets(): IBorrowValue[] { const ret: IBorrowValue[] = []; for (const widgetInfo of this._borrowableEditors.values()) { - for (const widget of widgetInfo.values()) { - ret.push(this._createBorrowValue(widget.token!, widget)); + for (const widgets of widgetInfo.values()) { + for (const widget of widgets) { + ret.push(this._createBorrowValue(widget.token!, widget)); + } } } return ret; } - retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, initialDimension?: Dimension, codeWindow?: CodeWindow): IBorrowValue { + retrieveWidget(accessor: ServicesAccessor, groupId: number, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, initialDimension?: Dimension, codeWindow?: CodeWindow): IBorrowValue { - let value = this._borrowableEditors.get(group.id)?.get(input.resource); + let value = this._borrowableEditors.get(groupId)?.get(input.resource)?.find(widget => widget.editorType === input.typeId); if (!value) { // NEW widget - const instantiationService = accessor.get(IInstantiationService); - const ctorOptions = creationOptions ?? getDefaultNotebookCreationOptions(); - const widget = instantiationService.createInstance(NotebookEditorWidget, { - ...ctorOptions, - codeWindow: codeWindow ?? ctorOptions.codeWindow, - }, initialDimension); + const editorGroupContextKeyService = accessor.get(IContextKeyService); + const editorGroupEditorProgressService = accessor.get(IEditorProgressService); + const widget = this.createWidget(editorGroupContextKeyService, editorGroupEditorProgressService, creationOptions, codeWindow, initialDimension); const token = this._tokenPool++; - value = { widget, token }; + value = { widget, editorType: input.typeId, token }; - let map = this._borrowableEditors.get(group.id); + let map = this._borrowableEditors.get(groupId); if (!map) { map = new ResourceMap(); - this._borrowableEditors.set(group.id, map); + this._borrowableEditors.set(groupId, map); } - map.set(input.resource, value); + const values = map.get(input.resource) ?? []; + values.push(value); + map.set(input.resource, values); } else { // reuse a widget which was either free'ed before or which @@ -207,6 +227,19 @@ export class NotebookEditorWidgetService implements INotebookEditorService { return this._createBorrowValue(value.token!, value); } + // protected for unit testing overrides + protected createWidget(editorGroupContextKeyService: IContextKeyService, editorGroupEditorProgressService: IEditorProgressService, creationOptions?: INotebookEditorCreationOptions, codeWindow?: CodeWindow, initialDimension?: Dimension) { + const notebookInstantiationService = this.instantiationService.createChild(new ServiceCollection( + [IContextKeyService, editorGroupContextKeyService], + [IEditorProgressService, editorGroupEditorProgressService])); + const ctorOptions = creationOptions ?? getDefaultNotebookCreationOptions(); + const widget = notebookInstantiationService.createInstance(NotebookEditorWidget, { + ...ctorOptions, + codeWindow: codeWindow ?? ctorOptions.codeWindow, + }, initialDimension); + return widget; + } + private _createBorrowValue(myToken: number, widget: { widget: NotebookEditorWidget; token: number | undefined }): IBorrowValue { return { get value() { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionServiceImpl.ts index d3666822..a0449134 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionServiceImpl.ts @@ -8,7 +8,6 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ILogService } from 'vs/platform/log/common/log'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { KernelPickerMRUStrategy } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -16,6 +15,7 @@ import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/wor import { INotebookExecutionService, ICellExecutionParticipant } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernelHistoryService, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; export class NotebookExecutionService implements INotebookExecutionService, IDisposable { @@ -27,7 +27,7 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, @INotebookKernelHistoryService private readonly _notebookKernelHistoryService: INotebookKernelHistoryService, @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService, - @ILogService private readonly _logService: ILogService, + @INotebookLoggingService private readonly _logService: INotebookLoggingService, @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, ) { } @@ -39,7 +39,7 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis return; } - this._logService.debug(`NotebookExecutionService#executeNotebookCells ${JSON.stringify(cellsArr.map(c => c.handle))}`); + this._logService.debug(`Execution`, `${JSON.stringify(cellsArr.map(c => c.handle))}`); const message = nls.localize('notebookRunTrust', "Executing a notebook cell will run code from this workspace."); const trust = await this._workspaceTrustRequestService.requestWorkspaceTrust({ message }); if (!trust) { @@ -85,15 +85,16 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis // the connecting state can change before the kernel resolves executeNotebookCellsRequest const unconfirmed = validCellExecutions.filter(exe => exe.state === NotebookCellExecutionState.Unconfirmed); if (unconfirmed.length) { - this._logService.debug(`NotebookExecutionService#executeNotebookCells completing unconfirmed executions ${JSON.stringify(unconfirmed.map(exe => exe.cellHandle))}`); + this._logService.debug(`Execution`, `Completing unconfirmed executions ${JSON.stringify(unconfirmed.map(exe => exe.cellHandle))}`); unconfirmed.forEach(exe => exe.complete({})); } + this._logService.debug(`Execution`, `Completed executions ${JSON.stringify(validCellExecutions.map(exe => exe.cellHandle))}`); } } async cancelNotebookCellHandles(notebook: INotebookTextModel, cells: Iterable): Promise { const cellsArr = Array.from(cells); - this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`); + this._logService.debug(`Execution`, `CancelNotebookCellHandles ${JSON.stringify(cellsArr)}`); const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); if (kernel) { await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts index 3e344634..0c385219 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl.ts @@ -48,7 +48,7 @@ export class NotebookKernelHistoryService extends Disposable implements INoteboo // We will suggest the only kernel const suggested = allAvailableKernels.all.length === 1 ? allAvailableKernels.all[0] : undefined; this._notebookLoggingService.debug('History', `getMatchingKernels: ${allAvailableKernels.all.length} kernels available for ${notebook.uri.path}. Selected: ${allAvailableKernels.selected?.label}. Suggested: ${suggested?.label}`); - const mostRecentKernelIds = this._mostRecentKernelsMap[notebook.viewType] ? [...this._mostRecentKernelsMap[notebook.viewType].values()] : []; + const mostRecentKernelIds = this._mostRecentKernelsMap[notebook.notebookType] ? [...this._mostRecentKernelsMap[notebook.notebookType].values()] : []; const all = mostRecentKernelIds.map(kernelId => allKernels.find(kernel => kernel.id === kernelId)).filter(kernel => !!kernel) as INotebookKernel[]; this._notebookLoggingService.debug('History', `mru: ${mostRecentKernelIds.length} kernels in history, ${all.length} registered already.`); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts index 572a833e..05174803 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts @@ -37,12 +37,12 @@ class KernelInfo { class NotebookTextModelLikeId { static str(k: INotebookTextModelLike): string { - return `${k.viewType}/${k.uri.toString()}`; + return `${k.notebookType}/${k.uri.toString()}`; } static obj(s: string): INotebookTextModelLike { const idx = s.indexOf('/'); return { - viewType: s.substring(0, idx), + notebookType: s.substring(0, idx), uri: URI.parse(s.substring(idx + 1)) }; } @@ -178,7 +178,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel private static _score(kernel: INotebookKernel, notebook: INotebookTextModelLike): number { if (kernel.viewType === '*') { return 5; - } else if (kernel.viewType === notebook.viewType) { + } else if (kernel.viewType === notebook.notebookType) { return 10; } else { return 0; @@ -343,7 +343,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel const stateChangeListener = sourceAction.onDidChangeState(() => { this._onDidChangeSourceActions.fire({ notebook: document.uri, - viewType: document.viewType, + viewType: document.notebookType, }); }); sourceActions.push([sourceAction, stateChangeListener]); @@ -351,7 +351,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel }); info.actions = sourceActions; this._kernelSources.set(id, info); - this._onDidChangeSourceActions.fire({ notebook: document.uri, viewType: document.viewType }); + this._onDidChangeSourceActions.fire({ notebook: document.uri, viewType: document.notebookType }); }; this._kernelSourceActionsUpdates.get(id)?.dispose(); @@ -382,7 +382,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel } getKernelDetectionTasks(notebook: INotebookTextModelLike): INotebookKernelDetectionTask[] { - return this._kernelDetectionTasks.get(notebook.viewType) ?? []; + return this._kernelDetectionTasks.get(notebook.notebookType) ?? []; } registerKernelSourceActionProvider(viewType: string, provider: IKernelSourceActionProvider): IDisposable { @@ -411,7 +411,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel * Get kernel source actions from providers */ getKernelSourceActions2(notebook: INotebookTextModelLike): Promise { - const viewType = notebook.viewType; + const viewType = notebook.notebookType; const providers = this._kernelSourceActionProviders.get(viewType) ?? []; const promises = providers.map(provider => provider.provideKernelSourceActions()); return Promise.all(promises).then(actions => { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts index 9ca31a78..c2085d61 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts @@ -20,7 +20,7 @@ export class NotebookLoggingService extends Disposable implements INotebookLoggi @ILoggerService loggerService: ILoggerService, ) { super(); - this._logger = this._register(loggerService.createLogger(logChannelId, { name: nls.localize('renderChannelName', "Notebook rendering") })); + this._logger = this._register(loggerService.createLogger(logChannelId, { name: nls.localize('renderChannelName', "Notebook") })); } debug(category: string, output: string): void { @@ -30,5 +30,13 @@ export class NotebookLoggingService extends Disposable implements INotebookLoggi info(category: string, output: string): void { this._logger.info(`[${category}] ${output}`); } + + warn(category: string, output: string): void { + this._logger.warn(`[${category}] ${output}`); + } + + error(category: string, output: string): void { + this._logger.error(`[${category}] ${output}`); + } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index 6a0fd291..ba697031 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -28,7 +28,7 @@ import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/no import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/common/notebookDiffEditorInput'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions, NotebookExtensionDescription, INotebookStaticPreloadInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, NotebookExtensionDescription, INotebookStaticPreloadInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { NotebookOutputRendererInfo, NotebookStaticPreloadInfo as NotebookStaticPreloadInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; @@ -41,7 +41,10 @@ import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensio import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { INotebookDocument, INotebookDocumentService } from 'vs/workbench/services/notebook/common/notebookDocumentService'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import type { EditorInputWithOptions, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; +import type { EditorInputWithOptions, IResourceDiffEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; +import { streamToBuffer, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; +import type { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { NotebookMultiDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/diff/notebookMultiDiffEditorInput'; export class NotebookProviderInfoStore extends Disposable { @@ -132,7 +135,6 @@ export class NotebookProviderInfoStore extends Disposable { selectors: notebookContribution.selector || [], priority: this._convertPriority(notebookContribution.priority), providerDisplayName: extension.description.displayName ?? extension.description.identifier.value, - exclusive: false })); } } @@ -171,7 +173,7 @@ export class NotebookProviderInfoStore extends Disposable { id: notebookProviderInfo.id, label: notebookProviderInfo.displayName, detail: notebookProviderInfo.providerDisplayName, - priority: notebookProviderInfo.exclusive ? RegisteredEditorPriority.exclusive : notebookProviderInfo.priority, + priority: notebookProviderInfo.priority, }; const notebookEditorOptions = { canHandleDiff: () => !!this._configurationService.getValue(NotebookSetting.textDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized(), @@ -197,7 +199,7 @@ export class NotebookProviderInfoStore extends Disposable { cellOptions = (options as INotebookEditorOptions | undefined)?.cellOptions; } - const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions; + const notebookOptions: INotebookEditorOptions = { ...options, cellOptions, viewState: undefined }; const editor = NotebookEditorInput.getOrCreate(this._instantiationService, notebookUri, preferredResource, notebookProviderInfo.id); return { editor, options: notebookOptions }; }; @@ -213,7 +215,12 @@ export class NotebookProviderInfoStore extends Disposable { return { editor: NotebookEditorInput.getOrCreate(this._instantiationService, ref.object.resource, undefined, notebookProviderInfo.id), options }; }; - const notebookDiffEditorInputFactory: DiffEditorInputFactoryFunction = ({ modified, original, label, description }) => { + const notebookDiffEditorInputFactory: DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEditorInput, group: IEditorGroup) => { + const { modified, original, label, description } = diffEditorInput; + + if (this._configurationService.getValue('notebook.experimental.enableNewDiffEditor')) { + return { editor: NotebookMultiDiffEditorInput.create(this._instantiationService, modified.resource!, label, description, original.resource!, notebookProviderInfo.id) }; + } return { editor: NotebookDiffEditorInput.create(this._instantiationService, modified.resource!, label, description, original.resource!, notebookProviderInfo.id) }; }; const mergeEditorInputFactory: MergeEditorInputFactoryFunction = (mergeEditor: IResourceMergeEditorInput): EditorInputWithOptions => { @@ -639,8 +646,7 @@ export class NotebookService extends Disposable implements INotebookService { id: viewType, displayName: data.displayName, providerDisplayName: data.providerDisplayName, - exclusive: data.exclusive, - priority: RegisteredEditorPriority.default, + priority: data.priority || RegisteredEditorPriority.default, selectors: [] }); @@ -697,6 +703,14 @@ export class NotebookService extends Disposable implements INotebookService { return result; } + tryGetDataProviderSync(viewType: string): SimpleNotebookProviderInfo | undefined { + const selected = this.notebookProviderInfoStore.get(viewType); + if (!selected) { + return undefined; + } + return this._notebookProviders.get(selected.id); + } + private _persistMementos(): void { this._memento.saveMemento(); @@ -737,17 +751,28 @@ export class NotebookService extends Disposable implements INotebookService { // --- notebook documents: create, destory, retrieve, enumerate - createNotebookTextModel(viewType: string, uri: URI, data: NotebookData, transientOptions: TransientOptions): NotebookTextModel { + async createNotebookTextModel(viewType: string, uri: URI, stream?: VSBufferReadableStream): Promise { if (this._models.has(uri)) { throw new Error(`notebook for ${uri} already exists`); } - const notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, uri, data.cells, data.metadata, transientOptions); + + const info = await this.withNotebookDataProvider(viewType); + if (!(info instanceof SimpleNotebookProviderInfo)) { + throw new Error('CANNOT open file notebook with this provider'); + } + + + const bytes = stream ? await streamToBuffer(stream) : VSBuffer.fromByteArray([]); + const data = await info.serializer.dataToNotebook(bytes); + + + const notebookModel = this._instantiationService.createInstance(NotebookTextModel, info.viewType, uri, data.cells, data.metadata, info.serializer.options); const modelData = new ModelData(notebookModel, this._onWillDisposeDocument.bind(this)); this._models.set(uri, modelData); this._notebookDocumentService.addNotebookDocument(modelData); this._onWillAddNotebookDocument.fire(notebookModel); this._onDidAddNotebookDocument.fire(notebookModel); - this._postDocumentOpenActivation(viewType); + this._postDocumentOpenActivation(info.viewType); return notebookModel; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts index 65980c57..f57cd98d 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts @@ -5,13 +5,12 @@ import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; -import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; +import { IWorkerClient, Proxied } from 'vs/base/common/worker/simpleWorker'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { IMainCellDto, INotebookDiffResult, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookEditorSimpleWorker } from 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker'; -import { INotebookWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerHost'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; export class NotebookEditorWorkerServiceImpl extends Disposable implements INotebookEditorWorkerService { @@ -58,23 +57,18 @@ class WorkerManager extends Disposable { withWorker(): Promise { // this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new NotebookWorkerClient(this._notebookService, 'notebookEditorWorkerService'); + this._editorWorkerClient = new NotebookWorkerClient(this._notebookService); } return Promise.resolve(this._editorWorkerClient); } } -interface IWorkerClient { - getProxyObject(): Promise; - dispose(): void; -} - class NotebookEditorModelManager extends Disposable { private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null); private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null); constructor( - private readonly _proxy: NotebookEditorSimpleWorker, + private readonly _proxy: Proxied, private readonly _notebookService: INotebookService ) { super(); @@ -101,7 +95,7 @@ class NotebookEditorModelManager extends Disposable { const modelUrl = resource.toString(); - this._proxy.acceptNewModel( + this._proxy.$acceptNewModel( model.uri.toString(), { cells: model.cells.map(cell => ({ @@ -161,7 +155,7 @@ class NotebookEditorModelManager extends Disposable { return data; }); - this._proxy.acceptModelChanged(modelUrl.toString(), { + this._proxy.$acceptModelChanged(modelUrl.toString(), { rawEvents: dto, versionId: event.versionId }); @@ -171,7 +165,7 @@ class NotebookEditorModelManager extends Disposable { this._stopModelSync(modelUrl); })); toDispose.add(toDisposable(() => { - this._proxy.acceptRemovedModel(modelUrl); + this._proxy.$acceptRemovedModel(modelUrl); })); this._syncedModels[modelUrl] = toDispose; @@ -185,72 +179,47 @@ class NotebookEditorModelManager extends Disposable { } } -class NotebookWorkerHost implements INotebookWorkerHost { - - private readonly _workerClient: NotebookWorkerClient; - - constructor(workerClient: NotebookWorkerClient) { - this._workerClient = workerClient; - } - - // foreign host request - public fhr(method: string, args: any[]): Promise { - return this._workerClient.fhr(method, args); - } -} - class NotebookWorkerClient extends Disposable { private _worker: IWorkerClient | null; - private readonly _workerFactory: DefaultWorkerFactory; private _modelManager: NotebookEditorModelManager | null; - constructor(private readonly _notebookService: INotebookService, label: string) { + constructor(private readonly _notebookService: INotebookService) { super(); - this._workerFactory = new DefaultWorkerFactory(label); this._worker = null; this._modelManager = null; } - // foreign host request - public fhr(method: string, args: any[]): Promise { - throw new Error(`Not implemented!`); - } - computeDiff(original: URI, modified: URI) { - return this._withSyncedResources([original, modified]).then(proxy => { - return proxy.computeDiff(original.toString(), modified.toString()); - }); + const proxy = this._ensureSyncedResources([original, modified]); + return proxy.$computeDiff(original.toString(), modified.toString()); } canPromptRecommendation(modelUri: URI) { - return this._withSyncedResources([modelUri]).then(proxy => { - return proxy.canPromptRecommendation(modelUri.toString()); - }); + const proxy = this._ensureSyncedResources([modelUri]); + return proxy.$canPromptRecommendation(modelUri.toString()); } - private _getOrCreateModelManager(proxy: NotebookEditorSimpleWorker): NotebookEditorModelManager { + private _getOrCreateModelManager(proxy: Proxied): NotebookEditorModelManager { if (!this._modelManager) { this._modelManager = this._register(new NotebookEditorModelManager(proxy, this._notebookService)); } return this._modelManager; } - protected _withSyncedResources(resources: URI[]): Promise { - return this._getProxy().then((proxy) => { - this._getOrCreateModelManager(proxy).ensureSyncedResources(resources); - return proxy; - }); + protected _ensureSyncedResources(resources: URI[]): Proxied { + const proxy = this._getOrCreateWorker().proxy; + this._getOrCreateModelManager(proxy).ensureSyncedResources(resources); + return proxy; } private _getOrCreateWorker(): IWorkerClient { if (!this._worker) { try { - this._worker = this._register(new SimpleWorkerClient( - this._workerFactory, + this._worker = this._register(createWebWorker( 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker', - new NotebookWorkerHost(this) + 'NotebookEditorWorker' )); } catch (err) { // logOnceWebWorkerWarning(err); @@ -260,15 +229,4 @@ class NotebookWorkerClient extends Disposable { } return this._worker; } - - protected _getProxy(): Promise { - return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => { - // logOnceWebWorkerWarning(err); - // this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); - // return this._getOrCreateWorker().getProxyObject(); - throw (err); - }); - } - - } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts index 546637fa..1ae8bf98 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts @@ -16,7 +16,7 @@ import { ICellExecutionStateChangedEvent } from 'vs/workbench/contrib/notebook/c */ export abstract class CellContentPart extends Disposable { protected currentCell: ICellViewModel | undefined; - protected readonly cellDisposables = new DisposableStore(); + protected readonly cellDisposables = this._register(new DisposableStore()); constructor() { super(); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts index 854358af..bfb757be 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -17,7 +17,7 @@ import { MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/ac import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; export class CodiconActionViewItem extends MenuEntryActionViewItem { @@ -49,7 +49,7 @@ export class ActionViewWithLabel extends MenuEntryActionViewItem { } export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { private _actionLabel?: HTMLAnchorElement; - private _hover?: IUpdatableHover; + private _hover?: IManagedHover; private _primaryAction: IAction | undefined; constructor( @@ -73,7 +73,7 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { this._actionLabel = document.createElement('a'); container.appendChild(this._actionLabel); - this._hover = this._register(this._hoverService.setupUpdatableHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('element'), this._actionLabel, '')); + this._hover = this._register(this._hoverService.setupManagedHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('element'), this._actionLabel, '')); this.updateLabel(); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts index b8ccbf07..6363dc3a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce } from 'vs/base/common/arrays'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import * as languages from 'vs/editor/common/languages'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -15,20 +15,16 @@ import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentSe import { CommentThreadWidget } from 'vs/workbench/contrib/comments/browser/commentThreadWidget'; import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; -import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; export class CellComments extends CellContentPart { - private _initialized: boolean = false; - private _commentThreadWidget: CommentThreadWidget | null = null; - private currentElement: CodeCellViewModel | undefined; - private readonly commentTheadDisposables = this._register(new DisposableStore()); + private readonly _commentThreadWidget: MutableDisposable>; + private currentElement: ICellViewModel | undefined; + private readonly _commentThreadDisposables = this._register(new DisposableStore()); constructor( private readonly notebookEditor: INotebookEditorDelegate, private readonly container: HTMLElement, - @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService private readonly themeService: IThemeService, @ICommentService private readonly commentService: ICommentService, @@ -38,6 +34,8 @@ export class CellComments extends CellContentPart { super(); this.container.classList.add('review-widget'); + this._register(this._commentThreadWidget = new MutableDisposable>()); + this._register(this.themeService.onDidColorThemeChange(this._applyTheme, this)); // TODO @rebornix onDidChangeLayout (font change) // this._register(this.notebookEditor.onDidchangeLa) @@ -45,22 +43,17 @@ export class CellComments extends CellContentPart { } private async initialize(element: ICellViewModel) { - if (this._initialized) { + if (this.currentElement === element) { return; } - this._initialized = true; - const info = await this._getCommentThreadForCell(element); - - if (info) { - await this._createCommentTheadWidget(info.owner, info.thread); - } + this.currentElement = element; + await this._updateThread(); } private async _createCommentTheadWidget(owner: string, commentThread: languages.CommentThread) { - this._commentThreadWidget?.dispose(); - this.commentTheadDisposables.clear(); - this._commentThreadWidget = this.instantiationService.createInstance( + this._commentThreadDisposables.clear(); + this._commentThreadWidget.value = this.instantiationService.createInstance( CommentThreadWidget, this.container, this.notebookEditor, @@ -84,44 +77,43 @@ export class CellComments extends CellContentPart { const layoutInfo = this.notebookEditor.getLayoutInfo(); - await this._commentThreadWidget.display(layoutInfo.fontInfo.lineHeight, true); + await this._commentThreadWidget.value.display(layoutInfo.fontInfo.lineHeight, true); this._applyTheme(); - this.commentTheadDisposables.add(this._commentThreadWidget.onDidResize(() => { - if (this.currentElement?.cellKind === CellKind.Code && this._commentThreadWidget) { - this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget.getDimensions().height); + this._commentThreadDisposables.add(this._commentThreadWidget.value.onDidResize(() => { + if (this.currentElement && this._commentThreadWidget.value) { + this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget.value.getDimensions().height); } })); } private _bindListeners() { - this.cellDisposables.add(this.commentService.onDidUpdateCommentThreads(async () => { - if (this.currentElement) { - const info = await this._getCommentThreadForCell(this.currentElement); - if (!this._commentThreadWidget && info) { - await this._createCommentTheadWidget(info.owner, info.thread); - const layoutInfo = (this.currentElement as CodeCellViewModel).layoutInfo; - this.container.style.top = `${layoutInfo.outputContainerOffset + layoutInfo.outputTotalHeight}px`; - this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget!.getDimensions().height); - return; - } + this.cellDisposables.add(this.commentService.onDidUpdateCommentThreads(async () => this._updateThread())); + } - if (this._commentThreadWidget) { - if (!info) { - this._commentThreadWidget.dispose(); - this.currentElement.commentHeight = 0; - return; - } - if (this._commentThreadWidget.commentThread === info.thread) { - this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget.getDimensions().height); - return; - } - - await this._commentThreadWidget.updateCommentThread(info.thread); - this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget.getDimensions().height); - } + private async _updateThread() { + if (!this.currentElement) { + return; + } + const info = await this._getCommentThreadForCell(this.currentElement); + if (!this._commentThreadWidget.value && info) { + await this._createCommentTheadWidget(info.owner, info.thread); + this.container.style.top = `${this.currentElement.layoutInfo.commentOffset}px`; + this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget.value!.getDimensions().height); + return; + } + + if (this._commentThreadWidget.value) { + if (!info) { + this._commentThreadDisposables.clear(); + this._commentThreadWidget.value = undefined; + this.currentElement.commentHeight = 0; + return; } - })); + + await this._commentThreadWidget.value.updateCommentThread(info.thread); + this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget.value.getDimensions().height); + } } private _calculateCommentThreadHeight(bodyHeight: number) { @@ -140,8 +132,11 @@ export class CellComments extends CellContentPart { private async _getCommentThreadForCell(element: ICellViewModel): Promise<{ thread: languages.CommentThread; owner: string } | null> { if (this.notebookEditor.hasModel()) { const commentInfos = coalesce(await this.commentService.getNotebookComments(element.uri)); - if (commentInfos.length && commentInfos[0].threads.length) { - return { owner: commentInfos[0].uniqueOwner, thread: commentInfos[0].threads[0] }; + for (const commentInfo of commentInfos) { + for (const thread of commentInfo.threads) { + // For now, only one thread per cell is supported. + return { owner: commentInfo.uniqueOwner, thread }; + } } } @@ -151,28 +146,23 @@ export class CellComments extends CellContentPart { private _applyTheme() { const theme = this.themeService.getColorTheme(); const fontInfo = this.notebookEditor.getLayoutInfo().fontInfo; - this._commentThreadWidget?.applyTheme(theme, fontInfo); + this._commentThreadWidget.value?.applyTheme(theme, fontInfo); } override didRenderCell(element: ICellViewModel): void { - if (element.cellKind === CellKind.Code) { - this.currentElement = element as CodeCellViewModel; - this.initialize(element); - this._bindListeners(); - } - + this.initialize(element); + this._bindListeners(); } override prepareLayout(): void { - if (this.currentElement?.cellKind === CellKind.Code && this._commentThreadWidget) { - this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget.getDimensions().height); + if (this.currentElement && this._commentThreadWidget.value) { + this.currentElement.commentHeight = this._calculateCommentThreadHeight(this._commentThreadWidget.value.getDimensions().height); } } override updateInternalLayoutNow(element: ICellViewModel): void { - if (this.currentElement?.cellKind === CellKind.Code && this._commentThreadWidget) { - const layoutInfo = (element as CodeCellViewModel).layoutInfo; - this.container.style.top = `${layoutInfo.outputContainerOffset + layoutInfo.outputTotalHeight}px`; + if (this.currentElement && this._commentThreadWidget.value) { + this.container.style.top = `${element.layoutInfo.commentOffset}px`; } } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts index 06444079..407fc5ea 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts @@ -335,7 +335,7 @@ export class CellDragAndDropController extends Disposable { const dragImage = dragImageProvider(); cellRoot.parentElement!.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, 0, 0); - setTimeout(() => cellRoot.parentElement!.removeChild(dragImage), 0); // Comment this out to debug drag image layout + setTimeout(() => dragImage.remove(), 0); // Comment this out to debug drag image layout }; for (const dragHandle of dragHandles) { templateData.templateDisposables.add(DOM.addDisposableListener(dragHandle, DOM.EventType.DRAG_START, onDragStart)); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts index d5c27d01..c69d62f3 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts @@ -6,9 +6,11 @@ import * as DOM from 'vs/base/browser/dom'; import { disposableTimeout } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { clamp } from 'vs/base/common/numbers'; import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -39,6 +41,10 @@ export class CellExecutionPart extends CellContentPart { this.updateExecutionOrder(this.currentCell.internalMetadata); } })); + + this._register(this._notebookEditor.onDidScroll(() => { + this._updatePosition(); + })); } override didRenderCell(element: ICellViewModel): void { @@ -74,12 +80,38 @@ export class CellExecutionPart extends CellContentPart { } override updateInternalLayoutNow(element: ICellViewModel): void { - if (element.isInputCollapsed) { - DOM.hide(this._executionOrderLabel); - } else { - DOM.show(this._executionOrderLabel); - const top = element.layoutInfo.editorHeight - 22 + element.layoutInfo.statusBarHeight; - this._executionOrderLabel.style.top = `${top}px`; + this._updatePosition(); + } + + private _updatePosition() { + if (this.currentCell) { + if (this.currentCell.isInputCollapsed) { + DOM.hide(this._executionOrderLabel); + } else { + DOM.show(this._executionOrderLabel); + let top = this.currentCell.layoutInfo.editorHeight - 22 + this.currentCell.layoutInfo.statusBarHeight; + + if (this.currentCell instanceof CodeCellViewModel) { + const elementTop = this._notebookEditor.getAbsoluteTopOfElement(this.currentCell); + const editorBottom = elementTop + this.currentCell.layoutInfo.outputContainerOffset; + // another approach to avoid the flicker caused by sticky scroll is manually calculate the scrollBottom: + // const scrollBottom = this._notebookEditor.scrollTop + this._notebookEditor.getLayoutInfo().height - 26 - this._notebookEditor.getLayoutInfo().stickyHeight; + const scrollBottom = this._notebookEditor.scrollBottom; + + const lineHeight = 22; + if (scrollBottom <= editorBottom) { + const offset = editorBottom - scrollBottom; + top -= offset; + top = clamp( + top, + lineHeight + 12, // line height + padding for single line + this.currentCell.layoutInfo.editorHeight - lineHeight + this.currentCell.layoutInfo.statusBarHeight + ); + } + } + + this._executionOrderLabel.style.top = `${top}px`; + } } } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts index 6cb5b68d..44465c84 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts @@ -33,8 +33,9 @@ import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKe import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { COPY_OUTPUT_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/controller/cellOutputActions'; -import { CLEAR_CELL_OUTPUTS_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; import { TEXT_BASED_MIMETYPES } from 'vs/workbench/contrib/notebook/browser/contrib/clipboard/cellOutputClipboard'; +import { autorun, observableValue } from 'vs/base/common/observable'; +import { NOTEBOOK_CELL_HAS_HIDDEN_OUTPUTS, NOTEBOOK_CELL_IS_FIRST_OUTPUT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; interface IMimeTypeRenderer extends IQuickPickItem { index: number; @@ -60,13 +61,14 @@ interface IRenderResult { // | | #cell-output-toolbar // | | #output-element class CellOutputElement extends Disposable { - private readonly _renderDisposableStore = this._register(new DisposableStore()); + private readonly toolbarDisposables = this._register(new DisposableStore()); innerContainer?: HTMLElement; renderedOutputContainer!: HTMLElement; renderResult?: IInsetRenderOutput; private readonly contextKeyService: IContextKeyService; + private toolbarAttached = false; constructor( private notebookEditor: INotebookEditorDelegate, @@ -95,7 +97,7 @@ class CellOutputElement extends Disposable { } detach() { - this.renderedOutputContainer?.parentElement?.removeChild(this.renderedOutputContainer); + this.renderedOutputContainer?.remove(); let count = 0; if (this.innerContainer) { @@ -110,7 +112,7 @@ class CellOutputElement extends Disposable { } if (count === 0) { - this.innerContainer.parentElement?.removeChild(this.innerContainer); + this.innerContainer.remove(); } } @@ -151,10 +153,10 @@ class CellOutputElement extends Disposable { } else { // Another mimetype or renderer is picked, we need to clear the current output and re-render const nextElement = this.innerContainer.nextElementSibling; - this._renderDisposableStore.clear(); + this.toolbarDisposables.clear(); const element = this.innerContainer; if (element) { - element.parentElement?.removeChild(element); + element.remove(); this.notebookEditor.removeInset(this.output); } @@ -207,7 +209,20 @@ class CellOutputElement extends Disposable { } const innerContainer = this._generateInnerOutputContainer(previousSibling, selectedPresentation); - this._attachToolbar(innerContainer, notebookTextModel, this.notebookEditor.activeKernel, index, mimeTypes); + if (index === 0 || this.output.visible.get()) { + this._attachToolbar(innerContainer, notebookTextModel, this.notebookEditor.activeKernel, index, mimeTypes); + } else { + this._register(autorun((reader) => { + const visible = reader.readObservable(this.output.visible); + if (visible && !this.toolbarAttached) { + this._attachToolbar(innerContainer, notebookTextModel, this.notebookEditor.activeKernel, index, mimeTypes); + } else if (!visible) { + this.toolbarDisposables.clear(); + } + this.cellOutputContainer.checkForHiddenOutputs(); + })); + this.cellOutputContainer.hasHiddenOutputs.set(true, undefined); + } this.renderedOutputContainer = DOM.append(innerContainer, DOM.$('.rendered-output')); @@ -291,14 +306,12 @@ class CellOutputElement extends Disposable { return; } - const useConsolidatedButton = this.notebookEditor.notebookOptions.getDisplayOptions().consolidatedOutputButton; - outputItemDiv.style.position = 'relative'; const mimeTypePicker = DOM.$('.cell-output-toolbar'); outputItemDiv.appendChild(mimeTypePicker); - const toolbar = this._renderDisposableStore.add(this.instantiationService.createInstance(WorkbenchToolBar, mimeTypePicker, { + const toolbar = this.toolbarDisposables.add(this.instantiationService.createInstance(WorkbenchToolBar, mimeTypePicker, { renderDropdownAsChildElement: false })); toolbar.context = { @@ -310,20 +323,22 @@ class CellOutputElement extends Disposable { }; // TODO: This could probably be a real registered action, but it has to talk to this output element - const pickAction = new Action('notebook.output.pickMimetype', nls.localize('pickMimeType', "Change Presentation"), ThemeIcon.asClassName(mimetypeIcon), undefined, - async _context => this._pickActiveMimeTypeRenderer(outputItemDiv, notebookTextModel, kernel, this.output)); + const pickAction = this.toolbarDisposables.add(new Action('notebook.output.pickMimetype', nls.localize('pickMimeType', "Change Presentation"), ThemeIcon.asClassName(mimetypeIcon), undefined, + async _context => this._pickActiveMimeTypeRenderer(outputItemDiv, notebookTextModel, kernel, this.output))); + + const menuContextKeyService = this.toolbarDisposables.add(this.contextKeyService.createScoped(outputItemDiv)); + const hasHiddenOutputs = NOTEBOOK_CELL_HAS_HIDDEN_OUTPUTS.bindTo(menuContextKeyService); + const isFirstCellOutput = NOTEBOOK_CELL_IS_FIRST_OUTPUT.bindTo(menuContextKeyService); + isFirstCellOutput.set(index === 0); + this.toolbarDisposables.add(autorun((reader) => { hasHiddenOutputs.set(reader.readObservable(this.cellOutputContainer.hasHiddenOutputs)); })); + const menu = this.toolbarDisposables.add(this.menuService.createMenu(MenuId.NotebookOutputToolbar, menuContextKeyService)); - const menu = this._renderDisposableStore.add(this.menuService.createMenu(MenuId.NotebookOutputToolbar, this.contextKeyService)); const updateMenuToolbar = () => { const primary: IAction[] = []; let secondary: IAction[] = []; const result = { primary, secondary }; - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result, () => false); - if (index > 0 || !useConsolidatedButton) { - // clear outputs should only appear in the first output item's menu - secondary = secondary.filter((action) => action.id !== CLEAR_CELL_OUTPUTS_COMMAND_ID); - } + createAndFillInActionBarActions(menu!, { shouldForwardArgs: true }, result, () => false); if (!isCopyEnabled) { secondary = secondary.filter((action) => action.id !== COPY_OUTPUT_COMMAND_ID); } @@ -334,8 +349,7 @@ class CellOutputElement extends Disposable { toolbar.setActions([], secondary); }; updateMenuToolbar(); - this._renderDisposableStore.add(menu.onDidChange(updateMenuToolbar)); - + this.toolbarDisposables.add(menu.onDidChange(updateMenuToolbar)); } private async _pickActiveMimeTypeRenderer(outputItemDiv: HTMLElement, notebookTextModel: NotebookTextModel, kernel: INotebookKernel | undefined, viewModel: ICellOutputViewModel) { @@ -367,7 +381,8 @@ class CellOutputElement extends Disposable { }); } - const picker = this.quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const picker = disposables.add(this.quickInputService.createQuickPick({ useSeparators: true })); picker.items = [ ...items, { type: 'separator' }, @@ -379,10 +394,10 @@ class CellOutputElement extends Disposable { : nls.localize('promptChooseMimeType.placeHolder', "Select mimetype to render for current output"); const pick = await new Promise(resolve => { - picker.onDidAccept(() => { + disposables.add(picker.onDidAccept(() => { resolve(picker.selectedItems.length === 1 ? (picker.selectedItems[0] as IMimeTypeRenderer) : undefined); - picker.dispose(); - }); + disposables.dispose(); + })); picker.show(); }); @@ -397,10 +412,10 @@ class CellOutputElement extends Disposable { // user chooses another mimetype const nextElement = outputItemDiv.nextElementSibling; - this._renderDisposableStore.clear(); + this.toolbarDisposables.clear(); const element = this.innerContainer; if (element) { - element.parentElement?.removeChild(element); + element.remove(); this.notebookEditor.removeInset(viewModel); } @@ -479,6 +494,15 @@ export class CellOutputContainer extends CellContentPart { private _outputEntries: OutputEntryViewHandler[] = []; private _hasStaleOutputs: boolean = false; + hasHiddenOutputs = observableValue('hasHiddenOutputs', false); + checkForHiddenOutputs() { + if (this._outputEntries.find(entry => { return entry.model.visible; })) { + this.hasHiddenOutputs.set(true, undefined); + } else { + this.hasHiddenOutputs.set(false, undefined); + } + } + get renderedOutputEntries() { return this._outputEntries; } @@ -807,5 +831,3 @@ const JUPYTER_RENDERER_MIMETYPES = [ 'application/vnd.jupyter.widget-view+json', 'application/vnd.code.notebook.error' ]; - - diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts index 98b202b5..a4514e41 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts @@ -31,7 +31,7 @@ import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/ import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import type { IUpdatableHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; const $ = DOM.$; @@ -123,12 +123,15 @@ export class CellEditorStatusBar extends CellContentPart { override didRenderCell(element: ICellViewModel): void { - this.updateContext({ - ui: true, - cell: element, - notebookEditor: this._notebookEditor, - $mid: MarshalledId.NotebookCellActionContext - }); + if (this._notebookEditor.hasModel()) { + const context: (INotebookCellActionContext & { $mid: number }) = { + ui: true, + cell: element, + notebookEditor: this._notebookEditor, + $mid: MarshalledId.NotebookCellActionContext + }; + this.updateContext(context); + } if (this._editor) { // Focus Mode @@ -235,7 +238,7 @@ export class CellEditorStatusBar extends CellContentPart { if (renderedItems.length > newItems.length) { const deleted = renderedItems.splice(newItems.length, renderedItems.length - newItems.length); for (const deletedItem of deleted) { - container.removeChild(deletedItem.container); + deletedItem.container.remove(); deletedItem.dispose(); } } @@ -327,8 +330,8 @@ class CellStatusBarItem extends Disposable { this.container.setAttribute('role', role || ''); if (item.tooltip) { - const hoverContent = typeof item.tooltip === 'string' ? item.tooltip : { markdown: item.tooltip } as IUpdatableHoverTooltipMarkdownString; - this._itemDisposables.add(this._hoverService.setupUpdatableHover(this._hoverDelegate, this.container, hoverContent)); + const hoverContent = typeof item.tooltip === 'string' ? item.tooltip : { markdown: item.tooltip, markdownNotSupportedFallback: undefined } satisfies IManagedHoverTooltipMarkdownString; + this._itemDisposables.add(this._hoverService.setupManagedHover(this._hoverDelegate, this.container, hoverContent)); } this.container.classList.toggle('cell-status-item-has-command', !!item.command); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index 733ac19d..36911bfb 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -80,13 +80,15 @@ export class BetweenCellToolbar extends CellOverlayPart { override didRenderCell(element: ICellViewModel): void { const betweenCellToolbar = this._initialize(); - betweenCellToolbar.context = { - ui: true, - cell: element, - notebookEditor: this._notebookEditor, - source: 'insertToolbar', - $mid: MarshalledId.NotebookCellActionContext - }; + if (this._notebookEditor.hasModel()) { + betweenCellToolbar.context = { + ui: true, + cell: element, + notebookEditor: this._notebookEditor, + source: 'insertToolbar', + $mid: MarshalledId.NotebookCellActionContext + } satisfies (INotebookCellActionContext & { source?: string; $mid: number }); + } this.updateInternalLayoutNow(element); } @@ -202,13 +204,17 @@ export class CellTitleToolbarPart extends CellOverlayPart { const view = this._initialize(model, element); this.cellDisposables.add(registerCellToolbarStickyScroll(this._notebookEditor, element, this.toolbarContainer, { extraOffset: 4, min: -14 })); - this.updateContext(view, { - ui: true, - cell: element, - notebookEditor: this._notebookEditor, - source: 'cellToolbar', - $mid: MarshalledId.NotebookCellActionContext - }); + if (this._notebookEditor.hasModel()) { + const toolbarContext: INotebookCellActionContext & { source?: string; $mid: number } = { + ui: true, + cell: element, + notebookEditor: this._notebookEditor, + source: 'cellToolbar', + $mid: MarshalledId.NotebookCellActionContext + }; + + this.updateContext(view, toolbarContext); + } } private updateContext(view: CellTitleToolbarView, toolbarContext: INotebookCellActionContext) { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index 45ccce37..00409da9 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -10,6 +10,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { clamp } from 'vs/base/common/numbers'; import * as strings from 'vs/base/common/strings'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; @@ -31,6 +32,7 @@ import { CodeCellViewModel, outputDisplayLimit } from 'vs/workbench/contrib/note import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { WordHighlighterContribution } from 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; +import { NotebookCellEditorPool } from 'vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool'; export class CodeCell extends Disposable { private _outputContainerRenderer: CellOutputContainer; @@ -48,6 +50,7 @@ export class CodeCell extends Disposable { private readonly notebookEditor: IActiveNotebookEditorDelegate, private readonly viewCell: CodeCellViewModel, private readonly templateData: CodeCellRenderTemplate, + private readonly editorPool: NotebookCellEditorPool, @IInstantiationService private readonly instantiationService: IInstantiationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IOpenerService openerService: IOpenerService, @@ -66,6 +69,7 @@ export class CodeCell extends Disposable { this.initializeEditor(editorHeight); this._renderedInputCollapseState = false; // editor is always expanded initially + this.registerNotebookEditorListeners(); this.registerViewCellLayoutChange(); this.registerCellEditorEventListeners(); this.registerDecorations(); @@ -128,7 +132,7 @@ export class CodeCell extends Disposable { const executionItemElement = DOM.append(this.templateData.cellInputCollapsedContainer, DOM.$('.collapsed-execution-icon')); this._register(toDisposable(() => { - executionItemElement.parentElement?.removeChild(executionItemElement); + executionItemElement.remove(); })); this._collapsedExecutionIcon = this._register(this.instantiationService.createInstance(CollapsedCodeCellExecutionIcon, this.notebookEditor, this.viewCell, executionItemElement)); this.updateForCollapseState(); @@ -264,12 +268,52 @@ export class CodeCell extends Disposable { } } + private registerNotebookEditorListeners() { + this._register(this.notebookEditor.onDidScroll(() => { + this.adjustEditorPosition(); + })); + + this._register(this.notebookEditor.onDidChangeLayout(() => { + this.adjustEditorPosition(); + this.onCellWidthChange(); + })); + } + + private adjustEditorPosition() { + const extraOffset = - 6 /** distance to the top of the cell editor, which is 6px under the focus indicator */ - 1 /** border */; + const min = 0; + + const scrollTop = this.notebookEditor.scrollTop; + const elementTop = this.notebookEditor.getAbsoluteTopOfElement(this.viewCell); + const diff = scrollTop - elementTop + extraOffset; + + const notebookEditorLayout = this.notebookEditor.getLayoutInfo(); + + // we should stop adjusting the top when users are viewing the bottom of the cell editor + const editorMaxHeight = notebookEditorLayout.height + - notebookEditorLayout.stickyHeight + - 26 /** notebook toolbar */; + + const maxTop = + this.viewCell.layoutInfo.editorHeight + // + this.viewCell.layoutInfo.statusBarHeight + - editorMaxHeight + ; + const top = maxTop > 20 ? + clamp(min, diff, maxTop) : + min; + this.templateData.editorPart.style.top = `${top}px`; + // scroll the editor with top + this.templateData.editor?.setScrollTop(top); + } + private registerViewCellLayoutChange() { this._register(this.viewCell.onDidChangeLayout((e) => { if (e.outerWidth !== undefined) { const layoutInfo = this.templateData.editor.getLayoutInfo(); if (layoutInfo.width !== this.viewCell.layoutInfo.editorWidth) { this.onCellWidthChange(); + this.adjustEditorPosition(); } } })); @@ -280,6 +324,7 @@ export class CodeCell extends Disposable { if (e.contentHeightChanged) { if (this.viewCell.layoutInfo.editorHeight !== e.contentHeight) { this.onCellEditorHeightChange(e.contentHeight); + this.adjustEditorPosition(); } } })); @@ -374,7 +419,7 @@ export class CodeCell extends Disposable { })); } - private shouldUpdateDOMFocus() { + private shouldPreserveEditor() { // The DOM focus needs to be adjusted: // when a cell editor should be focused // the document active element is inside the notebook editor or the document body (cell editor being disposed previously) @@ -384,7 +429,7 @@ export class CodeCell extends Disposable { } private updateEditorForFocusModeChange(sync: boolean) { - if (this.shouldUpdateDOMFocus()) { + if (this.shouldPreserveEditor()) { if (sync) { this.templateData.editor?.focus(); } else { @@ -496,7 +541,7 @@ export class CodeCell extends Disposable { } elements.forEach(element => { - element.parentElement?.removeChild(element); + element.remove(); }); } @@ -532,7 +577,17 @@ export class CodeCell extends Disposable { } private layoutEditor(dimension: IDimension): void { - this.templateData.editor?.layout(dimension, true); + const editorLayout = this.notebookEditor.getLayoutInfo(); + const maxHeight = Math.min( + editorLayout.height + - editorLayout.stickyHeight + - 26 /** notebook toolbar */, + dimension.height + ); + this.templateData.editor?.layout({ + width: dimension.width, + height: maxHeight + }, true); } private onCellWidthChange(): void { @@ -571,8 +626,9 @@ export class CodeCell extends Disposable { this._isDisposed = true; // move focus back to the cell list otherwise the focus goes to body - if (this.shouldUpdateDOMFocus()) { - this.notebookEditor.focusContainer(); + if (this.shouldPreserveEditor()) { + // now the focus is on the monaco editor for the cell but detached from the rows. + this.editorPool.preserveFocusedEditor(this.viewCell); } this.viewCell.detachTextEditor(); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts index de2c0e91..c6f34536 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts @@ -58,12 +58,15 @@ export class RunToolbar extends CellContentPart { override didRenderCell(element: ICellViewModel): void { this.cellDisposables.add(registerCellToolbarStickyScroll(this.notebookEditor, element, this.runButtonContainer)); - this.toolbar.context = { - ui: true, - cell: element, - notebookEditor: this.notebookEditor, - $mid: MarshalledId.NotebookCellActionContext - }; + if (this.notebookEditor.hasModel()) { + const context: INotebookCellActionContext & { $mid: number } = { + ui: true, + cell: element, + notebookEditor: this.notebookEditor, + $mid: MarshalledId.NotebookCellActionContext + }; + this.toolbar.context = context; + } } getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts index b1a3e8b3..7fbe705a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts @@ -349,7 +349,7 @@ export class MarkupCell extends Disposable { // create a special context key service that set the inCompositeEditor-contextkey const editorContextKeyService = this.contextKeyService.createScoped(this.templateData.editorPart); EditorContextKeys.inCompositeEditor.bindTo(editorContextKeyService).set(true); - const editorInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService])); + const editorInstaService = this.editorDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService]))); this.editorDisposables.add(editorContextKeyService); this.editor = this.editorDisposables.add(editorInstaService.createInstance(CodeEditorWidget, this.templateData.editorContainer, { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool.ts new file mode 100644 index 00000000..f37e78c1 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService, IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { CellFocusMode, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; + +export class NotebookCellEditorPool extends Disposable { + private readonly _focusedEditorDOM: HTMLElement; + private readonly _editorDisposable = this._register(new MutableDisposable()); + private _editorContextKeyService!: IScopedContextKeyService; + private _editor!: CodeEditorWidget; + private _focusEditorCancellablePromise: CancelablePromise | undefined; + private _isInitialized = false; + private _isDisposed = false; + + constructor( + readonly notebookEditor: INotebookEditorDelegate, + private readonly contextKeyServiceProvider: (container: HTMLElement) => IScopedContextKeyService, + @ITextModelService private readonly textModelService: ITextModelService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + super(); + + this._focusedEditorDOM = this.notebookEditor.getDomNode().appendChild(DOM.$('.cell-editor-part-cache')); + this._focusedEditorDOM.style.position = 'absolute'; + this._focusedEditorDOM.style.top = '-50000px'; + this._focusedEditorDOM.style.width = '1px'; + this._focusedEditorDOM.style.height = '1px'; + } + + private _initializeEditor(cell: ICellViewModel) { + this._editorContextKeyService = this._register(this.contextKeyServiceProvider(this._focusedEditorDOM)); + + const editorContainer = DOM.prepend(this._focusedEditorDOM, DOM.$('.cell-editor-container')); + const editorInstaService = this._register(this._instantiationService.createChild(new ServiceCollection([IContextKeyService, this._editorContextKeyService]))); + EditorContextKeys.inCompositeEditor.bindTo(this._editorContextKeyService).set(true); + const editorOptions = new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(cell.language), this.notebookEditor.notebookOptions, this._configurationService); + + this._editor = this._register(editorInstaService.createInstance(CodeEditorWidget, editorContainer, { + ...editorOptions.getDefaultValue(), + dimension: { + width: 0, + height: 0 + }, + scrollbar: { + vertical: 'hidden', + horizontal: 'auto', + handleMouseWheel: false, + useShadows: false, + }, + }, { + contributions: this.notebookEditor.creationOptions.cellEditorContributions + })); + + this._isInitialized = true; + } + + preserveFocusedEditor(cell: ICellViewModel): void { + if (!this._isInitialized) { + this._initializeEditor(cell); + } + + this._editorDisposable.clear(); + this._focusEditorCancellablePromise?.cancel(); + + this._focusEditorCancellablePromise = createCancelablePromise(async token => { + const ref = await this.textModelService.createModelReference(cell.uri); + + if (this._isDisposed || token.isCancellationRequested) { + ref.dispose(); + return; + } + + const editorDisposable = new DisposableStore(); + editorDisposable.add(ref); + this._editor.setModel(ref.object.textEditorModel); + this._editor.setSelections(cell.getSelections()); + this._editor.focus(); + + const _update = () => { + const editorSelections = this._editor.getSelections(); + if (editorSelections) { + cell.setSelections(editorSelections); + } + + this.notebookEditor.revealInView(cell); + this._editor.setModel(null); + ref.dispose(); + }; + + editorDisposable.add(this._editor.onDidChangeModelContent((e) => { + _update(); + })); + + editorDisposable.add(this._editor.onDidChangeCursorSelection(e => { + if (e.source === 'keyboard' || e.source === 'mouse') { + _update(); + } + })); + + editorDisposable.add(this.notebookEditor.onDidChangeActiveEditor(() => { + const latestActiveCell = this.notebookEditor.getActiveCell(); + + if (latestActiveCell !== cell || latestActiveCell.focusMode !== CellFocusMode.Editor) { + // focus moves to another cell or cell container + // we should stop preserving the editor + this._editorDisposable.clear(); + this._editor.setModel(null); + ref.dispose(); + } + })); + + this._editorDisposable.value = editorDisposable; + }); + } + + override dispose() { + this._isDisposed = true; + this._focusEditorCancellablePromise?.cancel(); + + super.dispose(); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index a7cd34ca..cd401a3b 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1152,9 +1152,6 @@ export class NotebookCellList extends WorkbenchList implements ID // Scrolled into view from above this.view.setScrollTop(positionTop - 30); } - - - element.revealRangeInCenter(range); } //#endregion diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index 52fbff10..d94da927 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -210,7 +210,7 @@ export class NotebookCellsLayout implements IRangeMap { return this.count; } - return this._prefixSumComputer.getIndexOf(offset).index; + return this._prefixSumComputer.getIndexOf(Math.trunc(offset)).index; } indexAfter(position: number): number { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 9cc82bc2..5df72991 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -30,7 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { ITextEditorOptions, ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -270,7 +270,7 @@ export class BackLayerWebView extends Themable { 'notebook-markdown-line-height': typeof this.options.markdownLineHeight === 'number' && this.options.markdownLineHeight > 0 ? `${this.options.markdownLineHeight}px` : `normal`, 'notebook-cell-output-font-size': `${this.options.outputFontSize || this.options.fontSize}px`, 'notebook-cell-output-line-height': `${this.options.outputLineHeight}px`, - 'notebook-cell-output-max-height': `${this.options.outputLineHeight * this.options.outputLineLimit}px`, + 'notebook-cell-output-max-height': `${this.options.outputLineHeight * this.options.outputLineLimit + 2}px`, 'notebook-cell-output-font-family': this.options.outputFontFamily || this.options.fontFamily, 'notebook-cell-markup-empty-content': nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double-click or press enter to edit."), 'notebook-cell-renderer-not-found-error': nls.localize({ @@ -436,7 +436,7 @@ export class BackLayerWebView extends Themable { } table, thead, tr, th, td, tbody { - border: none !important; + border: none; border-color: transparent; border-spacing: 0; border-collapse: collapse; @@ -569,7 +569,7 @@ export class BackLayerWebView extends Themable { } return [ - dirname(FileAccess.asFileUri('vs/loader.js')), + dirname(FileAccess.asFileUri('vs/loader.js')), // TODO@esm this file will not exist in the future ]; } @@ -1119,7 +1119,9 @@ export class BackLayerWebView extends Themable { } if (match) { - match.group.openEditor(match.editor, lineNumber !== undefined && column !== undefined ? { selection: { startLineNumber: lineNumber, startColumn: column } } : undefined); + const selection: ITextEditorSelection | undefined = lineNumber !== undefined && column !== undefined ? { startLineNumber: lineNumber, startColumn: column } : undefined; + const textEditorOptions: ITextEditorOptions = { selection: selection }; + match.group.openEditor(match.editor, selection ? textEditorOptions : undefined); } else { this.openerService.open(uri, { fromUserGesture: true, fromWorkspace: true }); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 9f6f5b8d..47841c23 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -50,6 +50,7 @@ import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewM import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { NotebookCellEditorPool } from 'vs/workbench/contrib/notebook/browser/view/notebookCellEditorPool'; const $ = DOM.$; @@ -157,7 +158,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen const innerContent = DOM.append(container, $('.cell.markdown')); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); - const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])); + const scopedInstaService = templateDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService]))); const rootClassDelegate = { toggle: (className: string, force?: boolean) => container.classList.toggle(className, force) }; @@ -239,6 +240,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende constructor( notebookEditor: INotebookEditorDelegate, private renderedEditors: Map, + private editorPool: NotebookCellEditorPool, dndController: CellDragAndDropController, contextKeyServiceProvider: (container: HTMLElement) => IScopedContextKeyService, @IConfigurationService configurationService: IConfigurationService, @@ -279,7 +281,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende // create a special context key service that set the inCompositeEditor-contextkey const editorContextKeyService = templateDisposables.add(this.contextKeyServiceProvider(editorPart)); - const editorInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService])); + const editorInstaService = templateDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService]))); EditorContextKeys.inCompositeEditor.bindTo(editorContextKeyService).set(true); const editor = editorInstaService.createInstance(CodeEditorWidget, editorContainer, { @@ -288,6 +290,12 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende width: 0, height: 0 }, + scrollbar: { + vertical: 'hidden', + horizontal: 'auto', + handleMouseWheel: false, + useShadows: false, + }, }, { contributions: this.notebookEditor.creationOptions.cellEditorContributions }); @@ -303,7 +311,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const bottomCellToolbarContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const focusIndicatorBottom = new FastDomNode(DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom'))); - const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])); + const scopedInstaService = templateDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService]))); const rootClassDelegate = { toggle: (className: string, force?: boolean) => container.classList.toggle(className, force) }; @@ -376,7 +384,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende templateData.outputContainer.domNode.innerText = ''; templateData.outputContainer.domNode.appendChild(templateData.cellOutputCollapsedContainer); - templateData.elementDisposables.add(templateData.instantiationService.createInstance(CodeCell, this.notebookEditor, element, templateData)); + templateData.elementDisposables.add(templateData.instantiationService.createInstance(CodeCell, this.notebookEditor, element, templateData, this.editorPool)); this.renderedEditors.set(element, templateData.editor); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index fb7dffde..f80e4f40 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -461,7 +461,7 @@ async function webviewPreloads(ctx: PreloadContext) { id, height, init: update.init, - isOutput: update.isOutput, + isOutput: update.isOutput }); } else { this.pending.set(id, { @@ -484,6 +484,11 @@ async function webviewPreloads(ctx: PreloadContext) { } }; + function elementHasContent(height: number) { + // we need to account for a potential 1px top and bottom border on a child within the output container + return height > 2.1; + } + const resizeObserver = new class { private readonly _observer: ResizeObserver; @@ -519,23 +524,23 @@ async function webviewPreloads(ctx: PreloadContext) { continue; } - const newHeight = entry.contentRect.height; + const hasContent = elementHasContent(entry.contentRect.height); const shouldUpdatePadding = - (newHeight !== 0 && observedElementInfo.lastKnownPadding === 0) || - (newHeight === 0 && observedElementInfo.lastKnownPadding !== 0); + (hasContent && observedElementInfo.lastKnownPadding === 0) || + (!hasContent && observedElementInfo.lastKnownPadding !== 0); if (shouldUpdatePadding) { // Do not update dimension in resize observer window.requestAnimationFrame(() => { - if (newHeight !== 0) { + if (hasContent) { entry.target.style.padding = `${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodeLeftPadding}px`; } else { entry.target.style.padding = `0px`; } - this.updateHeight(observedElementInfo, entry.target.offsetHeight); + this.updateHeight(observedElementInfo, hasContent ? entry.target.offsetHeight : 0); }); } else { - this.updateHeight(observedElementInfo, entry.target.offsetHeight); + this.updateHeight(observedElementInfo, hasContent ? entry.target.offsetHeight : 0); } } }); @@ -2755,10 +2760,6 @@ async function webviewPreloads(ctx: PreloadContext) { this.element.style.visibility = ''; this.element.style.top = `${top}px`; - - dimensionUpdater.updateHeight(outputId, outputContainer.element.offsetHeight, { - isOutput: true, - }); } public hide() { @@ -2941,17 +2942,26 @@ async function webviewPreloads(ctx: PreloadContext) { const offsetHeight = this.element.offsetHeight; const cps = document.defaultView!.getComputedStyle(this.element); - if (offsetHeight !== 0 && cps.padding === '0px') { - // we set padding to zero if the output height is zero (then we can have a zero-height output DOM node) + const verticalPadding = parseFloat(cps.paddingTop) + parseFloat(cps.paddingBottom); + const contentHeight = offsetHeight - verticalPadding; + if (elementHasContent(contentHeight) && cps.padding === '0px') { + // we set padding to zero if the output has no content (then we can have a zero-height output DOM node) // thus we need to ensure the padding is accounted when updating the init height of the output dimensionUpdater.updateHeight(this.outputId, offsetHeight + ctx.style.outputNodePadding * 2, { isOutput: true, - init: true, + init: true }); this.element.style.padding = `${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodeLeftPadding}`; - } else { + } else if (elementHasContent(contentHeight)) { dimensionUpdater.updateHeight(this.outputId, this.element.offsetHeight, { + isOutput: true, + init: true + }); + this.element.style.padding = `0 ${ctx.style.outputNodePadding}px 0 ${ctx.style.outputNodeLeftPadding}`; + } else { + // we have a zero-height output DOM node + dimensionUpdater.updateHeight(this.outputId, 0, { isOutput: true, init: true, }); @@ -3069,7 +3079,7 @@ async function webviewPreloads(ctx: PreloadContext) { }); if (this.dragOverlay) { - window.document.body.removeChild(this.dragOverlay); + this.dragOverlay.remove(); this.dragOverlay = undefined; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index f946c214..f75bce45 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -8,8 +8,9 @@ import { Disposable, IDisposable, IReference, MutableDisposable, dispose } from import { Mimes } from 'vs/base/common/mime'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { IEditorCommentsOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; @@ -19,12 +20,12 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { IWordWrapTransientState, readTransientState, writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CellEditState, CellFocusMode, CursorAtBoundary, CursorAtLineBoundary, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, CellLayoutChangeEvent, CursorAtBoundary, CursorAtLineBoundary, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, INotebookCellStatusBarItem, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, INotebookCellStatusBarItem, INotebookFindOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export abstract class BaseCellViewModel extends Disposable { @@ -85,6 +86,15 @@ export abstract class BaseCellViewModel extends Disposable { this._onDidChangeState.fire({ cellLineNumberChanged: true }); } + private _commentOptions: IEditorCommentsOptions; + public get commentOptions(): IEditorCommentsOptions { + return this._commentOptions; + } + + public set commentOptions(newOptions: IEditorCommentsOptions) { + this._commentOptions = newOptions; + } + private _focusMode: CellFocusMode = CellFocusMode.Container; get focusMode() { return this._focusMode; @@ -158,6 +168,16 @@ export abstract class BaseCellViewModel extends Disposable { this._onDidChangeState.fire({ outputCollapsedChanged: true }); } + protected _commentHeight = 0; + + set commentHeight(height: number) { + if (this._commentHeight === height) { + return; + } + this._commentHeight = height; + this.layoutChange({ commentHeight: true }, 'BaseCellViewModel#commentHeight'); + } + private _isDisposed = false; constructor( @@ -198,13 +218,20 @@ export abstract class BaseCellViewModel extends Disposable { if (this.model.collapseState?.outputCollapsed) { this._outputCollapsed = true; } + + this._commentOptions = this._configurationService.getValue('editor.comments', { overrideIdentifier: this.language }); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.comments')) { + this._commentOptions = this._configurationService.getValue('editor.comments', { overrideIdentifier: this.language }); + } + })); } abstract updateOptions(e: NotebookOptionsChangeEvent): void; abstract getHeight(lineHeight: number): number; abstract onDeselect(): void; - abstract layoutChange(change: any): void; + abstract layoutChange(change: CellLayoutChangeEvent, source?: string): void; assertTextModelAttached(): boolean { if (this.textModel && this._textEditor && this._textEditor.getModel() === this.textModel) { @@ -258,7 +285,12 @@ export abstract class BaseCellViewModel extends Disposable { writeTransientState(editor.getModel(), this._editorTransientState, this._codeEditorService); } - this._textEditor?.changeDecorations((accessor) => { + if (this._isDisposed) { + // Restore View State could adjust the editor layout and trigger a list view update. The list view update might then dispose this view model. + return; + } + + editor.changeDecorations((accessor) => { this._resolvedDecorations.forEach((value, key) => { if (key.startsWith('_lazy_')) { // lazy ones @@ -272,7 +304,7 @@ export abstract class BaseCellViewModel extends Disposable { }); }); - this._editorListeners.push(this._textEditor.onDidChangeCursorSelection(() => { this._onDidChangeState.fire({ selectionChanged: true }); })); + this._editorListeners.push(editor.onDidChangeCursorSelection(() => { this._onDidChangeState.fire({ selectionChanged: true }); })); const inlineChatController = InlineChatController.get(this._textEditor); if (inlineChatController) { this._editorListeners.push(inlineChatController.onWillStartSession(() => { @@ -281,7 +313,7 @@ export abstract class BaseCellViewModel extends Disposable { } })); } - // this._editorListeners.push(this._textEditor.onKeyDown(e => this.handleKeyDown(e))); + this._onDidChangeState.fire({ selectionChanged: true }); this._onDidChangeEditorAttachState.fire(); } @@ -495,12 +527,24 @@ export abstract class BaseCellViewModel extends Disposable { setSelections(selections: Selection[]) { if (selections.length) { - this._textEditor?.setSelections(selections); + if (this._textEditor) { + this._textEditor?.setSelections(selections); + } else if (this._editorViewStates) { + this._editorViewStates.cursorState = selections.map(selection => { + return { + inSelectionMode: !selection.isEmpty(), + selectionStart: selection.getStartPosition(), + position: selection.getEndPosition(), + }; + }); + } } } getSelections() { - return this._textEditor?.getSelections() || []; + return this._textEditor?.getSelections() + ?? this._editorViewStates?.cursorState.map(state => new Selection(state.selectionStart.lineNumber, state.selectionStart.column, state.position.lineNumber, state.position.column)) + ?? []; } getSelectionsStartPosition(): IPosition[] | undefined { @@ -645,20 +689,21 @@ export abstract class BaseCellViewModel extends Disposable { protected abstract onDidChangeTextModelContent(): void; - protected cellStartFind(value: string, options: INotebookSearchOptions): model.FindMatch[] | null { + protected cellStartFind(value: string, options: INotebookFindOptions): model.FindMatch[] | null { let cellMatches: model.FindMatch[] = []; + const lineCount = this.textBuffer.getLineCount(); + const findRange: IRange[] = options.findScope?.selectedTextRanges ?? [new Range(1, 1, lineCount, this.textBuffer.getLineLength(lineCount) + 1)]; + if (this.assertTextModelAttached()) { cellMatches = this.textModel!.findMatches( value, - false, + findRange, options.regex || false, options.caseSensitive || false, options.wholeWord ? options.wordSeparators || null : null, options.regex || false); } else { - const lineCount = this.textBuffer.getLineCount(); - const fullRange = new Range(1, 1, lineCount, this.textBuffer.getLineLength(lineCount) + 1); const searchParams = new SearchParams(value, options.regex || false, options.caseSensitive || false, options.wholeWord ? options.wordSeparators || null : null,); const searchData = searchParams.parseSearchRequest(); @@ -666,7 +711,9 @@ export abstract class BaseCellViewModel extends Disposable { return null; } - cellMatches = this.textBuffer.findMatchesLineByLine(fullRange, searchData, options.regex || false, 1000); + findRange.forEach(range => { + cellMatches.push(...this.textBuffer.findMatchesLineByLine(new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn), searchData, options.regex || false, 1000)); + }); } return cellMatches; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts index e8cf31df..5c523a51 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts @@ -5,6 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { observableValue } from 'vs/base/common/observable'; import { ICellOutputViewModel, IGenericCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { ICellOutput, IOrderedMimeType, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -14,6 +15,22 @@ let handle = 0; export class CellOutputViewModel extends Disposable implements ICellOutputViewModel { private _onDidResetRendererEmitter = this._register(new Emitter()); readonly onDidResetRenderer = this._onDidResetRendererEmitter.event; + + private alwaysShow = false; + visible = observableValue('outputVisible', false); + setVisible(visible = true, force: boolean = false) { + if (!visible && this.alwaysShow) { + // we are forced to show, so no-op + return; + } + + if (force && visible) { + this.alwaysShow = true; + } + + this.visible.set(visible, undefined); + } + outputHandle = handle++; get model(): ICellOutput { return this._outputRawData; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 3f5a6c3e..3ef89aa0 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -5,25 +5,25 @@ import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; +import { observableValue } from 'vs/base/common/observable'; import * as UUID from 'vs/base/common/uuid'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { PrefixSumComputer } from 'vs/editor/common/model/prefixSumComputer'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, CellLayoutState, ICellOutputViewModel, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFindMatch, CellLayoutState, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellOutputViewModel, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; +import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { CellOutputViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel'; import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, INotebookSearchOptions, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; +import { CellKind, INotebookFindOptions, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellExecutionError, ICellExecutionStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { BaseCellViewModel } from './baseCellViewModel'; -import { NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; -import { ICellExecutionError, ICellExecutionStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { observableValue } from 'vs/base/common/observable'; export const outputDisplayLimit = 500; @@ -80,16 +80,6 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod return this._chatHeight; } - private _commentHeight = 0; - - set commentHeight(height: number) { - if (this._commentHeight === height) { - return; - } - this._commentHeight = height; - this.layoutChange({ commentHeight: true }, 'CodeCellViewModel#commentHeight'); - } - private _hoveringOutput: boolean = false; public get outputIsHovered(): boolean { return this._hoveringOutput; @@ -193,6 +183,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod : 0, chatHeight: 0, statusBarHeight: 0, + commentOffset: 0, commentHeight: 0, outputContainerOffset: 0, outputTotalHeight: 0, @@ -289,11 +280,12 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod editorHeight, editorWidth, statusBarHeight, - commentHeight, outputContainerOffset, outputTotalHeight, outputShowMoreContainerHeight, outputShowMoreContainerOffset, + commentOffset: outputContainerOffset + outputTotalHeight, + commentHeight, totalHeight, codeIndicatorHeight, outputIndicatorHeight, @@ -330,11 +322,12 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod editorWidth, chatHeight: chatHeight, statusBarHeight: 0, - commentHeight, outputContainerOffset, outputTotalHeight, outputShowMoreContainerHeight, outputShowMoreContainerOffset, + commentOffset: outputContainerOffset + outputTotalHeight, + commentHeight, totalHeight, codeIndicatorHeight, outputIndicatorHeight, @@ -359,22 +352,9 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod super.restoreEditorViewState(editorViewStates); if (totalHeight !== undefined && this._layoutInfo.layoutState !== CellLayoutState.Measured) { this._layoutInfo = { - fontInfo: this._layoutInfo.fontInfo, - chatHeight: this._layoutInfo.chatHeight, - editorHeight: this._layoutInfo.editorHeight, - editorWidth: this._layoutInfo.editorWidth, - statusBarHeight: this.layoutInfo.statusBarHeight, - commentHeight: this.layoutInfo.commentHeight, - outputContainerOffset: this._layoutInfo.outputContainerOffset, - outputTotalHeight: this._layoutInfo.outputTotalHeight, - outputShowMoreContainerHeight: this._layoutInfo.outputShowMoreContainerHeight, - outputShowMoreContainerOffset: this._layoutInfo.outputShowMoreContainerOffset, + ...this._layoutInfo, totalHeight: totalHeight, - codeIndicatorHeight: this._layoutInfo.codeIndicatorHeight, - outputIndicatorHeight: this._layoutInfo.outputIndicatorHeight, - bottomToolbarOffset: this._layoutInfo.bottomToolbarOffset, layoutState: CellLayoutState.FromCache, - estimatedHasHorizontalScrolling: this._layoutInfo.estimatedHasHorizontalScrolling }; } } @@ -464,7 +444,14 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } this._ensureOutputsTop(); - if (height < 28 && this._outputViewModels[index].hasMultiMimeType()) { + + if (index === 0 || height > 0) { + this._outputViewModels[index].setVisible(true); + } else if (height === 0) { + this._outputViewModels[index].setVisible(false); + } + + if (this._outputViewModels[index].visible.get() && height < 28) { height = 28; } @@ -518,7 +505,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod private readonly _hasFindResult = this._register(new Emitter()); public readonly hasFindResult: Event = this._hasFindResult.event; - startFind(value: string, options: INotebookSearchOptions): CellFindMatch | null { + startFind(value: string, options: INotebookFindOptions): CellFindMatch | null { const matches = super.cellStartFind(value, options); if (matches === null) { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts index 0f255cc2..3652f955 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts @@ -10,7 +10,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CellEditState, CellFindMatch, CellFoldingState, CellLayoutContext, CellLayoutState, EditorFoldingStateDelegate, ICellOutputViewModel, ICellViewModel, MarkupCellLayoutChangeEvent, MarkupCellLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, INotebookFindOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; @@ -134,6 +134,8 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM editorWidth: initialNotebookLayoutInfo?.width ? this.viewContext.notebookOptions.computeMarkdownCellEditorWidth(initialNotebookLayoutInfo.width) : 0, + commentOffset: 0, + commentHeight: 0, bottomToolbarOffset: bottomToolbarGap, totalHeight: 100, layoutState: CellLayoutState.Uninitialized, @@ -160,13 +162,14 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM + layoutConfiguration.markdownCellTopMargin + layoutConfiguration.markdownCellBottomMargin + bottomToolbarGap - + this._statusBarHeight; + + this._statusBarHeight + + this._commentHeight; } else { // @rebornix // On file open, the previewHeight + bottomToolbarGap for a cell out of viewport can be 0 // When it's 0, the list view will never try to render it anymore even if we scroll the cell into view. // Thus we make sure it's greater than 0 - return Math.max(1, this._previewHeight + bottomToolbarGap + foldHintHeight); + return Math.max(1, this._previewHeight + bottomToolbarGap + foldHintHeight + this._commentHeight); } } @@ -204,51 +207,59 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM } layoutChange(state: MarkupCellLayoutChangeEvent) { - // recompute - const foldHintHeight = this._computeFoldHintHeight(); + let totalHeight: number; + let foldHintHeight: number; if (!this.isInputCollapsed) { - const editorWidth = state.outerWidth !== undefined - ? this.viewContext.notebookOptions.computeMarkdownCellEditorWidth(state.outerWidth) - : this._layoutInfo.editorWidth; - const totalHeight = state.totalHeight === undefined - ? (this._layoutInfo.layoutState === CellLayoutState.Uninitialized ? 100 : this._layoutInfo.totalHeight) - : state.totalHeight; - const previewHeight = this._previewHeight; - - this._layoutInfo = { - fontInfo: state.font || this._layoutInfo.fontInfo, - editorWidth, - previewHeight, - chatHeight: this._chatHeight, - editorHeight: this._editorHeight, - statusBarHeight: this._statusBarHeight, - bottomToolbarOffset: this.viewContext.notebookOptions.computeBottomToolbarOffset(totalHeight, this.viewType), - totalHeight, - layoutState: CellLayoutState.Measured, - foldHintHeight - }; + totalHeight = state.totalHeight === undefined ? + (this._layoutInfo.layoutState === + CellLayoutState.Uninitialized ? + 100 : + this._layoutInfo.totalHeight) : + state.totalHeight; + // recompute + foldHintHeight = this._computeFoldHintHeight(); } else { - const editorWidth = state.outerWidth !== undefined - ? this.viewContext.notebookOptions.computeMarkdownCellEditorWidth(state.outerWidth) - : this._layoutInfo.editorWidth; - const totalHeight = this.viewContext.notebookOptions.computeCollapsedMarkdownCellHeight(this.viewType); - + totalHeight = + this.viewContext.notebookOptions + .computeCollapsedMarkdownCellHeight(this.viewType); state.totalHeight = totalHeight; - this._layoutInfo = { - fontInfo: state.font || this._layoutInfo.fontInfo, - editorWidth, - chatHeight: this._chatHeight, - editorHeight: this._editorHeight, - statusBarHeight: this._statusBarHeight, - previewHeight: this._previewHeight, - bottomToolbarOffset: this.viewContext.notebookOptions.computeBottomToolbarOffset(totalHeight, this.viewType), - totalHeight, - layoutState: CellLayoutState.Measured, - foldHintHeight: 0 - }; + foldHintHeight = 0; + } + let commentOffset: number; + if (this.getEditState() === CellEditState.Editing) { + const notebookLayoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); + commentOffset = notebookLayoutConfiguration.editorToolbarHeight + + notebookLayoutConfiguration.cellTopMargin // CELL_TOP_MARGIN + + this._chatHeight + + this._editorHeight + + this._statusBarHeight; + } else { + commentOffset = this._previewHeight; } + this._layoutInfo = { + fontInfo: state.font || this._layoutInfo.fontInfo, + editorWidth: state.outerWidth !== undefined ? + this.viewContext.notebookOptions + .computeMarkdownCellEditorWidth(state.outerWidth) : + this._layoutInfo.editorWidth, + chatHeight: this._chatHeight, + editorHeight: this._editorHeight, + statusBarHeight: this._statusBarHeight, + previewHeight: this._previewHeight, + bottomToolbarOffset: this.viewContext.notebookOptions + .computeBottomToolbarOffset( + totalHeight, this.viewType), + totalHeight, + layoutState: CellLayoutState.Measured, + foldHintHeight, + commentOffset, + commentHeight: state.commentHeight ? + this._commentHeight : + this._layoutInfo.commentHeight, + }; + this._onDidChangeLayout.fire(state); } @@ -257,16 +268,12 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM // we might already warmup the viewport so the cell has a total height computed if (totalHeight !== undefined && this.layoutInfo.layoutState === CellLayoutState.Uninitialized) { this._layoutInfo = { - fontInfo: this._layoutInfo.fontInfo, - editorWidth: this._layoutInfo.editorWidth, - previewHeight: this._layoutInfo.previewHeight, - bottomToolbarOffset: this._layoutInfo.bottomToolbarOffset, + ...this.layoutInfo, totalHeight: totalHeight, chatHeight: this._chatHeight, editorHeight: this._editorHeight, statusBarHeight: this._statusBarHeight, layoutState: CellLayoutState.FromCache, - foldHintHeight: this._layoutInfo.foldHintHeight }; this.layoutChange({}); } @@ -295,7 +302,7 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM private readonly _hasFindResult = this._register(new Emitter()); public readonly hasFindResult: Event = this._hasFindResult.event; - startFind(value: string, options: INotebookSearchOptions): CellFindMatch | null { + startFind(value: string, options: INotebookFindOptions): CellFindMatch | null { const matches = super.cellStartFind(value, options); if (matches === null) { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource.ts new file mode 100644 index 00000000..767b9f8f --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource.ts @@ -0,0 +1,222 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { IActiveNotebookEditor, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { OutlineChangeEvent, OutlineConfigKeys } from 'vs/workbench/services/outline/browser/outline'; +import { OutlineEntry } from './OutlineEntry'; +import { IOutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; + +export interface INotebookCellOutlineDataSource { + readonly activeElement: OutlineEntry | undefined; + readonly entries: OutlineEntry[]; +} + +export class NotebookCellOutlineDataSource implements INotebookCellOutlineDataSource { + + private readonly _disposables = new DisposableStore(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + private _uri: URI | undefined; + private _entries: OutlineEntry[] = []; + private _activeEntry?: OutlineEntry; + + private readonly _outlineEntryFactory: NotebookOutlineEntryFactory; + + constructor( + private readonly _editor: INotebookEditor, + @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, + @IOutlineModelService private readonly _outlineModelService: IOutlineModelService, + @IMarkerService private readonly _markerService: IMarkerService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + this._outlineEntryFactory = new NotebookOutlineEntryFactory(this._notebookExecutionStateService); + this.recomputeState(); + } + + get activeElement(): OutlineEntry | undefined { + return this._activeEntry; + } + get entries(): OutlineEntry[] { + return this._entries; + } + get isEmpty(): boolean { + return this._entries.length === 0; + } + get uri() { + return this._uri; + } + + public async computeFullSymbols(cancelToken: CancellationToken) { + const notebookEditorWidget = this._editor; + + const notebookCells = notebookEditorWidget?.getViewModel()?.viewCells.filter((cell) => cell.cellKind === CellKind.Code); + + if (notebookCells) { + const promises: Promise[] = []; + // limit the number of cells so that we don't resolve an excessive amount of text models + for (const cell of notebookCells.slice(0, 100)) { + // gather all symbols asynchronously + promises.push(this._outlineEntryFactory.cacheSymbols(cell, this._outlineModelService, cancelToken)); + } + await Promise.allSettled(promises); + } + this.recomputeState(); + } + + public recomputeState(): void { + this._disposables.clear(); + this._activeEntry = undefined; + this._uri = undefined; + + if (!this._editor.hasModel()) { + return; + } + + this._uri = this._editor.textModel.uri; + + const notebookEditorWidget: IActiveNotebookEditor = this._editor; + + if (notebookEditorWidget.getLength() === 0) { + return; + } + + const notebookCells = notebookEditorWidget.getViewModel().viewCells; + + const entries: OutlineEntry[] = []; + for (const cell of notebookCells) { + entries.push(...this._outlineEntryFactory.getOutlineEntries(cell, entries.length)); + } + + // build a tree from the list of entries + if (entries.length > 0) { + const result: OutlineEntry[] = [entries[0]]; + const parentStack: OutlineEntry[] = [entries[0]]; + + for (let i = 1; i < entries.length; i++) { + const entry = entries[i]; + + while (true) { + const len = parentStack.length; + if (len === 0) { + // root node + result.push(entry); + parentStack.push(entry); + break; + + } else { + const parentCandidate = parentStack[len - 1]; + if (parentCandidate.level < entry.level) { + parentCandidate.addChild(entry); + parentStack.push(entry); + break; + } else { + parentStack.pop(); + } + } + } + } + this._entries = result; + } + + // feature: show markers with each cell + const markerServiceListener = new MutableDisposable(); + this._disposables.add(markerServiceListener); + const updateMarkerUpdater = () => { + if (notebookEditorWidget.isDisposed) { + return; + } + + const doUpdateMarker = (clear: boolean) => { + for (const entry of this._entries) { + if (clear) { + entry.clearMarkers(); + } else { + entry.updateMarkers(this._markerService); + } + } + }; + const problem = this._configurationService.getValue('problems.visibility'); + if (problem === undefined) { + return; + } + + const config = this._configurationService.getValue(OutlineConfigKeys.problemsEnabled); + + if (problem && config) { + markerServiceListener.value = this._markerService.onMarkerChanged(e => { + if (notebookEditorWidget.isDisposed) { + console.error('notebook editor is disposed'); + return; + } + + if (e.some(uri => notebookEditorWidget.getCellsInRange().some(cell => isEqual(cell.uri, uri)))) { + doUpdateMarker(false); + this._onDidChange.fire({}); + } + }); + doUpdateMarker(false); + } else { + markerServiceListener.clear(); + doUpdateMarker(true); + } + }; + updateMarkerUpdater(); + this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('problems.visibility') || e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) { + updateMarkerUpdater(); + this._onDidChange.fire({}); + } + })); + + const { changeEventTriggered } = this.recomputeActive(); + if (!changeEventTriggered) { + this._onDidChange.fire({}); + } + } + + public recomputeActive(): { changeEventTriggered: boolean } { + let newActive: OutlineEntry | undefined; + const notebookEditorWidget = this._editor; + + if (notebookEditorWidget) {//TODO don't check for widget, only here if we do have + if (notebookEditorWidget.hasModel() && notebookEditorWidget.getLength() > 0) { + const cell = notebookEditorWidget.cellAt(notebookEditorWidget.getFocus().start); + if (cell) { + for (const entry of this._entries) { + newActive = entry.find(cell, []); + if (newActive) { + break; + } + } + } + } + } + + if (newActive !== this._activeEntry) { + this._activeEntry = newActive; + this._onDidChange.fire({ affectOnlyActiveElement: true }); + return { changeEventTriggered: true }; + } + return { changeEventTriggered: false }; + } + + dispose(): void { + this._entries.length = 0; + this._activeEntry = undefined; + this._disposables.dispose(); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory.ts new file mode 100644 index 00000000..c4d2f825 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ReferenceCollection, type IReference } from 'vs/base/common/lifecycle'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import type { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookCellOutlineDataSource } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource'; + +class NotebookCellOutlineDataSourceReferenceCollection extends ReferenceCollection { + constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { + super(); + } + protected override createReferencedObject(_key: string, editor: INotebookEditor): NotebookCellOutlineDataSource { + return this.instantiationService.createInstance(NotebookCellOutlineDataSource, editor); + } + protected override destroyReferencedObject(_key: string, object: NotebookCellOutlineDataSource): void { + object.dispose(); + } +} + +export const INotebookCellOutlineDataSourceFactory = createDecorator('INotebookCellOutlineDataSourceFactory'); + +export interface INotebookCellOutlineDataSourceFactory { + getOrCreate(editor: INotebookEditor): IReference; +} + +export class NotebookCellOutlineDataSourceFactory implements INotebookCellOutlineDataSourceFactory { + private readonly _data: NotebookCellOutlineDataSourceReferenceCollection; + constructor(@IInstantiationService instantiationService: IInstantiationService) { + this._data = instantiationService.createInstance(NotebookCellOutlineDataSourceReferenceCollection); + } + + getOrCreate(editor: INotebookEditor): IReference { + return this._data.acquire(editor.getId(), editor); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts index 77a8ba22..a7de8857 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts @@ -14,7 +14,6 @@ import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { IRange } from 'vs/editor/common/core/range'; import { SymbolKind } from 'vs/editor/common/languages'; -import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; export const enum NotebookOutlineConstants { NonHeaderOutlineLevel = 7, @@ -50,7 +49,7 @@ export class NotebookOutlineEntryFactory { private readonly executionStateService: INotebookExecutionStateService ) { } - public getOutlineEntries(cell: ICellViewModel, target: OutlineTarget, index: number): OutlineEntry[] { + public getOutlineEntries(cell: ICellViewModel, index: number): OutlineEntry[] { const entries: OutlineEntry[] = []; const isMarkdown = cell.cellKind === CellKind.Markup; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts deleted file mode 100644 index 1ac84359..00000000 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts +++ /dev/null @@ -1,316 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { isEqual } from 'vs/base/common/resources'; -import { URI } from 'vs/base/common/uri'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IActiveNotebookEditor, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellKind, NotebookCellsChangeType, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { OutlineChangeEvent, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; -import { OutlineEntry } from './OutlineEntry'; -import { IOutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { NotebookOutlineConstants, NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; -import { Delayer } from 'vs/base/common/async'; - -export class NotebookCellOutlineProvider { - private readonly _disposables = new DisposableStore(); - private readonly _onDidChange = new Emitter(); - - readonly onDidChange: Event = this._onDidChange.event; - - private _uri: URI | undefined; - private _entries: OutlineEntry[] = []; - get entries(): OutlineEntry[] { - if (this.delayedOutlineRecompute.isTriggered()) { - this.delayedOutlineRecompute.cancel(); - this._recomputeState(); - } - return this._entries; - } - - private _activeEntry?: OutlineEntry; - private readonly _entriesDisposables = new DisposableStore(); - - readonly outlineKind = 'notebookCells'; - - get activeElement(): OutlineEntry | undefined { - if (this.delayedOutlineRecompute.isTriggered()) { - this.delayedOutlineRecompute.cancel(); - this._recomputeState(); - } - return this._activeEntry; - } - - private readonly _outlineEntryFactory: NotebookOutlineEntryFactory; - private readonly delayedOutlineRecompute: Delayer; - constructor( - private readonly _editor: INotebookEditor, - private readonly _target: OutlineTarget, - @IThemeService themeService: IThemeService, - @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, - @IOutlineModelService private readonly _outlineModelService: IOutlineModelService, - @IMarkerService private readonly _markerService: IMarkerService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - ) { - this._outlineEntryFactory = new NotebookOutlineEntryFactory(notebookExecutionStateService); - - const delayerRecomputeActive = this._disposables.add(new Delayer(200)); - this._disposables.add(_editor.onDidChangeSelection(() => { - delayerRecomputeActive.trigger(() => this._recomputeActive()); - }, this)); - - // .3s of a delay is sufficient, 100-200s is too quick and will unnecessarily block the ui thread. - // Given we're only updating the outline when the user types, we can afford to wait a bit. - this.delayedOutlineRecompute = this._disposables.add(new Delayer(300)); - const delayedRecompute = () => { - delayerRecomputeActive.cancel(); // Active is always recomputed after a recomputing the outline state. - this.delayedOutlineRecompute.trigger(() => this._recomputeState()); - }; - - this._disposables.add(_configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(NotebookSetting.outlineShowMarkdownHeadersOnly) || - e.affectsConfiguration(NotebookSetting.outlineShowCodeCells) || - e.affectsConfiguration(NotebookSetting.outlineShowCodeCellSymbols) || - e.affectsConfiguration(NotebookSetting.breadcrumbsShowCodeCells) - ) { - delayedRecompute(); - } - })); - - this._disposables.add(themeService.onDidFileIconThemeChange(() => { - this._onDidChange.fire({}); - })); - - this._disposables.add( - notebookExecutionStateService.onDidChangeExecution(e => { - if (e.type === NotebookExecutionType.cell && !!this._editor.textModel && e.affectsNotebook(this._editor.textModel?.uri)) { - delayedRecompute(); - } - }) - ); - - const disposable = this._disposables.add(new DisposableStore()); - const monitorModelChanges = () => { - disposable.clear(); - if (!this._editor.textModel) { - return; - } - disposable.add(this._editor.textModel.onDidChangeContent(contentChanges => { - if (contentChanges.rawEvents.some(c => c.kind === NotebookCellsChangeType.ChangeCellContent || - c.kind === NotebookCellsChangeType.ChangeCellInternalMetadata || - c.kind === NotebookCellsChangeType.Move || - c.kind === NotebookCellsChangeType.ModelChange)) { - delayedRecompute(); - } - })); - // Perhaps this is the first time we're building the outline - if (!this._entries.length) { - this._recomputeState(); - } - }; - this._disposables.add(this._editor.onDidChangeModel(monitorModelChanges)); - monitorModelChanges(); - this._recomputeState(); - } - - dispose(): void { - this._entries.length = 0; - this._activeEntry = undefined; - this._entriesDisposables.dispose(); - this._disposables.dispose(); - } - - async setFullSymbols(cancelToken: CancellationToken) { - const notebookEditorWidget = this._editor; - - const notebookCells = notebookEditorWidget?.getViewModel()?.viewCells.filter((cell) => cell.cellKind === CellKind.Code); - - if (notebookCells) { - const promises: Promise[] = []; - // limit the number of cells so that we don't resolve an excessive amount of text models - for (const cell of notebookCells.slice(0, 100)) { - // gather all symbols asynchronously - promises.push(this._outlineEntryFactory.cacheSymbols(cell, this._outlineModelService, cancelToken)); - } - await Promise.allSettled(promises); - } - - this._recomputeState(); - } - private _recomputeState(): void { - this._entriesDisposables.clear(); - this._activeEntry = undefined; - this._uri = undefined; - - if (!this._editor.hasModel()) { - return; - } - - this._uri = this._editor.textModel.uri; - - const notebookEditorWidget: IActiveNotebookEditor = this._editor; - - if (notebookEditorWidget.getLength() === 0) { - return; - } - - let includeCodeCells = true; - if (this._target === OutlineTarget.Breadcrumbs) { - includeCodeCells = this._configurationService.getValue('notebook.breadcrumbs.showCodeCells'); - } - - let notebookCells: ICellViewModel[]; - if (this._target === OutlineTarget.Breadcrumbs) { - notebookCells = notebookEditorWidget.getViewModel().viewCells.filter((cell) => cell.cellKind === CellKind.Markup || includeCodeCells); - } else { - notebookCells = notebookEditorWidget.getViewModel().viewCells; - } - - const entries: OutlineEntry[] = []; - for (const cell of notebookCells) { - entries.push(...this._outlineEntryFactory.getOutlineEntries(cell, this._target, entries.length)); - } - - // build a tree from the list of entries - if (entries.length > 0) { - const result: OutlineEntry[] = [entries[0]]; - const parentStack: OutlineEntry[] = [entries[0]]; - - for (let i = 1; i < entries.length; i++) { - const entry = entries[i]; - - while (true) { - const len = parentStack.length; - if (len === 0) { - // root node - result.push(entry); - parentStack.push(entry); - break; - - } else { - const parentCandidate = parentStack[len - 1]; - if (parentCandidate.level < entry.level) { - parentCandidate.addChild(entry); - parentStack.push(entry); - break; - } else { - parentStack.pop(); - } - } - } - } - this._entries = result; - } - - // feature: show markers with each cell - const markerServiceListener = new MutableDisposable(); - this._entriesDisposables.add(markerServiceListener); - const updateMarkerUpdater = () => { - if (notebookEditorWidget.isDisposed) { - return; - } - - const doUpdateMarker = (clear: boolean) => { - for (const entry of this._entries) { - if (clear) { - entry.clearMarkers(); - } else { - entry.updateMarkers(this._markerService); - } - } - }; - const problem = this._configurationService.getValue('problems.visibility'); - if (problem === undefined) { - return; - } - - const config = this._configurationService.getValue(OutlineConfigKeys.problemsEnabled); - - if (problem && config) { - markerServiceListener.value = this._markerService.onMarkerChanged(e => { - if (notebookEditorWidget.isDisposed) { - console.error('notebook editor is disposed'); - return; - } - - if (e.some(uri => notebookEditorWidget.getCellsInRange().some(cell => isEqual(cell.uri, uri)))) { - doUpdateMarker(false); - this._onDidChange.fire({}); - } - }); - doUpdateMarker(false); - } else { - markerServiceListener.clear(); - doUpdateMarker(true); - } - }; - updateMarkerUpdater(); - this._entriesDisposables.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('problems.visibility') || e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) { - updateMarkerUpdater(); - this._onDidChange.fire({}); - } - })); - - const { changeEventTriggered } = this._recomputeActive(); - if (!changeEventTriggered) { - this._onDidChange.fire({}); - } - } - - private _recomputeActive(): { changeEventTriggered: boolean } { - let newActive: OutlineEntry | undefined; - const notebookEditorWidget = this._editor; - - if (notebookEditorWidget) {//TODO don't check for widget, only here if we do have - if (notebookEditorWidget.hasModel() && notebookEditorWidget.getLength() > 0) { - const cell = notebookEditorWidget.cellAt(notebookEditorWidget.getFocus().start); - if (cell) { - for (const entry of this._entries) { - newActive = entry.find(cell, []); - if (newActive) { - break; - } - } - } - } - } - - // @Yoyokrazy - Make sure the new active entry isn't part of the filtered exclusions - const showCodeCells = this._configurationService.getValue(NotebookSetting.outlineShowCodeCells); - const showCodeCellSymbols = this._configurationService.getValue(NotebookSetting.outlineShowCodeCellSymbols); - const showMarkdownHeadersOnly = this._configurationService.getValue(NotebookSetting.outlineShowMarkdownHeadersOnly); - - // check the three outline filtering conditions - // if any are true, newActive should NOT be set to this._activeEntry and the event should NOT fire - if ( - (newActive !== this._activeEntry) && !( - (showMarkdownHeadersOnly && newActive?.cell.cellKind === CellKind.Markup && newActive?.level === NotebookOutlineConstants.NonHeaderOutlineLevel) || // show headers only + cell is mkdn + is level 7 (no header) - (!showCodeCells && newActive?.cell.cellKind === CellKind.Code) || // show code cells + cell is code - (!showCodeCellSymbols && newActive?.cell.cellKind === CellKind.Code && newActive?.level > NotebookOutlineConstants.NonHeaderOutlineLevel) // show code symbols + cell is code + has level > 7 (nb symbol levels) - ) - ) { - this._activeEntry = newActive; - this._onDidChange.fire({ affectOnlyActiveElement: true }); - return { changeEventTriggered: true }; - } - - return { changeEventTriggered: false }; - } - - get isEmpty(): boolean { - return this._entries.length === 0; - } - - get uri() { - return this._uri; - } -} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory.ts deleted file mode 100644 index 54411bcd..00000000 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ReferenceCollection, type IReference } from 'vs/base/common/lifecycle'; -import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import type { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; -import type { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; - -class NotebookCellOutlineProviderReferenceCollection extends ReferenceCollection { - constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { - super(); - } - protected override createReferencedObject(_key: string, editor: INotebookEditor, target: OutlineTarget): NotebookCellOutlineProvider { - return this.instantiationService.createInstance(NotebookCellOutlineProvider, editor, target); - } - protected override destroyReferencedObject(_key: string, object: NotebookCellOutlineProvider): void { - object.dispose(); - } -} - -export const INotebookCellOutlineProviderFactory = createDecorator('INotebookCellOutlineProviderFactory'); - -export interface INotebookCellOutlineProviderFactory { - getOrCreate(editor: INotebookEditor, target: OutlineTarget): IReference; -} - -export class NotebookCellOutlineProviderFactory implements INotebookCellOutlineProviderFactory { - private readonly _data: NotebookCellOutlineProviderReferenceCollection; - constructor(@IInstantiationService instantiationService: IInstantiationService) { - this._data = instantiationService.createInstance(NotebookCellOutlineProviderReferenceCollection); - } - - getOrCreate(editor: INotebookEditor, target: OutlineTarget): IReference { - return this._data.acquire(editor.getId(), editor, target); - } -} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts index 8a0f8379..60a36264 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts @@ -13,27 +13,27 @@ import { URI } from 'vs/base/common/uri'; import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IWorkspaceTextEdit } from 'vs/editor/common/languages'; import { FindMatch, IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/editor/common/model/editStack'; import { IntervalNode, IntervalTree } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { IWorkspaceTextEdit } from 'vs/editor/common/languages'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FoldingRegions } from 'vs/editor/contrib/folding/browser/foldingRanges'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { CellEditState, CellFindMatchWithIndex, CellFoldingState, EditorFoldingStateDelegate, ICellViewModel, INotebookDeltaCellStatusBarItems, INotebookDeltaDecoration, ICellModelDecorations, ICellModelDeltaDecorations, IModelDecorationsChangeAccessor, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFindMatchModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; +import { CellEditState, CellFindMatchWithIndex, CellFoldingState, EditorFoldingStateDelegate, ICellModelDecorations, ICellModelDeltaDecorations, ICellViewModel, IModelDecorationsChangeAccessor, INotebookDeltaCellStatusBarItems, INotebookDeltaDecoration, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookLayoutInfo, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { NotebookCellSelectionCollection } from 'vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, ICell, INotebookSearchOptions, ISelectionState, NotebookCellsChangeType, NotebookCellTextModelSplice, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { cellIndexesToRanges, cellRangesToIndexes, ICellRange, reduceCellRanges } from 'vs/workbench/contrib/notebook/common/notebookRange'; -import { NotebookLayoutInfo, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; -import { CellFindMatchModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; +import { CellKind, ICell, INotebookFindOptions, ISelectionState, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookFindScopeType, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { cellIndexesToRanges, cellRangesToIndexes, ICellRange, reduceCellRanges } from 'vs/workbench/contrib/notebook/common/notebookRange'; const invalidFunc = () => { throw new Error(`Invalid change accessor`); }; @@ -99,6 +99,7 @@ let MODEL_ID = 0; export interface NotebookViewModelOptions { isReadOnly: boolean; + inRepl?: boolean; } export class NotebookViewModel extends Disposable implements EditorFoldingStateDelegate, INotebookViewModel { @@ -108,15 +109,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD private readonly _onDidChangeOptions = this._register(new Emitter()); get onDidChangeOptions(): Event { return this._onDidChangeOptions.event; } private _viewCells: CellViewModel[] = []; + private readonly replView: boolean; get viewCells(): ICellViewModel[] { return this._viewCells; } - set viewCells(_: ICellViewModel[]) { - throw new Error('NotebookViewModel.viewCells is readonly'); - } - get length(): number { return this._viewCells.length; } @@ -206,6 +204,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD MODEL_ID++; this.id = '$notebookViewModel' + MODEL_ID; this._instanceId = strings.singleLetterHash(MODEL_ID); + this.replView = !!this.options.inRepl; const compute = (changes: NotebookCellTextModelSplice[], synchronous: boolean) => { const diffs = changes.map(splice => { @@ -337,9 +336,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._onDidChangeSelection.fire(e); })); - this._viewCells = this._notebook.cells.map(cell => { - return createCellViewModel(this._instantiationService, this, cell, this._viewContext); - }); + + const viewCellCount = this.replView ? this._notebook.cells.length - 1 : this._notebook.cells.length; + for (let i = 0; i < viewCellCount; i++) { + this._viewCells.push(createCellViewModel(this._instantiationService, this, this._notebook.cells[i], this._viewContext)); + } + this._viewCells.forEach(cell => { this._handleToViewCellMapping.set(cell.handle, cell); @@ -908,13 +910,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } //#region Find - find(value: string, options: INotebookSearchOptions): CellFindMatchWithIndex[] { + find(value: string, options: INotebookFindOptions): CellFindMatchWithIndex[] { const matches: CellFindMatchWithIndex[] = []; let findCells: CellViewModel[] = []; - const selectedRanges = options.selectedRanges?.map(range => this.validateRange(range)).filter(range => !!range); - - if (options.searchInRanges && selectedRanges) { + if (options.findScope && (options.findScope.findScopeType === NotebookFindScopeType.Cells || options.findScope.findScopeType === NotebookFindScopeType.Text)) { + const selectedRanges = options.findScope.selectedCellRanges?.map(range => this.validateRange(range)).filter(range => !!range) ?? []; const selectedIndexes = cellRangesToIndexes(selectedRanges); findCells = selectedIndexes.map(index => this._viewCells[index]); } else { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index 52e6889c..e82cdf45 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -13,7 +13,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { CellFoldingState, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry'; -import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; +import { NotebookCellOutlineDataSource } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Delayer } from 'vs/base/common/async'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -23,8 +23,7 @@ import { FoldingController } from 'vs/workbench/contrib/notebook/browser/control import { NotebookOptionsChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { NotebookSectionArgs } from 'vs/workbench/contrib/notebook/browser/controller/sectionActions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { INotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory'; -import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; +import { INotebookCellOutlineDataSourceFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory'; export class NotebookStickyLine extends Disposable { constructor( @@ -105,7 +104,9 @@ export class NotebookStickyScroll extends Disposable { private readonly _onDidChangeNotebookStickyScroll = this._register(new Emitter()); readonly onDidChangeNotebookStickyScroll: Event = this._onDidChangeNotebookStickyScroll.event; - private notebookOutlineReference?: IReference; + private notebookCellOutlineReference?: IReference; + + private readonly _layoutDisposableStore = this._register(new DisposableStore()); getDomNode(): HTMLElement { return this.domNode; @@ -144,6 +145,7 @@ export class NotebookStickyScroll extends Disposable { private readonly domNode: HTMLElement, private readonly notebookEditor: INotebookEditor, private readonly notebookCellList: INotebookCellList, + private readonly layoutFn: (delta: number) => void, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { @@ -191,37 +193,37 @@ export class NotebookStickyScroll extends Disposable { this.init(); } else { this._disposables.clear(); - this.notebookOutlineReference?.dispose(); + this.notebookCellOutlineReference?.dispose(); this.disposeCurrentStickyLines(); DOM.clearNode(this.domNode); this.updateDisplay(); } - } else if (e.stickyScrollMode && this.notebookEditor.notebookOptions.getDisplayOptions().stickyScrollEnabled && this.notebookOutlineReference?.object) { - this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.notebookOutlineReference?.object?.entries, this.getCurrentStickyHeight())); + } else if (e.stickyScrollMode && this.notebookEditor.notebookOptions.getDisplayOptions().stickyScrollEnabled && this.notebookCellOutlineReference?.object) { + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, this.notebookCellOutlineReference?.object?.entries, this.getCurrentStickyHeight())); } } private init() { - const { object: notebookOutlineReference } = this.notebookOutlineReference = this.instantiationService.invokeFunction((accessor) => accessor.get(INotebookCellOutlineProviderFactory).getOrCreate(this.notebookEditor, OutlineTarget.OutlinePane)); - this._register(this.notebookOutlineReference); - this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, notebookOutlineReference.entries, this.getCurrentStickyHeight())); + const { object: notebookCellOutline } = this.notebookCellOutlineReference = this.instantiationService.invokeFunction((accessor) => accessor.get(INotebookCellOutlineDataSourceFactory).getOrCreate(this.notebookEditor)); + this._register(this.notebookCellOutlineReference); + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, notebookCellOutline.entries, this.getCurrentStickyHeight())); - this._disposables.add(notebookOutlineReference.onDidChange(() => { - const recompute = computeContent(this.notebookEditor, this.notebookCellList, notebookOutlineReference.entries, this.getCurrentStickyHeight()); + this._disposables.add(notebookCellOutline.onDidChange(() => { + const recompute = computeContent(this.notebookEditor, this.notebookCellList, notebookCellOutline.entries, this.getCurrentStickyHeight()); if (!this.compareStickyLineMaps(recompute, this.currentStickyLines)) { this.updateContent(recompute); } })); this._disposables.add(this.notebookEditor.onDidAttachViewModel(() => { - this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, notebookOutlineReference.entries, this.getCurrentStickyHeight())); + this.updateContent(computeContent(this.notebookEditor, this.notebookCellList, notebookCellOutline.entries, this.getCurrentStickyHeight())); })); this._disposables.add(this.notebookEditor.onDidScroll(() => { const d = new Delayer(100); d.trigger(() => { d.dispose(); - const recompute = computeContent(this.notebookEditor, this.notebookCellList, notebookOutlineReference.entries, this.getCurrentStickyHeight()); + const recompute = computeContent(this.notebookEditor, this.notebookCellList, notebookCellOutline.entries, this.getCurrentStickyHeight()); if (!this.compareStickyLineMaps(recompute, this.currentStickyLines)) { this.updateContent(recompute); } @@ -270,8 +272,16 @@ export class NotebookStickyScroll extends Disposable { const sizeDelta = this.getCurrentStickyHeight() - oldStickyHeight; if (sizeDelta !== 0) { this._onDidChangeNotebookStickyScroll.fire(sizeDelta); + + const d = this._layoutDisposableStore.add(DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.getDomNode()), () => { + this.layoutFn(sizeDelta); + this.updateDisplay(); + + this._layoutDisposableStore.delete(d); + })); + } else { + this.updateDisplay(); } - this.updateDisplay(); } private updateDisplay() { @@ -369,7 +379,7 @@ export class NotebookStickyScroll extends Disposable { override dispose() { this._disposables.dispose(); this.disposeCurrentStickyLines(); - this.notebookOutlineReference?.dispose(); + this.notebookCellOutlineReference?.dispose(); super.dispose(); } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index 6348c0d3..ea1039d6 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -416,7 +416,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { this._renderLabel = this._convertConfiguration(this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel)); this._updateStrategy(); const oldElement = this._notebookLeftToolbar.getElement(); - oldElement.parentElement?.removeChild(oldElement); + oldElement.remove(); this._notebookLeftToolbar.dispose(); this._notebookLeftToolbar = this.instantiationService.createInstance( diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts new file mode 100644 index 00000000..22b678d7 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableListener, EventType, getWindow } from 'vs/base/browser/dom'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isChrome } from 'vs/base/common/platform'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export class NotebookHorizontalTracker extends Disposable { + constructor( + private readonly _notebookEditor: INotebookEditorDelegate, + private readonly _listViewScrollablement: HTMLElement, + ) { + super(); + + this._register(addDisposableListener(this._listViewScrollablement, EventType.MOUSE_WHEEL, (event: IMouseWheelEvent) => { + if (event.deltaX === 0) { + return; + } + + const hoveringOnEditor = this._notebookEditor.codeEditors.find(editor => { + const editorLayout = editor[1].getLayoutInfo(); + if (editorLayout.contentWidth === editorLayout.width) { + // no overflow + return false; + } + + const editorDOM = editor[1].getDomNode(); + if (editorDOM && editorDOM.contains(event.target as HTMLElement)) { + return true; + } + + return false; + }); + + if (!hoveringOnEditor) { + return; + } + + const targetWindow = getWindow(event); + const evt = { + deltaMode: event.deltaMode, + deltaX: event.deltaX, + deltaY: 0, + deltaZ: 0, + wheelDelta: event.wheelDelta && isChrome ? (event.wheelDelta / targetWindow.devicePixelRatio) : event.wheelDelta, + wheelDeltaX: event.wheelDeltaX && isChrome ? (event.wheelDeltaX / targetWindow.devicePixelRatio) : event.wheelDeltaX, + wheelDeltaY: 0, + detail: event.detail, + shiftKey: event.shiftKey, + type: event.type, + defaultPrevented: false, + preventDefault: () => { }, + stopPropagation: () => { } + }; + + (hoveringOnEditor[1] as CodeEditorWidget).delegateScrollFromMouseWheelEvent(evt as any); + })); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts index 25feb7f9..f2d9c334 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts @@ -136,11 +136,15 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { return true; } - const quickPick = this._quickInputService.createQuickPick(); + + const localDisposableStore = new DisposableStore(); + const quickPick = localDisposableStore.add(this._quickInputService.createQuickPick({ useSeparators: true })); const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService); if (quickPickItems.length === 1 && supportAutoRun(quickPickItems[0]) && !skipAutoRun) { - return await this._handleQuickPick(editor, quickPickItems[0], quickPickItems as KernelQuickPickItem[]); + const picked = await this._handleQuickPick(editor, quickPickItems[0], quickPickItems as KernelQuickPickItem[]); + localDisposableStore.dispose(); + return picked; } quickPick.items = quickPickItems; @@ -201,7 +205,7 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { }, this); const pick = await new Promise<{ selected: KernelQuickPickItem | undefined; items: KernelQuickPickItem[] }>((resolve, reject) => { - quickPick.onDidAccept(() => { + localDisposableStore.add(quickPick.onDidAccept(() => { const item = quickPick.selectedItems[0]; if (item) { resolve({ selected: item, items: quickPick.items as KernelQuickPickItem[] }); @@ -210,17 +214,19 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { } quickPick.hide(); - }); + })); - quickPick.onDidHide(() => { + localDisposableStore.add(quickPick.onDidHide(() => { kernelDetectionTaskListener.dispose(); kernelChangeEventListener.dispose(); quickPick.dispose(); resolve({ selected: undefined, items: quickPick.items as KernelQuickPickItem[] }); - }); + })); quickPick.show(); }); + localDisposableStore.dispose(); + if (pick.selected) { return await this._handleQuickPick(editor, pick.selected, pick.items); } @@ -339,7 +345,7 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { private async _showInstallKernelExtensionRecommendation( notebookTextModel: NotebookTextModel, - quickPick: IQuickPick, + quickPick: IQuickPick, extensionWorkbenchService: IExtensionsWorkbenchService, token: CancellationToken ) { @@ -384,13 +390,13 @@ abstract class KernelPickerStrategyBase implements IKernelPickerStrategy { description: suggestedExtension.displayName ?? suggestedExtension.extensionIds.join(', '), label: `$(${Codicon.lightbulb.id}) ` + localize('installSuggestedKernel', 'Install/Enable suggested extensions'), extensionIds: suggestedExtension.extensionIds - } as InstallExtensionPick); + } satisfies InstallExtensionPick); } // there is no kernel, show the install from marketplace quickPickItems.push({ id: 'install', label: localize('searchForKernels', "Browse marketplace for kernel extensions"), - } as SearchMarketplacePick); + } satisfies SearchMarketplacePick); return quickPickItems; } @@ -519,7 +525,7 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { private async displaySelectAnotherQuickPick(editor: IActiveNotebookEditor, kernelListEmpty: boolean): Promise { const notebook: NotebookTextModel = editor.textModel; const disposables = new DisposableStore(); - const quickPick = this._quickInputService.createQuickPick(); + const quickPick = disposables.add(this._quickInputService.createQuickPick({ useSeparators: true })); const quickPickItem = await new Promise(resolve => { // select from kernel sources quickPick.title = kernelListEmpty ? localize('select', "Select Kernel") : localize('selectAnotherKernel', "Select Another Kernel"); @@ -533,13 +539,12 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { resolve(button); } })); - quickPick.onDidTriggerItemButton(async (e) => { - + disposables.add(quickPick.onDidTriggerItemButton(async (e) => { if (isKernelSourceQuickPickItem(e.item) && e.item.documentation !== undefined) { const uri = URI.isUri(e.item.documentation) ? URI.parse(e.item.documentation) : await this._commandService.executeCommand(e.item.documentation); void this._openerService.open(uri, { openExternal: true }); } - }); + })); disposables.add(quickPick.onDidAccept(async () => { resolve(quickPick.selectedItems[0]); })); @@ -699,24 +704,25 @@ export class KernelPickerMRUStrategy extends KernelPickerStrategyBase { private async _selectOneKernel(notebook: NotebookTextModel, source: string, kernels: INotebookKernel[]) { const quickPickItems: QuickPickInput[] = kernels.map(kernel => toKernelQuickPick(kernel, undefined)); - const quickPick = this._quickInputService.createQuickPick(); + const localDisposableStore = new DisposableStore(); + const quickPick = localDisposableStore.add(this._quickInputService.createQuickPick({ useSeparators: true })); quickPick.items = quickPickItems; quickPick.canSelectMany = false; quickPick.title = localize('selectKernelFromExtension', "Select Kernel from {0}", source); - quickPick.onDidAccept(async () => { + localDisposableStore.add(quickPick.onDidAccept(async () => { if (quickPick.selectedItems && quickPick.selectedItems.length > 0 && isKernelPick(quickPick.selectedItems[0])) { await this._selecteKernel(notebook, quickPick.selectedItems[0].kernel); } quickPick.hide(); quickPick.dispose(); - }); + })); - quickPick.onDidHide(() => { - quickPick.dispose(); - }); + localDisposableStore.add(quickPick.onDidHide(() => { + localDisposableStore.dispose(); + })); quickPick.show(); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts index 9856cbf9..19b4df7f 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts @@ -113,9 +113,11 @@ export class ListTopCellToolbar extends Disposable { hiddenItemStrategy: HiddenItemStrategy.Ignore, }); - toolbar.context = { - notebookEditor: this.notebookEditor - }; + if (this.notebookEditor.hasModel()) { + toolbar.context = { + notebookEditor: this.notebookEditor + } satisfies INotebookActionContext; + } this.viewZone.value?.add(toolbar); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 5c373d8b..e83261cf 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -163,6 +163,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { this._versionId = this._textModel.getVersionId(); this._alternativeId = this._textModel.getAlternativeVersionId(); } + this._textBufferHash = null; this._onDidChangeContent.fire('content'); })); @@ -418,6 +419,10 @@ export class NotebookCellTextModel extends Disposable implements ICell { return false; } + if (this.outputs.length !== b.outputs.length) { + return false; + } + if (this.getTextLength() !== b.getTextLength()) { return false; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index b3b6ff6e..e1a875e7 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -17,11 +17,13 @@ import { IModelService } from 'vs/editor/common/services/model'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ITextModel } from 'vs/editor/common/model'; +import { FindMatch, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { isDefined } from 'vs/base/common/types'; import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; - +import { IPosition } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { SearchParams } from 'vs/editor/common/model/textModelSearch'; class StackOperation implements IWorkspaceUndoRedoElement { type: UndoRedoElementType.Workspace; @@ -215,6 +217,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return this._alternativeVersionId; } + get notebookType() { + return this.viewType; + } + constructor( readonly viewType: string, readonly uri: URI, @@ -1169,6 +1175,43 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel private _indexIsInvalid(index: number): boolean { return index < 0 || index >= this._cells.length; } + + //#region Find + findNextMatch(searchString: string, searchStart: { cellIndex: number; position: IPosition }, isRegex: boolean, matchCase: boolean, wordSeparators: string | null): { cell: NotebookCellTextModel; match: FindMatch } | null { + // check if search cell index is valid + this._assertIndex(searchStart.cellIndex); + const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators); + const searchData = searchParams.parseSearchRequest(); + + if (!searchData) { + return null; + } + + let cellIndex = searchStart.cellIndex; + let searchStartPosition = searchStart.position; + + while (cellIndex < this._cells.length) { + const cell = this._cells[cellIndex]; + const searchRange = new Range( + searchStartPosition.lineNumber, + searchStartPosition.column, + cell.textBuffer.getLineCount(), + cell.textBuffer.getLineMaxColumn(cell.textBuffer.getLineCount()) + ); + + const result = cell.textBuffer.findMatchesLineByLine(searchRange, searchData, false, 1); + if (result.length > 0) { + return { cell, match: result[0] }; + } + + // Move to the next cell + cellIndex++; + searchStartPosition = { lineNumber: 1, column: 1 }; // Reset position to start of the next cell + } + + return null; + } + //#endregion } class OutputSequence implements ISequence { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 33c85b27..7ec48d7c 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -8,36 +8,42 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDiffResult } from 'vs/base/common/diff/diff'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { ISplice } from 'vs/base/common/sequence'; +import { ThemeColor } from 'vs/base/common/themables'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { Range } from 'vs/editor/common/core/range'; import { ILineChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { Command, WorkspaceEditMetadata } from 'vs/editor/common/languages'; import { IReadonlyTextBuffer } from 'vs/editor/common/model'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ThemeColor } from 'vs/base/common/themables'; +import { IFileReadLimits } from 'vs/platform/files/common/files'; import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; import { IRevertOptions, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { ICellExecutionError } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { INotebookTextModelLike } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { generateMetadataUri, generate as generateUri, parseMetadataUri, parse as parseUri } from 'vs/workbench/services/notebook/common/notebookDocumentService'; import { IWorkingCopyBackupMeta, IWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { IFileReadLimits } from 'vs/platform/files/common/files'; -import { parse as parseUri, generate as generateUri } from 'vs/workbench/services/notebook/common/notebookDocumentService'; -import { ICellExecutionError } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook'; export const NOTEBOOK_DIFF_EDITOR_ID = 'workbench.editor.notebookTextDiffEditor'; +export const NOTEBOOK_MULTI_DIFF_EDITOR_ID = 'workbench.editor.notebookMultiTextDiffEditor'; export const INTERACTIVE_WINDOW_EDITOR_ID = 'workbench.editor.interactive'; +export const REPL_EDITOR_ID = 'workbench.editor.repl'; +export const EXECUTE_REPL_COMMAND_ID = 'replNotebook.input.execute'; export enum CellKind { Markup = 1, @@ -252,7 +258,8 @@ export interface ICell { onDidChangeInternalMetadata: Event; } -export interface INotebookTextModel { +export interface INotebookTextModel extends INotebookTextModelLike { + readonly notebookType: string; readonly viewType: string; metadata: NotebookDocumentMetadata; readonly transientOptions: TransientOptions; @@ -551,9 +558,18 @@ export interface INotebookContributionData { providerDisplayName: string; displayName: string; filenamePattern: (string | glob.IRelativePattern | INotebookExclusiveDocumentFilter)[]; - exclusive: boolean; + priority?: RegisteredEditorPriority; } +export namespace NotebookUri { + export const scheme = Schemas.vscodeNotebookMetadata; + export function generate(notebook: URI): URI { + return generateMetadataUri(notebook); + } + export function parse(metadata: URI): URI | undefined { + return parseMetadataUri(metadata); + } +} export namespace CellUri { export const scheme = Schemas.vscodeNotebookCell; @@ -776,6 +792,11 @@ export interface INotebookLoadOptions { readonly limits?: IFileReadLimits; } +export type NotebookEditorModelCreationOptions = { + limits?: IFileReadLimits; + scratchpad?: boolean; +}; + export interface IResolvedNotebookEditorModel extends INotebookEditorModel { notebook: NotebookTextModel; } @@ -818,7 +839,7 @@ export enum NotebookEditorPriority { option = 'option', } -export interface INotebookSearchOptions { +export interface INotebookFindOptions { regex?: boolean; wholeWord?: boolean; caseSensitive?: boolean; @@ -827,8 +848,19 @@ export interface INotebookSearchOptions { includeMarkupPreview?: boolean; includeCodeInput?: boolean; includeOutput?: boolean; - searchInRanges?: boolean; - selectedRanges?: ICellRange[]; + findScope?: INotebookFindScope; +} + +export interface INotebookFindScope { + findScopeType: NotebookFindScopeType; + selectedCellRanges?: ICellRange[]; + selectedTextRanges?: Range[]; +} + +export enum NotebookFindScopeType { + Cells = 'cells', + Text = 'text', + None = 'none' } export interface INotebookExclusiveDocumentFilter { @@ -954,7 +986,6 @@ export const NotebookSetting = { outputFontFamilyDeprecated: 'notebook.outputFontFamily', outputFontFamily: 'notebook.output.fontFamily', findFilters: 'notebook.find.filters', - findScope: 'notebook.experimental.find.scope.enabled', logging: 'notebook.logging', confirmDeleteRunningCell: 'notebook.confirmDeleteRunningCell', remoteSaving: 'notebook.experimental.remoteSave', diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts index 4aad255f..e659674d 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { INTERACTIVE_WINDOW_EDITOR_ID, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INTERACTIVE_WINDOW_EDITOR_ID, NOTEBOOK_EDITOR_ID, REPL_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -16,11 +16,15 @@ export const InteractiveWindowOpen = new RawContextKey('interactiveWind // Is Notebook export const NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', NOTEBOOK_EDITOR_ID); export const INTERACTIVE_WINDOW_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', INTERACTIVE_WINDOW_EDITOR_ID); +export const REPL_NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', REPL_EDITOR_ID); // Editor keys +// based on the focus of the notebook editor widget export const NOTEBOOK_EDITOR_FOCUSED = new RawContextKey('notebookEditorFocused', false); +// always true within the cell list html element export const NOTEBOOK_CELL_LIST_FOCUSED = new RawContextKey('notebookCellListFocused', false); export const NOTEBOOK_OUTPUT_FOCUSED = new RawContextKey('notebookOutputFocused', false); +// an input html element within the output webview has focus export const NOTEBOOK_OUTPUT_INPUT_FOCUSED = new RawContextKey('notebookOutputInputFocused', false); export const NOTEBOOK_EDITOR_EDITABLE = new RawContextKey('notebookEditable', true); export const NOTEBOOK_HAS_RUNNING_CELL = new RawContextKey('notebookHasRunningCell', false); @@ -43,6 +47,8 @@ export type NotebookCellExecutionStateContext = 'idle' | 'pending' | 'executing' export const NOTEBOOK_CELL_EXECUTION_STATE = new RawContextKey('notebookCellExecutionState', undefined); export const NOTEBOOK_CELL_EXECUTING = new RawContextKey('notebookCellExecuting', false); // This only exists to simplify a context key expression, see #129625 export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCellHasOutputs', false); +export const NOTEBOOK_CELL_IS_FIRST_OUTPUT = new RawContextKey('notebookCellIsFirstOutput', false); +export const NOTEBOOK_CELL_HAS_HIDDEN_OUTPUTS = new RawContextKey('hasHiddenOutputs', false); export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey('notebookCellInputIsCollapsed', false); export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); export const NOTEBOOK_CELL_RESOURCE = new RawContextKey('notebookCellResource', ''); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 0ac6de71..0536b7b0 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -53,7 +53,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { static readonly ID: string = 'workbench.input.notebook'; - private _editorModelReference: IReference | null = null; + protected editorModelReference: IReference | null = null; private _sideLoadedListener: IDisposable; private _defaultDirtyState: boolean = false; @@ -90,8 +90,15 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { return; } + const reason = e.auto + ? localize('vetoAutoExtHostRestart', "One of the opened editors is a notebook editor.") + : localize('vetoExtHostRestart', "Notebook '{0}' could not be saved.", this.resource.path); + e.veto((async () => { const editors = editorService.findEditors(this); + if (e.auto) { + return true; + } if (editors.length > 0) { const result = await editorService.save(editors[0]); if (result.success) { @@ -99,14 +106,14 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } } return true; // Veto - })(), localize('vetoExtHostRestart', "Notebook '{0}' could not be saved.", this.resource.path)); + })(), reason); })); } override dispose() { this._sideLoadedListener.dispose(); - this._editorModelReference?.dispose(); - this._editorModelReference = null; + this.editorModelReference?.dispose(); + this.editorModelReference = null; super.dispose(); } @@ -125,8 +132,8 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { capabilities |= EditorInputCapabilities.Untitled; } - if (this._editorModelReference) { - if (this._editorModelReference.object.isReadonly()) { + if (this.editorModelReference) { + if (this.editorModelReference.object.isReadonly()) { capabilities |= EditorInputCapabilities.Readonly; } } else { @@ -143,7 +150,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } override getDescription(verbosity = Verbosity.MEDIUM): string | undefined { - if (!this.hasCapability(EditorInputCapabilities.Untitled) || this._editorModelReference?.object.hasAssociatedFilePath()) { + if (!this.hasCapability(EditorInputCapabilities.Untitled) || this.editorModelReference?.object.hasAssociatedFilePath()) { return super.getDescription(verbosity); } @@ -151,21 +158,21 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } override isReadonly(): boolean | IMarkdownString { - if (!this._editorModelReference) { + if (!this.editorModelReference) { return this.filesConfigurationService.isReadonly(this.resource); } - return this._editorModelReference.object.isReadonly(); + return this.editorModelReference.object.isReadonly(); } override isDirty() { - if (!this._editorModelReference) { + if (!this.editorModelReference) { return this._defaultDirtyState; } - return this._editorModelReference.object.isDirty(); + return this.editorModelReference.object.isDirty(); } override isSaving(): boolean { - const model = this._editorModelReference?.object; + const model = this.editorModelReference?.object; if (!model || !model.isDirty() || model.hasErrorState || this.hasCapability(EditorInputCapabilities.Untitled)) { return false; // require the model to be dirty, file-backed and not in an error state } @@ -175,12 +182,12 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } override async save(group: GroupIdentifier, options?: ISaveOptions): Promise { - if (this._editorModelReference) { + if (this.editorModelReference) { if (this.hasCapability(EditorInputCapabilities.Untitled)) { return this.saveAs(group, options); } else { - await this._editorModelReference.object.save(options); + await this.editorModelReference.object.save(options); } return this; @@ -190,7 +197,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { - if (!this._editorModelReference) { + if (!this.editorModelReference) { return undefined; } @@ -200,9 +207,9 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { return undefined; } - const pathCandidate = this.hasCapability(EditorInputCapabilities.Untitled) ? await this._suggestName(provider, this.labelService.getUriBasenameLabel(this.resource)) : this._editorModelReference.object.resource; + const pathCandidate = this.hasCapability(EditorInputCapabilities.Untitled) ? await this._suggestName(provider, this.labelService.getUriBasenameLabel(this.resource)) : this.editorModelReference.object.resource; let target: URI | undefined; - if (this._editorModelReference.object.hasAssociatedFilePath()) { + if (this.editorModelReference.object.hasAssociatedFilePath()) { target = pathCandidate; } else { target = await this._fileDialogService.pickFileToSave(pathCandidate, options?.availableFileSystems); @@ -231,7 +238,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { throw new Error(`File name ${target} is not supported by ${provider.providerDisplayName}.\n\nPlease make sure the file name matches following patterns:\n${patterns}`); } - return await this._editorModelReference.object.saveAs(target); + return await this.editorModelReference.object.saveAs(target); } private async _suggestName(provider: NotebookProviderInfo, suggestedFilename: string) { @@ -260,7 +267,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { // called when users rename a notebook document override async rename(group: GroupIdentifier, target: URI): Promise { - if (this._editorModelReference) { + if (this.editorModelReference) { return { editor: { resource: target }, options: { override: this.viewType } }; } @@ -268,8 +275,8 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } override async revert(_group: GroupIdentifier, options?: IRevertOptions): Promise { - if (this._editorModelReference && this._editorModelReference.object.isDirty()) { - await this._editorModelReference.object.revert(options); + if (this.editorModelReference && this.editorModelReference.object.isDirty()) { + await this.editorModelReference.object.revert(options); } } @@ -284,42 +291,43 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { // "other" loading anymore this._sideLoadedListener.dispose(); - if (!this._editorModelReference) { - const ref = await this._notebookModelResolverService.resolve(this.resource, this.viewType, this.ensureLimits(_options)); - if (this._editorModelReference) { + if (!this.editorModelReference) { + const scratchpad = this.capabilities & EditorInputCapabilities.Scratchpad ? true : false; + const ref = await this._notebookModelResolverService.resolve(this.resource, this.viewType, { limits: this.ensureLimits(_options), scratchpad }); + if (this.editorModelReference) { // Re-entrant, double resolve happened. Dispose the addition references and proceed // with the truth. ref.dispose(); - return (>this._editorModelReference).object; + return (>this.editorModelReference).object; } - this._editorModelReference = ref; + this.editorModelReference = ref; if (this.isDisposed()) { - this._editorModelReference.dispose(); - this._editorModelReference = null; + this.editorModelReference.dispose(); + this.editorModelReference = null; return null; } - this._register(this._editorModelReference.object.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - this._register(this._editorModelReference.object.onDidChangeReadonly(() => this._onDidChangeCapabilities.fire())); - this._register(this._editorModelReference.object.onDidRevertUntitled(() => this.dispose())); - if (this._editorModelReference.object.isDirty()) { + this._register(this.editorModelReference.object.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this._register(this.editorModelReference.object.onDidChangeReadonly(() => this._onDidChangeCapabilities.fire())); + this._register(this.editorModelReference.object.onDidRevertUntitled(() => this.dispose())); + if (this.editorModelReference.object.isDirty()) { this._onDidChangeDirty.fire(); } } else { - this._editorModelReference.object.load({ limits: this.ensureLimits(_options) }); + this.editorModelReference.object.load({ limits: this.ensureLimits(_options) }); } if (this.options._backupId) { - const info = await this._notebookService.withNotebookDataProvider(this._editorModelReference.object.notebook.viewType); + const info = await this._notebookService.withNotebookDataProvider(this.editorModelReference.object.notebook.viewType); if (!(info instanceof SimpleNotebookProviderInfo)) { throw new Error('CANNOT open file notebook with this provider'); } const data = await info.serializer.dataToNotebook(VSBuffer.fromString(JSON.stringify({ __webview_backup: this.options._backupId }))); - this._editorModelReference.object.notebook.applyEdits([ + this.editorModelReference.object.notebook.applyEdits([ { editType: CellEditType.Replace, index: 0, - count: this._editorModelReference.object.notebook.length, + count: this.editorModelReference.object.notebook.length, cells: data.cells } ], true, undefined, () => undefined, undefined, false); @@ -331,7 +339,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } } - return this._editorModelReference.object; + return this.editorModelReference.object; } override toUntyped(): IResourceEditorInput { @@ -348,7 +356,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { return true; } if (otherInput instanceof NotebookEditorInput) { - return this.viewType === otherInput.viewType && isEqual(this.resource, otherInput.resource); + return this.editorId === otherInput.editorId && isEqual(this.resource, otherInput.resource); } return false; } @@ -365,8 +373,8 @@ export function isCompositeNotebookEditorInput(thing: unknown): thing is ICompos && ((thing).editorInputs.every(input => input instanceof NotebookEditorInput)); } -export function isNotebookEditorInput(thing: unknown): thing is NotebookEditorInput { +export function isNotebookEditorInput(thing: EditorInput | undefined): thing is NotebookEditorInput { return !!thing && typeof thing === 'object' - && thing instanceof NotebookEditorInput; + && thing.typeId === NotebookEditorInput.ID; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 3c26eff2..52bd9d3a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -20,6 +20,7 @@ import { IRevertOptions, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/ import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { ICellDto2, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookData, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; import { INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IFileWorkingCopyModelConfiguration, SnapshotContext } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; @@ -197,7 +198,8 @@ export class NotebookFileWorkingCopyModel extends Disposable implements IStoredF private readonly _notebookModel: NotebookTextModel, private readonly _notebookService: INotebookService, private readonly _configurationService: IConfigurationService, - private readonly _telemetryService: ITelemetryService + private readonly _telemetryService: ITelemetryService, + private readonly _notebookLogService: INotebookLoggingService, ) { super(); @@ -237,13 +239,22 @@ export class NotebookFileWorkingCopyModel extends Disposable implements IStoredF } private async setSaveDelegate() { - const serializer = await this.getNotebookSerializer(); - this.save = async (options: IWriteFileOptions, token: CancellationToken) => { - if (token.isCancellationRequested) { - throw new CancellationError(); - } + // make sure we wait for a serializer to resolve before we try to handle saves in the EH + await this.getNotebookSerializer(); + this.save = async (options: IWriteFileOptions, token: CancellationToken) => { try { + let serializer = this._notebookService.tryGetDataProviderSync(this.notebookModel.viewType)?.serializer; + + if (!serializer) { + this._notebookLogService.info('WorkingCopyModel', 'No serializer found for notebook model, checking if provider still needs to be resolved'); + serializer = await this.getNotebookSerializer(); + } + + if (token.isCancellationRequested) { + throw new CancellationError(); + } + const stat = await serializer.save(this._notebookModel.uri, this._notebookModel.versionId, options, token); return stat; } catch (error) { @@ -331,6 +342,8 @@ export class NotebookFileWorkingCopyModel extends Disposable implements IStoredF if (token.isCancellationRequested) { throw new CancellationError(); } + + this._notebookLogService.info('WorkingCopyModel', 'Notebook content updated from file system - ' + this._notebookModel.uri.toString()); this._notebookModel.reset(data.cells, data.metadata, serializer.options); } @@ -358,25 +371,16 @@ export class NotebookFileWorkingCopyModelFactory implements IStoredFileWorkingCo private readonly _viewType: string, @INotebookService private readonly _notebookService: INotebookService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITelemetryService private readonly _telemetryService: ITelemetryService + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @INotebookLoggingService private readonly _notebookLogService: INotebookLoggingService ) { } async createModel(resource: URI, stream: VSBufferReadableStream, token: CancellationToken): Promise { - const info = await this._notebookService.withNotebookDataProvider(this._viewType); - if (!(info instanceof SimpleNotebookProviderInfo)) { - throw new Error('CANNOT open file notebook with this provider'); - } - - const bytes = await streamToBuffer(stream); - const data = await info.serializer.dataToNotebook(bytes); - - if (token.isCancellationRequested) { - throw new CancellationError(); - } + const notebookModel = this._notebookService.getNotebookTextModel(resource) ?? + await this._notebookService.createNotebookTextModel(this._viewType, resource, stream); - const notebookModel = this._notebookService.createNotebookTextModel(info.viewType, resource, data, info.serializer.options); - return new NotebookFileWorkingCopyModel(notebookModel, this._notebookService, this._configurationService, this._telemetryService); + return new NotebookFileWorkingCopyModel(notebookModel, this._notebookService, this._configurationService, this._telemetryService, this._notebookLogService); } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts index 3eff1eb5..8231edfc 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts @@ -5,10 +5,10 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IResolvedNotebookEditorModel, NotebookEditorModelCreationOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IReference } from 'vs/base/common/lifecycle'; import { Event, IWaitUntil } from 'vs/base/common/event'; -import { IFileReadLimits } from 'vs/platform/files/common/files'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; export const INotebookEditorModelResolverService = createDecorator('INotebookModelResolverService'); @@ -50,6 +50,8 @@ export interface INotebookEditorModelResolverService { isDirty(resource: URI): boolean; - resolve(resource: URI, viewType?: string, limits?: IFileReadLimits): Promise>; - resolve(resource: IUntitledNotebookResource, viewType: string, limits?: IFileReadLimits): Promise>; + createUntitledNotebookTextModel(viewType: string): Promise; + + resolve(resource: URI, viewType?: string, creationOptions?: NotebookEditorModelCreationOptions): Promise>; + resolve(resource: IUntitledNotebookResource, viewType: string, creationOtions?: NotebookEditorModelCreationOptions): Promise>; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts index 9d0a90e4..f34fbcae 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts @@ -5,11 +5,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { CellUri, IResolvedNotebookEditorModel, NotebookSetting, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, IResolvedNotebookEditorModel, NotebookEditorModelCreationOptions, NotebookSetting, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModelFactory, SimpleNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { combinedDisposable, DisposableStore, dispose, IDisposable, IReference, ReferenceCollection, toDisposable } from 'vs/base/common/lifecycle'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { ILogService } from 'vs/platform/log/common/log'; import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -23,6 +22,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileReadLimits } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; class NotebookModelReferenceCollection extends ReferenceCollection> { @@ -42,9 +42,9 @@ class NotebookModelReferenceCollection extends ReferenceCollection { + protected async createReferencedObject(key: string, viewType: string, hasAssociatedFilePath: boolean, limits?: IFileReadLimits, isScratchpad?: boolean): Promise { // Untrack as being disposed this.modelsToDispose.delete(key); @@ -70,7 +70,7 @@ class NotebookModelReferenceCollection extends ReferenceCollection>this._instantiationService.createInstance( FileWorkingCopyManager, workingCopyTypeId, @@ -79,8 +79,9 @@ class NotebookModelReferenceCollection extends ReferenceCollection(NotebookSetting.InteractiveWindowPromptToSave) !== true; - const model = this._instantiationService.createInstance(SimpleNotebookEditorModel, uri, hasAssociatedFilePath, viewType, workingCopyManager, scratchPad); + + const isScratchpadView = isScratchpad || (viewType === 'interactive' && this._configurationService.getValue(NotebookSetting.InteractiveWindowPromptToSave) !== true); + const model = this._instantiationService.createInstance(SimpleNotebookEditorModel, uri, hasAssociatedFilePath, viewType, workingCopyManager, isScratchpadView); const result = await model.load({ limits }); @@ -137,7 +138,7 @@ class NotebookModelReferenceCollection extends ReferenceCollection>; - async resolve(resource: IUntitledNotebookResource, viewType: string, limits?: IFileReadLimits): Promise>; - async resolve(arg0: URI | IUntitledNotebookResource, viewType?: string, limits?: IFileReadLimits): Promise> { - let resource: URI; - let hasAssociatedFilePath = false; - if (URI.isUri(arg0)) { - resource = arg0; - } else { - if (!arg0.untitledResource) { - const info = this._notebookService.getContributedNotebookType(assertIsDefined(viewType)); - if (!info) { - throw new Error('UNKNOWN view type: ' + viewType); - } + private createUntitledUri(notebookType: string) { + const info = this._notebookService.getContributedNotebookType(assertIsDefined(notebookType)); + if (!info) { + throw new Error('UNKNOWN notebook type: ' + notebookType); + } - const suffix = NotebookProviderInfo.possibleFileEnding(info.selectors) ?? ''; - for (let counter = 1; ; counter++) { - const candidate = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}${suffix}`, query: viewType }); - if (!this._notebookService.getNotebookTextModel(candidate)) { - resource = candidate; - break; - } - } - } else if (arg0.untitledResource.scheme === Schemas.untitled) { - resource = arg0.untitledResource; - } else { - resource = arg0.untitledResource.with({ scheme: Schemas.untitled }); - hasAssociatedFilePath = true; + const suffix = NotebookProviderInfo.possibleFileEnding(info.selectors) ?? ''; + for (let counter = 1; ; counter++) { + const candidate = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}${suffix}`, query: notebookType }); + if (!this._notebookService.getNotebookTextModel(candidate)) { + return candidate; } } + } - if (resource.scheme === CellUri.scheme) { - throw new Error(`CANNOT open a cell-uri as notebook. Tried with ${resource.toString()}`); + private async validateResourceViewType(uri: URI | undefined, viewType: string | undefined) { + if (!uri && !viewType) { + throw new Error('Must provide at least one of resource or viewType'); } - resource = this._uriIdentService.asCanonicalUri(resource); + if (uri?.scheme === CellUri.scheme) { + throw new Error(`CANNOT open a cell-uri as notebook. Tried with ${uri.toString()}`); + } + + const resource = this._uriIdentService.asCanonicalUri(uri ?? this.createUntitledUri(viewType!)); - const existingViewType = this._notebookService.getNotebookTextModel(resource)?.viewType; + const existingNotebook = this._notebookService.getNotebookTextModel(resource); if (!viewType) { - if (existingViewType) { - viewType = existingViewType; + if (existingNotebook) { + viewType = existingNotebook.viewType; } else { await this._extensionService.whenInstalledExtensionsRegistered(); const providers = this._notebookService.getContributedNotebookTypes(resource); - const exclusiveProvider = providers.find(provider => provider.exclusive); - viewType = exclusiveProvider?.id || providers[0]?.id; + viewType = providers.find(provider => provider.priority === 'exclusive')?.id ?? + providers.find(provider => provider.priority === 'default')?.id ?? + providers[0]?.id; } } @@ -228,9 +220,9 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes throw new Error(`Missing viewType for '${resource}'`); } - if (existingViewType && existingViewType !== viewType) { + if (existingNotebook && existingNotebook.viewType !== viewType) { - await this._onWillFailWithConflict.fireAsync({ resource, viewType }, CancellationToken.None); + await this._onWillFailWithConflict.fireAsync({ resource: resource, viewType }, CancellationToken.None); // check again, listener should have done cleanup const existingViewType2 = this._notebookService.getNotebookTextModel(resource)?.viewType; @@ -238,8 +230,34 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes throw new Error(`A notebook with view type '${existingViewType2}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`); } } + return { resource, viewType }; + } + + public async createUntitledNotebookTextModel(viewType: string) { + const resource = this._uriIdentService.asCanonicalUri(this.createUntitledUri(viewType)); + + return (await this._notebookService.createNotebookTextModel(viewType, resource)); + } + + async resolve(resource: URI, viewType?: string, options?: NotebookEditorModelCreationOptions): Promise>; + async resolve(resource: IUntitledNotebookResource, viewType: string, options: NotebookEditorModelCreationOptions): Promise>; + async resolve(arg0: URI | IUntitledNotebookResource, viewType?: string, options?: NotebookEditorModelCreationOptions): Promise> { + let resource: URI | undefined; + let hasAssociatedFilePath; + if (URI.isUri(arg0)) { + resource = arg0; + } else if (arg0.untitledResource) { + if (arg0.untitledResource.scheme === Schemas.untitled) { + resource = arg0.untitledResource; + } else { + resource = arg0.untitledResource.with({ scheme: Schemas.untitled }); + hasAssociatedFilePath = true; + } + } + + const validated = await this.validateResourceViewType(resource, viewType); - const reference = this._data.acquire(resource.toString(), viewType, hasAssociatedFilePath, limits); + const reference = this._data.acquire(validated.resource.toString(), validated.viewType, hasAssociatedFilePath, options?.limits, options?.scratchpad); try { const model = await reference.object; return { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index be999203..2351e4b4 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -106,7 +106,7 @@ export interface IKernelSourceActionProvider { provideKernelSourceActions(): Promise; } -export interface INotebookTextModelLike { uri: URI; viewType: string } +export interface INotebookTextModelLike { uri: URI; notebookType: string } export const INotebookKernelService = createDecorator('INotebookKernelService'); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookLoggingService.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookLoggingService.ts index 95d716f7..66440b42 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookLoggingService.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookLoggingService.ts @@ -10,5 +10,7 @@ export const INotebookLoggingService = createDecorator( export interface INotebookLoggingService { readonly _serviceBrand: undefined; info(category: string, output: string): void; + warn(category: string, output: string): void; + error(category: string, output: string): void; debug(category: string, output: string): void; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookProvider.ts index 16a9ee3a..e2911dcf 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -19,7 +19,6 @@ export interface NotebookEditorDescriptor { readonly selectors: readonly { filenamePattern?: string; excludeFileNamePattern?: string }[]; readonly priority: RegisteredEditorPriority; readonly providerDisplayName: string; - readonly exclusive: boolean; } export class NotebookProviderInfo { @@ -29,7 +28,6 @@ export class NotebookProviderInfo { readonly displayName: string; readonly priority: RegisteredEditorPriority; readonly providerDisplayName: string; - readonly exclusive: boolean; private _selectors: NotebookSelector[]; get selectors() { @@ -50,7 +48,6 @@ export class NotebookProviderInfo { })) || []; this.priority = descriptor.priority; this.providerDisplayName = descriptor.providerDisplayName; - this.exclusive = descriptor.exclusive; this._options = { transientCellMetadata: {}, transientDocumentMetadata: {}, diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookService.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookService.ts index 7709674b..0f7b65b8 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -12,7 +12,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { VSBuffer } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFileStatWithMetadata, IWriteFileOptions } from 'vs/platform/files/common/files'; import { ITextQuery } from 'vs/workbench/services/search/common/search'; @@ -65,6 +65,7 @@ export interface INotebookService { registerNotebookSerializer(viewType: string, extensionData: NotebookExtensionDescription, serializer: INotebookSerializer): IDisposable; withNotebookDataProvider(viewType: string): Promise; + tryGetDataProviderSync(viewType: string): SimpleNotebookProviderInfo | undefined; getOutputMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[]; @@ -78,7 +79,7 @@ export interface INotebookService { updateMimePreferredRenderer(viewType: string, mimeType: string, rendererId: string, otherMimetypes: readonly string[]): void; saveMimeDisplayOrder(target: ConfigurationTarget): void; - createNotebookTextModel(viewType: string, uri: URI, data: NotebookData, transientOptions: TransientOptions): NotebookTextModel; + createNotebookTextModel(viewType: string, uri: URI, stream?: VSBufferReadableStream): Promise; getNotebookTextModel(uri: URI): NotebookTextModel | undefined; getNotebookTextModels(): Iterable; listNotebookDocuments(): readonly NotebookTextModel[]; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.esm.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.esm.ts new file mode 100644 index 00000000..d288a2eb --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; +import { create } from './notebookSimpleWorker'; + +bootstrapSimpleWorker(create); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts b/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts index b0b8c515..aea29952 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts @@ -6,12 +6,11 @@ import { ISequence, LcsDiff } from 'vs/base/common/diff/diff'; import { doHash, hash, numberHash } from 'vs/base/common/hash'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; import * as model from 'vs/editor/common/model'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IOutputDto, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Range } from 'vs/editor/common/core/range'; -import { INotebookWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerHost'; import { VSBuffer } from 'vs/base/common/buffer'; import { SearchParams } from 'vs/editor/common/model/textModelSearch'; @@ -191,7 +190,7 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable dispose(): void { } - public acceptNewModel(uri: string, data: NotebookData): void { + public $acceptNewModel(uri: string, data: NotebookData): void { this._models[uri] = new MirrorNotebookDocument(URI.parse(uri), data.cells.map(dto => new MirrorCell( (dto as unknown as IMainCellDto).handle, dto.source, @@ -202,19 +201,19 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable )), data.metadata); } - public acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) { + public $acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) { const model = this._models[strURL]; model?.acceptModelChanged(event); } - public acceptRemovedModel(strURL: string): void { + public $acceptRemovedModel(strURL: string): void { if (!this._models[strURL]) { return; } delete this._models[strURL]; } - computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult { + $computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); @@ -276,7 +275,7 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable }; } - canPromptRecommendation(modelUrl: string): boolean { + $canPromptRecommendation(modelUrl: string): boolean { const model = this._getModel(modelUrl); const cells = model.cells; @@ -315,9 +314,9 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable } /** - * Called on the worker side - * @internal + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle */ -export function create(host: INotebookWorkerHost): IRequestHandler { +export function create(workerServer: IWorkerServer): IRequestHandler { return new NotebookEditorSimpleWorker(); } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts new file mode 100644 index 00000000..3fecf4ca --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/NotebookEditorWidgetService.test.ts @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import assert from 'assert'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { GroupIdentifier, IEditorCloseEvent, IEditorWillMoveEvent } from 'vs/workbench/common/editor'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { setupInstantiationService } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; +import { IEditorGroup, IEditorGroupsService, IEditorPart } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +class TestNotebookEditorWidgetService extends NotebookEditorWidgetService { + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(editorGroupService, editorService, contextKeyService, instantiationService); + } + + protected override createWidget(): NotebookEditorWidget { + return new class extends mock() { + override onWillHide = () => { }; + override getDomNode = () => { return { remove: () => { } } as any; }; + override dispose = () => { }; + }; + } +} + +function createNotebookInput(path: string, editorType: string) { + return new class extends mock() { + override resource = URI.parse(path); + override get typeId() { return editorType; } + }; +} + +suite('NotebookEditorWidgetService', () => { + let disposables: DisposableStore; + let instantiationService: TestInstantiationService; + let editorGroup1: IEditorGroup; + let editorGroup2: IEditorGroup; + + let ondidRemoveGroup: Emitter; + let onDidCloseEditor: Emitter; + let onWillMoveEditor: Emitter; + teardown(() => disposables.dispose()); + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { + disposables = new DisposableStore(); + + ondidRemoveGroup = new Emitter(); + onDidCloseEditor = new Emitter(); + onWillMoveEditor = new Emitter(); + + editorGroup1 = new class extends mock() { + override id = 1; + override onDidCloseEditor = onDidCloseEditor.event; + override onWillMoveEditor = onWillMoveEditor.event; + }; + editorGroup2 = new class extends mock() { + override id = 2; + override onDidCloseEditor = Event.None; + override onWillMoveEditor = Event.None; + }; + + instantiationService = setupInstantiationService(disposables); + instantiationService.stub(IEditorGroupsService, new class extends mock() { + override onDidRemoveGroup = ondidRemoveGroup.event; + override onDidAddGroup = Event.None; + override whenReady = Promise.resolve(); + override groups = [editorGroup1, editorGroup2]; + override getPart(group: IEditorGroup | GroupIdentifier): IEditorPart; + override getPart(container: unknown): IEditorPart; + override getPart(container: unknown): import("vs/workbench/services/editor/common/editorGroupsService").IEditorPart { + return { windowId: 0 } as any; + } + }); + instantiationService.stub(IEditorService, new class extends mock() { + override onDidEditorsChange = Event.None; + }); + }); + + test('Retrieve widget within group', async function () { + const notebookEditorInput = createNotebookInput('/test.np', 'type1'); + const notebookEditorService = disposables.add(instantiationService.createInstance(TestNotebookEditorWidgetService)); + const widget = notebookEditorService.retrieveWidget(instantiationService, 1, notebookEditorInput); + const value = widget.value; + const widget2 = notebookEditorService.retrieveWidget(instantiationService, 1, notebookEditorInput); + + assert.notStrictEqual(widget2.value, undefined, 'should create a widget'); + assert.strictEqual(value, widget2.value, 'should return the same widget'); + assert.strictEqual(widget.value, undefined, 'initial borrow should no longer have widget'); + }); + + test('Retrieve independent widgets', async function () { + const inputType1 = createNotebookInput('/test.np', 'type1'); + const inputType2 = createNotebookInput('/test.np', 'type2'); + const notebookEditorService = disposables.add(instantiationService.createInstance(TestNotebookEditorWidgetService)); + const widget = notebookEditorService.retrieveWidget(instantiationService, 1, inputType1); + const widgetDiffGroup = notebookEditorService.retrieveWidget(instantiationService, 2, inputType1); + const widgetDiffType = notebookEditorService.retrieveWidget(instantiationService, 1, inputType2); + + assert.notStrictEqual(widget.value, undefined, 'should create a widget'); + assert.notStrictEqual(widgetDiffGroup.value, undefined, 'should create a widget'); + assert.notStrictEqual(widgetDiffType.value, undefined, 'should create a widget'); + assert.notStrictEqual(widget.value, widgetDiffGroup.value, 'should return a different widget'); + assert.notStrictEqual(widget.value, widgetDiffType.value, 'should return a different widget'); + }); + + test('Only relevant widgets get disposed', async function () { + const inputType1 = createNotebookInput('/test.np', 'type1'); + const inputType2 = createNotebookInput('/test.np', 'type2'); + const notebookEditorService = disposables.add(instantiationService.createInstance(TestNotebookEditorWidgetService)); + const widget = notebookEditorService.retrieveWidget(instantiationService, 1, inputType1); + const widgetDiffType = notebookEditorService.retrieveWidget(instantiationService, 1, inputType2); + const widgetDiffGroup = notebookEditorService.retrieveWidget(instantiationService, 2, inputType1); + + ondidRemoveGroup.fire(editorGroup1); + + assert.strictEqual(widget.value, undefined, 'widgets in group should get disposed'); + assert.strictEqual(widgetDiffType.value, undefined, 'widgets in group should get disposed'); + assert.notStrictEqual(widgetDiffGroup.value, undefined, 'other group should not be disposed'); + }); + + test('Widget should move between groups when editor is moved', async function () { + const inputType1 = createNotebookInput('/test.np', NotebookEditorInput.ID); + const notebookEditorService = disposables.add(instantiationService.createInstance(TestNotebookEditorWidgetService)); + const initialValue = notebookEditorService.retrieveWidget(instantiationService, 1, inputType1).value; + + await new Promise(resolve => setTimeout(resolve, 0)); + + onWillMoveEditor.fire({ + editor: inputType1, + groupId: 1, + target: 2, + }); + + const widgetDiffGroup = notebookEditorService.retrieveWidget(instantiationService, 2, inputType1); + const widgetFirstGroup = notebookEditorService.retrieveWidget(instantiationService, 1, inputType1); + + assert.notStrictEqual(initialValue, undefined, 'valid widget'); + assert.strictEqual(widgetDiffGroup.value, initialValue, 'widget should be reused in new group'); + assert.notStrictEqual(widgetFirstGroup.value, initialValue, 'should create a new widget in the first group'); + }); + +}); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellDecorations.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellDecorations.test.ts index 17fbe22a..7063f688 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellDecorations.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellDecorations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellDnd.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellDnd.test.ts index 0db4fb19..5867385c 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellDnd.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellDnd.test.ts @@ -6,7 +6,7 @@ import { performCellDropEdits } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; -import * as assert from 'assert'; +import assert from 'assert'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts index 357927db..55dec43a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; import { changeCellToKind, computeCellLinesContents, copyCellRange, insertCell, joinNotebookCells, moveCellRange, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -48,7 +48,7 @@ suite('CellOperations', () => { ], async (editor, viewModel) => { viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 0, end: 2 }] }); - await moveCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)! }, 'down'); + await moveCellRange({ notebookEditor: editor }, 'down'); assert.strictEqual(viewModel.cellAt(0)?.getText(), '# header b'); assert.strictEqual(viewModel.cellAt(1)?.getText(), '# header a'); assert.strictEqual(viewModel.cellAt(2)?.getText(), 'var b = 1;'); @@ -74,7 +74,7 @@ suite('CellOperations', () => { editor.setHiddenAreas(viewModel.getHiddenRanges()); viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }); - await moveCellRange({ notebookEditor: editor, cell: viewModel.cellAt(1)! }, 'down'); + await moveCellRange({ notebookEditor: editor }, 'down'); assert.strictEqual(viewModel.cellAt(0)?.getText(), '# header b'); assert.strictEqual(viewModel.cellAt(1)?.getText(), '# header a'); assert.strictEqual(viewModel.cellAt(2)?.getText(), 'var b = 1;'); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellOutput.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellOutput.test.ts new file mode 100644 index 00000000..f08ca269 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/cellOutput.test.ts @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { CellOutputContainer } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput'; +import { CodeCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { CellKind, INotebookRendererInfo, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; +import { FastDomNode } from 'vs/base/browser/fastDomNode'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { mock } from 'vs/base/test/common/mock'; +import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { Event } from 'vs/base/common/event'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; + +suite('CellOutput', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + let instantiationService: TestInstantiationService; + let outputMenus: IMenu[] = []; + + setup(() => { + outputMenus = []; + instantiationService = setupInstantiationService(store); + instantiationService.stub(INotebookService, new class extends mock() { + override getOutputMimeTypeInfo() { + return [{ + rendererId: 'plainTextRendererId', + mimeType: 'text/plain', + isTrusted: true + }, { + rendererId: 'htmlRendererId', + mimeType: 'text/html', + isTrusted: true + }]; + } + override getRendererInfo(): INotebookRendererInfo { + return { + id: 'rendererId', + displayName: 'Stubbed Renderer', + extensionId: { _lower: 'id', value: 'id' }, + } as INotebookRendererInfo; + } + }); + instantiationService.stub(IMenuService, new class extends mock() { + override createMenu() { + const menu = new class extends mock() { + override onDidChange = Event.None; + override getActions() { return []; } + override dispose() { outputMenus = outputMenus.filter(item => item !== menu); } + }; + outputMenus.push(menu); + return menu; + } + }); + }); + + test('Render cell output items with multiple mime types', async function () { + const outputItem = { data: VSBuffer.fromString('output content'), mime: 'text/plain' }; + const htmlOutputItem = { data: VSBuffer.fromString('output content'), mime: 'text/html' }; + const output1: IOutputDto = { outputId: 'abc', outputs: [outputItem, htmlOutputItem] }; + const output2: IOutputDto = { outputId: 'def', outputs: [outputItem, htmlOutputItem] }; + + await withTestNotebook( + [ + ['print(output content)', 'python', CellKind.Code, [output1, output2], {}], + ], + (editor, viewModel, disposables, accessor) => { + + const cell = viewModel.viewCells[0] as CodeCellViewModel; + const cellTemplate = createCellTemplate(disposables); + const output = disposables.add(accessor.createInstance(CellOutputContainer, editor, cell, cellTemplate, { limit: 100 })); + output.render(); + cell.outputsViewModels[0].setVisible(true); + assert.strictEqual(outputMenus.length, 1, 'should have 1 output menus'); + assert(cellTemplate.outputContainer.domNode.style.display !== 'none', 'output container should be visible'); + cell.outputsViewModels[1].setVisible(true); + assert.strictEqual(outputMenus.length, 2, 'should have 2 output menus'); + cell.outputsViewModels[1].setVisible(true); + assert.strictEqual(outputMenus.length, 2, 'should still have 2 output menus'); + }, + instantiationService + ); + }); + + test('One of many cell outputs becomes hidden', async function () { + const outputItem = { data: VSBuffer.fromString('output content'), mime: 'text/plain' }; + const htmlOutputItem = { data: VSBuffer.fromString('output content'), mime: 'text/html' }; + const output1: IOutputDto = { outputId: 'abc', outputs: [outputItem, htmlOutputItem] }; + const output2: IOutputDto = { outputId: 'def', outputs: [outputItem, htmlOutputItem] }; + const output3: IOutputDto = { outputId: 'ghi', outputs: [outputItem, htmlOutputItem] }; + + await withTestNotebook( + [ + ['print(output content)', 'python', CellKind.Code, [output1, output2, output3], {}], + ], + (editor, viewModel, disposables, accessor) => { + + const cell = viewModel.viewCells[0] as CodeCellViewModel; + const cellTemplate = createCellTemplate(disposables); + const output = disposables.add(accessor.createInstance(CellOutputContainer, editor, cell, cellTemplate, { limit: 100 })); + output.render(); + cell.outputsViewModels[0].setVisible(true); + cell.outputsViewModels[1].setVisible(true); + cell.outputsViewModels[2].setVisible(true); + cell.outputsViewModels[1].setVisible(false); + assert(cellTemplate.outputContainer.domNode.style.display !== 'none', 'output container should be visible'); + assert.strictEqual(outputMenus.length, 2, 'should have 2 output menus'); + }, + instantiationService + ); + }); + + +}); + +function createCellTemplate(disposables: DisposableStore) { + return { + outputContainer: new FastDomNode(document.createElement('div')), + outputShowMoreContainer: new FastDomNode(document.createElement('div')), + focusSinkElement: document.createElement('div'), + templateDisposables: disposables, + elementDisposables: disposables, + } as unknown as CodeCellRenderTemplate; +} diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts index fefb3d36..13b2d31e 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/executionStatusBarItem.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/executionStatusBarItem.test.ts index 80931b65..342bbd90 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/executionStatusBarItem.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/executionStatusBarItem.test.ts @@ -8,7 +8,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { formatCellDuration } from 'vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts index c8f3a958..7f9571b9 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, ITextBuffer, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/layoutActions.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/layoutActions.test.ts index 886806af..11a24c15 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/layoutActions.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/layoutActions.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ToggleCellToolbarPositionAction } from 'vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts index ef1c965a..8cb02024 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; @@ -73,7 +73,8 @@ suite('notebookCellDiagnostics', () => { isDefault: true, locations: [ChatAgentLocation.Editor], metadata: {}, - slashCommands: [] + slashCommands: [], + disambiguation: [], }; const chatAgentService = new class extends mock() { override getAgents(): IChatAgentData[] { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts index de3e3c30..501dcb33 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; import { NotebookClipboardContribution, runCopyCells, runCutCells } from 'vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard'; import { CellKind, NOTEBOOK_EDITOR_ID, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts index 9145f0bf..ed6e5f63 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { IFileIconTheme, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -18,6 +18,9 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NotebookCellOutline } from 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; +import { IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor'; suite('Notebook Outline', function () { @@ -32,6 +35,7 @@ suite('Notebook Outline', function () { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); instantiationService.set(IEditorService, new class extends mock() { }); + instantiationService.set(ILanguageFeaturesService, new LanguageFeaturesService()); instantiationService.set(IMarkerService, disposables.add(new MarkerService())); instantiationService.set(IThemeService, new class extends mock() { override onDidFileIconThemeChange = Event.None; @@ -52,6 +56,7 @@ suite('Notebook Outline', function () { return editor; } override onDidChangeModel: Event = Event.None; + override onDidChangeSelection: Event = Event.None; }, OutlineTarget.OutlinePane); disposables.add(outline); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutlineViewProviders.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutlineViewProviders.test.ts index e9d26500..8f4eb76f 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutlineViewProviders.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutlineViewProviders.test.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { IDataSource } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IReference } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ITextModel } from 'vs/editor/common/model'; @@ -14,15 +15,17 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NotebookBreadcrumbsProvider, NotebookCellOutline, NotebookOutlinePaneProvider, NotebookQuickPickProvider } from 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookCellOutlineDataSource } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSource'; import { NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { MockDocumentSymbol } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; -import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; suite('Notebook Outline View Providers', function () { + // #region Setup - ensureNoDisposablesAreLeakedInTestSuite(); + + const store = ensureNoDisposablesAreLeakedInTestSuite(); const configurationService = new TestConfigurationService(); const themeService = new TestThemeService(); @@ -52,8 +55,10 @@ suite('Notebook Outline View Providers', function () { return 0; } }; + // #endregion // #region Helpers + function createCodeCellViewModel(version: number = 1, source = '# code', textmodelId = 'textId') { return { textBuffer: { @@ -75,6 +80,15 @@ suite('Notebook Outline View Providers', function () { } as ICellViewModel; } + function createMockOutlineDataSource(entries: OutlineEntry[], activeElement: OutlineEntry | undefined = undefined) { + return new class extends mock>() { + override object: INotebookCellOutlineDataSource = { + entries: entries, + activeElement: activeElement, + }; + }; + } + function createMarkupCellViewModel(version: number = 1, source = 'markup', textmodelId = 'textId', alternativeId = 1) { return { textBuffer: { @@ -99,7 +113,7 @@ suite('Notebook Outline View Providers', function () { } as ICellViewModel; } - function flatten(element: NotebookCellOutline | OutlineEntry, dataSource: IDataSource): OutlineEntry[] { + function flatten(element: OutlineEntry, dataSource: IDataSource): OutlineEntry[] { const elements: OutlineEntry[] = []; const children = dataSource.getChildren(element); @@ -166,6 +180,7 @@ suite('Notebook Outline View Providers', function () { await configurationService.setUserConfiguration('notebook.gotoSymbols.showAllSymbols', config.quickPickShowAllSymbols); await configurationService.setUserConfiguration('notebook.breadcrumbs.showCodeCells', config.breadcrumbsShowCodeCells); } + // #endregion // #region OutlinePane @@ -199,11 +214,11 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createCodeCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } // Generate filtered outline (view model) - const outlinePaneProvider = new NotebookOutlinePaneProvider(() => [], configurationService); + const outlinePaneProvider = store.add(new NotebookOutlinePaneProvider(undefined, configurationService)); const results = flatten(outlineModel, outlinePaneProvider); // Validate @@ -242,11 +257,11 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createCodeCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } // Generate filtered outline (view model) - const outlinePaneProvider = new NotebookOutlinePaneProvider(() => [], configurationService); + const outlinePaneProvider = store.add(new NotebookOutlinePaneProvider(undefined, configurationService)); const results = flatten(outlineModel, outlinePaneProvider); assert.equal(results.length, 2); @@ -288,11 +303,11 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createCodeCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } // Generate filtered outline (view model) - const outlinePaneProvider = new NotebookOutlinePaneProvider(() => [], configurationService); + const outlinePaneProvider = store.add(new NotebookOutlinePaneProvider(undefined, configurationService)); const results = flatten(outlineModel, outlinePaneProvider); assert.equal(results.length, 1); @@ -331,11 +346,11 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createCodeCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } // Generate filtered outline (view model) - const outlinePaneProvider = new NotebookOutlinePaneProvider(() => [], configurationService); + const outlinePaneProvider = store.add(new NotebookOutlinePaneProvider(undefined, configurationService)); const results = flatten(outlineModel, outlinePaneProvider); assert.equal(results.length, 3); @@ -380,11 +395,11 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createCodeCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } // Generate filtered outline (view model) - const outlinePaneProvider = new NotebookOutlinePaneProvider(() => [], configurationService); + const outlinePaneProvider = store.add(new NotebookOutlinePaneProvider(undefined, configurationService)); const results = flatten(outlineModel, outlinePaneProvider); // validate @@ -439,11 +454,11 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createCodeCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } // Generate filtered outline (view model) - const quickPickProvider = new NotebookQuickPickProvider(() => [...outlineModel.children], configurationService, themeService); + const quickPickProvider = store.add(new NotebookQuickPickProvider(createMockOutlineDataSource([...outlineModel.children]), configurationService, themeService)); const results = quickPickProvider.getQuickPickElements(); // Validate @@ -492,11 +507,11 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createCodeCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } // Generate filtered outline (view model) - const quickPickProvider = new NotebookQuickPickProvider(() => [...outlineModel.children], configurationService, themeService); + const quickPickProvider = store.add(new NotebookQuickPickProvider(createMockOutlineDataSource([...outlineModel.children]), configurationService, themeService)); const results = quickPickProvider.getQuickPickElements(); // Validate @@ -545,11 +560,11 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createCodeCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } // Generate filtered outline (view model) - const quickPickProvider = new NotebookQuickPickProvider(() => [...outlineModel.children], configurationService, themeService); + const quickPickProvider = store.add(new NotebookQuickPickProvider(createMockOutlineDataSource([...outlineModel.children]), configurationService, themeService)); const results = quickPickProvider.getQuickPickElements(); // Validate @@ -601,12 +616,12 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createMarkupCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } const outlineTree = buildOutlineTree([...outlineModel.children]); // Generate filtered outline (view model) - const breadcrumbsProvider = new NotebookBreadcrumbsProvider(() => [...outlineTree![0].children][1], configurationService); + const breadcrumbsProvider = store.add(new NotebookBreadcrumbsProvider(createMockOutlineDataSource([], [...outlineTree![0].children][1]), configurationService)); const results = breadcrumbsProvider.getBreadcrumbElements(); // Validate @@ -652,12 +667,12 @@ suite('Notebook Outline View Providers', function () { // Generate raw outline const outlineModel = new OutlineEntry(-1, -1, createMarkupCellViewModel(), 'fakeRoot', false, false, undefined, undefined); for (const cell of cells) { - entryFactory.getOutlineEntries(cell, OutlineTarget.OutlinePane, 0).forEach(entry => outlineModel.addChild(entry)); + entryFactory.getOutlineEntries(cell, 0).forEach(entry => outlineModel.addChild(entry)); } const outlineTree = buildOutlineTree([...outlineModel.children]); // Generate filtered outline (view model) - const breadcrumbsProvider = new NotebookBreadcrumbsProvider(() => [...outlineTree![0].children][1], configurationService); + const breadcrumbsProvider = store.add(new NotebookBreadcrumbsProvider(createMockOutlineDataSource([], [...outlineTree![0].children][1]), configurationService)); const results = breadcrumbsProvider.getBreadcrumbElements(); // Validate diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts index 90d732a0..dfe5b81e 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -13,7 +13,6 @@ import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBr import { NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { MockDocumentSymbol } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; -import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; suite('Notebook Symbols', function () { ensureNoDisposablesAreLeakedInTestSuite(); @@ -67,7 +66,7 @@ suite('Notebook Symbols', function () { test('Cell without symbols cache', function () { setSymbolsForTextModel([{ name: 'var', range: {} }]); const entryFactory = new NotebookOutlineEntryFactory(executionService); - const entries = entryFactory.getOutlineEntries(createCellViewModel(), OutlineTarget.QuickPick, 0); + const entries = entryFactory.getOutlineEntries(createCellViewModel(), 0); assert.equal(entries.length, 1, 'no entries created'); assert.equal(entries[0].label, '# code', 'entry should fall back to first line of cell'); @@ -79,7 +78,7 @@ suite('Notebook Symbols', function () { const cell = createCellViewModel(); await entryFactory.cacheSymbols(cell, outlineModelService, CancellationToken.None); - const entries = entryFactory.getOutlineEntries(cell, OutlineTarget.QuickPick, 0); + const entries = entryFactory.getOutlineEntries(cell, 0); assert.equal(entries.length, 3, 'wrong number of outline entries'); assert.equal(entries[0].label, '# code'); @@ -101,7 +100,7 @@ suite('Notebook Symbols', function () { const cell = createCellViewModel(); await entryFactory.cacheSymbols(cell, outlineModelService, CancellationToken.None); - const entries = entryFactory.getOutlineEntries(createCellViewModel(), OutlineTarget.QuickPick, 0); + const entries = entryFactory.getOutlineEntries(createCellViewModel(), 0); assert.equal(entries.length, 6, 'wrong number of outline entries'); assert.equal(entries[0].label, '# code'); @@ -127,8 +126,8 @@ suite('Notebook Symbols', function () { await entryFactory.cacheSymbols(cell1, outlineModelService, CancellationToken.None); await entryFactory.cacheSymbols(cell2, outlineModelService, CancellationToken.None); - const entries1 = entryFactory.getOutlineEntries(createCellViewModel(1, '$1'), OutlineTarget.QuickPick, 0); - const entries2 = entryFactory.getOutlineEntries(createCellViewModel(1, '$2'), OutlineTarget.QuickPick, 0); + const entries1 = entryFactory.getOutlineEntries(createCellViewModel(1, '$1'), 0); + const entries2 = entryFactory.getOutlineEntries(createCellViewModel(1, '$2'), 0); assert.equal(entries1.length, 2, 'wrong number of outline entries'); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts index 98628d0c..0770113f 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createNotebookCellList, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/outputCopyTests.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/outputCopyTests.test.ts index 066d89cc..ade4aa5a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/outputCopyTests.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/contrib/outputCopyTests.test.ts @@ -7,7 +7,7 @@ import { ICellOutputViewModel, ICellViewModel } from 'vs/workbench/contrib/noteb import { mock } from 'vs/base/test/common/mock'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ILogService } from 'vs/platform/log/common/log'; -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { copyCellOutput } from 'vs/workbench/contrib/notebook/browser/contrib/clipboard/cellOutputClipboard'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookBrowser.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookBrowser.test.ts index 9b6eb24d..dedeec10 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookBrowser.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookBrowser.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCellAnchor.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCellAnchor.test.ts index 782c8145..b2dfc2be 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCellAnchor.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCellAnchor.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts index 6f9de776..f62c9169 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index d18126e1..d0c5bc4b 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts index 654fe7ee..e7e2ec10 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts @@ -3,15 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ISequence, LcsDiff } from 'vs/base/common/diff/diff'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IDiffResult, ISequence, LcsDiff } from 'vs/base/common/diff/diff'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; +import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IDiffElementViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; -import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor'; +import { INotebookDiffViewModel, INotebookDiffViewModelUpdateEvent } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; +import { NotebookDiffViewModel, prettyChanges } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffViewModel'; import { CellKind, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { withTestNotebookDiffModel } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; class CellSequence implements ISequence { @@ -29,18 +36,44 @@ class CellSequence implements ISequence { } } -suite('NotebookCommon', () => { +suite('NotebookDiff', () => { + let disposables: DisposableStore; + let token: CancellationToken; + let eventDispatcher: NotebookDiffEditorEventDispatcher; + let diffViewModel: NotebookDiffViewModel; + let diffResult: IDiffResult; + let notebookEditorWorkerService: INotebookEditorWorkerService; + teardown(() => disposables.dispose()); + const configurationService = new TestConfigurationService(); ensureNoDisposablesAreLeakedInTestSuite(); + setup(() => { + disposables = new DisposableStore(); + const cancellation = disposables.add(new CancellationTokenSource()); + eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); + token = cancellation.token; + notebookEditorWorkerService = new class extends mock() { + override computeDiff() { return Promise.resolve({ cellsDiff: diffResult }); } + }; + }); + + async function verifyChangeEventIsNotFired(diffViewModel: INotebookDiffViewModel) { + let eventArgs: INotebookDiffViewModelUpdateEvent | undefined = undefined; + disposables.add(diffViewModel.onDidChangeItems(e => eventArgs = e)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(eventArgs, undefined); + } + test('diff different source', async () => { await withTestNotebookDiffModel([ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], ], [ ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], - ], (model, disposables, accessor) => { + ], async (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); + diffResult = diff.ComputeDiff(false); assert.strictEqual(diffResult.changes.length, 1); assert.deepStrictEqual(diffResult.changes.map(change => ({ originalStart: change.originalStart, @@ -54,19 +87,39 @@ suite('NotebookCommon', () => { modifiedLength: 1 }]); - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: diffResult - }, undefined); - assert.strictEqual(diffViewModels.viewModels.length, 1); - assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); - diffViewModels.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items.length, 1); + assert.strictEqual(diffViewModel.items[0].type, 'modified'); + }); + }); + + test('No changes when re-computing diff with the same source', async () => { + await withTestNotebookDiffModel([ + ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], + ], [ + ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], + ], async (model, disposables, accessor) => { + const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); + diffResult = diff.ComputeDiff(false); + assert.strictEqual(diffResult.changes.length, 1); + assert.deepStrictEqual(diffResult.changes.map(change => ({ + originalStart: change.originalStart, + originalLength: change.originalLength, + modifiedStart: change.modifiedStart, + modifiedLength: change.modifiedLength + })), [{ + originalStart: 0, + originalLength: 1, + modifiedStart: 0, + modifiedLength: 1 + }]); + + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + await diffViewModel.computeDiff(token); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); @@ -77,9 +130,9 @@ suite('NotebookCommon', () => { ], [ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], ['', 'javascript', CellKind.Code, [], {}] - ], (model, disposables, accessor) => { + ], async (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); + diffResult = diff.ComputeDiff(false); assert.strictEqual(diffResult.changes.length, 1); assert.deepStrictEqual(diffResult.changes.map(change => ({ originalStart: change.originalStart, @@ -93,21 +146,31 @@ suite('NotebookCommon', () => { modifiedLength: 1 }]); - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: diffResult - }, undefined); - assert.strictEqual(diffViewModels.viewModels.length, 2); - assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); - assert.strictEqual(diffViewModels.viewModels[1].type, 'unchanged'); - - diffViewModels.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + let eventArgs: INotebookDiffViewModelUpdateEvent | undefined = undefined; + disposables.add(diffViewModel.onDidChangeItems(e => eventArgs = e)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items.length, 2); + assert.strictEqual(diffViewModel.items[0].type, 'modified'); + assert.strictEqual(diffViewModel.items[1].type, 'placeholder'); + + + diffViewModel.items[1].showHiddenCells(); + + assert.strictEqual(diffViewModel.items.length, 2); + assert.strictEqual(diffViewModel.items[0].type, 'modified'); + assert.strictEqual(diffViewModel.items[1].type, 'unchanged'); + assert.deepStrictEqual(eventArgs, { start: 1, deleteCount: 1, elements: [diffViewModel.items[1]] }); + + (diffViewModel.items[1] as unknown as SideBySideDiffElementViewModel).hideUnchangedCells(); + + assert.strictEqual(diffViewModel.items.length, 2); + assert.strictEqual(diffViewModel.items[0].type, 'modified'); + assert.strictEqual((diffViewModel.items[1] as IDiffElementViewModelBase).type, 'placeholder'); + assert.deepStrictEqual(eventArgs, { start: 1, deleteCount: 1, elements: [diffViewModel.items[1]] }); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); @@ -116,9 +179,9 @@ suite('NotebookCommon', () => { ['123456789', 'javascript', CellKind.Code, [], {}] ], [ ['987654321', 'javascript', CellKind.Code, [], {}], - ], (model, disposables, accessor) => { + ], async (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); + diffResult = diff.ComputeDiff(false); assert.strictEqual(diffResult.changes.length, 1); assert.deepStrictEqual(diffResult.changes.map(change => ({ originalStart: change.originalStart, @@ -132,20 +195,13 @@ suite('NotebookCommon', () => { modifiedLength: 1 }]); - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: diffResult - }, undefined); - assert.strictEqual(diffViewModels.viewModels.length, 1); - assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); - - diffViewModels.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items.length, 1); + assert.strictEqual(diffViewModel.items[0].type, 'modified'); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); @@ -162,9 +218,9 @@ suite('NotebookCommon', () => { ' \'This version is debugged.\'\n', ' return a * b' ].join(''), 'javascript', CellKind.Code, [], {}], - ], (model, disposables, accessor) => { + ], async (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); + diffResult = diff.ComputeDiff(false); assert.strictEqual(diffResult.changes.length, 1); assert.deepStrictEqual(diffResult.changes.map(change => ({ originalStart: change.originalStart, @@ -178,20 +234,13 @@ suite('NotebookCommon', () => { modifiedLength: 1 }]); - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: diffResult - }, undefined); - assert.strictEqual(diffViewModels.viewModels.length, 1); - assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); - - diffViewModels.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items.length, 1); + assert.strictEqual(diffViewModel.items[0].type, 'modified'); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); @@ -204,25 +253,24 @@ suite('NotebookCommon', () => { [['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([6])) }] }], { metadata: { collapsed: false }, executionOrder: 5 }], [['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([2])) }] }], { metadata: { collapsed: false }, executionOrder: 6 }], ['', 'javascript', CellKind.Code, [], {}] - ], (model, disposables, accessor) => { + ], async (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: diffResult - }, undefined); - assert.strictEqual(diffViewModels.viewModels.length, 3); - assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); - assert.strictEqual(diffViewModels.viewModels[1].type, 'modified'); - assert.strictEqual(diffViewModels.viewModels[2].type, 'unchanged'); - - diffViewModels.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffResult = diff.ComputeDiff(false); + + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + let eventArgs: INotebookDiffViewModelUpdateEvent | undefined = undefined; + disposables.add(diffViewModel.onDidChangeItems(e => eventArgs = e)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items.length, 3); + assert.strictEqual(diffViewModel.items[0].type, 'modified'); + assert.strictEqual(diffViewModel.items[1].type, 'modified'); + assert.strictEqual(diffViewModel.items[2].type, 'placeholder'); + diffViewModel.items[2].showHiddenCells(); + assert.strictEqual(diffViewModel.items[2].type, 'unchanged'); + assert.deepStrictEqual(eventArgs, { start: 2, deleteCount: 1, elements: [diffViewModel.items[2]] }); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); @@ -235,25 +283,26 @@ suite('NotebookCommon', () => { ['This is a test notebook with markdown cells only', 'markdown', CellKind.Markup, [], {}], ['Lorem ipsum dolor sit amet', 'markdown', CellKind.Markup, [], {}], ['In the news', 'markdown', CellKind.Markup, [], {}], - ], (model, disposables, accessor) => { + ], async (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: diffResult - }, undefined); - assert.strictEqual(diffViewModels.viewModels.length, 3); - assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); - assert.strictEqual(diffViewModels.viewModels[1].type, 'unchanged'); - assert.strictEqual(diffViewModels.viewModels[2].type, 'modified'); - - diffViewModels.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffResult = diff.ComputeDiff(false); + + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + let eventArgs: INotebookDiffViewModelUpdateEvent | undefined = undefined; + disposables.add(diffViewModel.onDidChangeItems(e => eventArgs = e)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items.length, 3); + assert.strictEqual(diffViewModel.items[0].type, 'modified'); + assert.strictEqual(diffViewModel.items[1].type, 'placeholder'); + assert.strictEqual(diffViewModel.items[2].type, 'modified'); + + diffViewModel.items[1].showHiddenCells(); + assert.strictEqual(diffViewModel.items[1].type, 'unchanged'); + assert.deepStrictEqual(eventArgs, { start: 1, deleteCount: 1, elements: [diffViewModel.items[1]] }); + + await verifyChangeEventIsNotFired(diffViewModel); + }); }); @@ -265,32 +314,32 @@ suite('NotebookCommon', () => { ['var h = 8;', 'javascript', CellKind.Code, [], {}], ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] - ], (model, disposables, accessor) => { - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffResult = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: { - changes: [{ - originalStart: 0, - originalLength: 0, - modifiedStart: 0, - modifiedLength: 1 - }], - quitEarly: false - } - }, undefined); - - assert.strictEqual(diffResult.firstChangeIndex, 0); - assert.strictEqual(diffResult.viewModels[0].type, 'insert'); - assert.strictEqual(diffResult.viewModels[1].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[2].type, 'unchanged'); - - diffResult.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + ], async (model, disposables, accessor) => { + diffResult = { + changes: [{ + originalStart: 0, + originalLength: 0, + modifiedStart: 0, + modifiedLength: 1 + }], + quitEarly: false + }; + + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + let eventArgs: INotebookDiffViewModelUpdateEvent | undefined = undefined; + disposables.add(diffViewModel.onDidChangeItems(e => eventArgs = e)); + const result = await diffViewModel.computeDiff(token); + + assert.strictEqual(result?.firstChangeIndex, 0); + assert.strictEqual(diffViewModel.items[0].type, 'insert'); + assert.strictEqual(diffViewModel.items[1].type, 'placeholder'); + + diffViewModel.items[1].showHiddenCells(); + assert.strictEqual(diffViewModel.items[1].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[2].type, 'unchanged'); + assert.deepStrictEqual(eventArgs, { start: 1, deleteCount: 1, elements: [diffViewModel.items[1], diffViewModel.items[2]] }); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); @@ -315,40 +364,50 @@ suite('NotebookCommon', () => { ['var g = 7;', 'javascript', CellKind.Code, [], {}], ], async (model, disposables, accessor) => { const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffResult = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: { - changes: [{ - originalStart: 0, - originalLength: 0, - modifiedStart: 0, - modifiedLength: 1 - }, { - originalStart: 0, - originalLength: 6, - modifiedStart: 1, - modifiedLength: 6 - }], - quitEarly: false - } - }, undefined); - - assert.strictEqual(diffResult.firstChangeIndex, 0); - assert.strictEqual(diffResult.viewModels[0].type, 'insert'); - assert.strictEqual(diffResult.viewModels[1].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[2].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[3].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[4].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[5].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[6].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[7].type, 'unchanged'); - - diffResult.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffResult = { + changes: [{ + originalStart: 0, + originalLength: 0, + modifiedStart: 0, + modifiedLength: 1 + }, { + originalStart: 0, + originalLength: 6, + modifiedStart: 1, + modifiedLength: 6 + }], + quitEarly: false + }; + + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + let eventArgs: INotebookDiffViewModelUpdateEvent | undefined = undefined; + disposables.add(diffViewModel.onDidChangeItems(e => eventArgs = e)); + const result = await diffViewModel.computeDiff(token); + + assert.strictEqual(result?.firstChangeIndex, 0); + assert.strictEqual(diffViewModel.items.length, 2); + assert.strictEqual(diffViewModel.items[0].type, 'insert'); + assert.strictEqual(diffViewModel.items[1].type, 'placeholder'); + + diffViewModel.items[1].showHiddenCells(); + assert.strictEqual(diffViewModel.items[1].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[2].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[3].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[4].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[5].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[6].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[7].type, 'unchanged'); + assert.deepStrictEqual(eventArgs, { start: 1, deleteCount: 1, elements: diffViewModel.items.slice(1) }); + + + (diffViewModel.items[1] as unknown as SideBySideDiffElementViewModel).hideUnchangedCells(); + + assert.strictEqual(diffViewModel.items.length, 2); + assert.strictEqual(diffViewModel.items[0].type, 'insert'); + assert.strictEqual((diffViewModel.items[1] as IDiffElementViewModelBase).type, 'placeholder'); + assert.deepStrictEqual(eventArgs, { start: 1, deleteCount: 7, elements: [diffViewModel.items[1]] }); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); @@ -372,36 +431,51 @@ suite('NotebookCommon', () => { ['var f = 6;', 'javascript', CellKind.Code, [], {}], ['var g = 7;', 'javascript', CellKind.Code, [], {}], ], async (model, disposables, accessor) => { - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffResult = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: { - changes: [{ - originalStart: 4, - originalLength: 0, - modifiedStart: 4, - modifiedLength: 1 - }], - quitEarly: false - } - }, undefined); - - // assert.strictEqual(diffResult.firstChangeIndex, 4); - assert.strictEqual(diffResult.viewModels[0].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[1].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[2].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[3].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[4].type, 'insert'); - assert.strictEqual(diffResult.viewModels[5].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[6].type, 'unchanged'); - assert.strictEqual(diffResult.viewModels[7].type, 'unchanged'); - - diffResult.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffResult = { + changes: [{ + originalStart: 4, + originalLength: 0, + modifiedStart: 4, + modifiedLength: 1 + }], + quitEarly: false + }; + + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + let eventArgs: INotebookDiffViewModelUpdateEvent | undefined = undefined; + disposables.add(diffViewModel.onDidChangeItems(e => eventArgs = e)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items[0].type, 'placeholder'); + assert.strictEqual(diffViewModel.items[1].type, 'insert'); + assert.strictEqual(diffViewModel.items[2].type, 'placeholder'); + + diffViewModel.items[0].showHiddenCells(); + assert.strictEqual(diffViewModel.items[0].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[1].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[2].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[3].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[4].type, 'insert'); + assert.strictEqual(diffViewModel.items[5].type, 'placeholder'); + assert.deepStrictEqual(eventArgs, { start: 0, deleteCount: 1, elements: diffViewModel.items.slice(0, 4) }); + + diffViewModel.items[5].showHiddenCells(); + assert.strictEqual((diffViewModel.items[0] as IDiffElementViewModelBase).type, 'unchanged'); + assert.strictEqual(diffViewModel.items[1].type, 'unchanged'); + assert.strictEqual((diffViewModel.items[2] as IDiffElementViewModelBase).type, 'unchanged'); + assert.strictEqual(diffViewModel.items[3].type, 'unchanged'); + assert.strictEqual(diffViewModel.items[4].type, 'insert'); + assert.strictEqual(diffViewModel.items[5].type, 'unchanged'); + assert.deepStrictEqual(eventArgs, { start: 5, deleteCount: 1, elements: diffViewModel.items.slice(5) }); + + (diffViewModel.items[0] as SideBySideDiffElementViewModel).hideUnchangedCells(); + assert.strictEqual((diffViewModel.items[0] as IDiffElementViewModelBase).type, 'placeholder'); + assert.strictEqual(diffViewModel.items[1].type, 'insert'); + assert.strictEqual((diffViewModel.items[2] as IDiffElementViewModelBase).type, 'unchanged'); + assert.deepStrictEqual(eventArgs, { start: 0, deleteCount: 4, elements: diffViewModel.items.slice(0, 1) }); + + await verifyChangeEventIsNotFired(diffViewModel); + }); }); @@ -458,7 +532,7 @@ suite('NotebookCommon', () => { ], async (model) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); - NotebookTextDiffEditor.prettyChanges(model, diffResult); + prettyChanges(model, diffResult); assert.deepStrictEqual(diffResult.changes.map(change => ({ originalStart: change.originalStart, @@ -510,7 +584,7 @@ suite('NotebookCommon', () => { ], async (model) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); - NotebookTextDiffEditor.prettyChanges(model, diffResult); + prettyChanges(model, diffResult); assert.deepStrictEqual(diffResult.changes.map(change => ({ originalStart: change.originalStart, @@ -533,25 +607,20 @@ suite('NotebookCommon', () => { ], [ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([5])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], - ], (model, disposables, accessor) => { + ], async (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: diffResult - }, undefined); - assert.strictEqual(diffViewModels.viewModels.length, 2); - assert.strictEqual(diffViewModels.viewModels[0].type, 'unchanged'); - assert.strictEqual(diffViewModels.viewModels[0].checkIfOutputsModified(), false); - assert.strictEqual(diffViewModels.viewModels[1].type, 'modified'); - - diffViewModels.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffResult = diff.ComputeDiff(false); + + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items.length, 2); + assert.strictEqual(diffViewModel.items[0].type, 'placeholder'); + diffViewModel.items[0].showHiddenCells(); + assert.strictEqual((diffViewModel.items[0] as unknown as SideBySideDiffElementViewModel).checkIfOutputsModified(), false); + assert.strictEqual(diffViewModel.items[1].type, 'modified'); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); @@ -562,23 +631,20 @@ suite('NotebookCommon', () => { ], [ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([5])) }] }], { metadata: { collapsed: false }, executionOrder: 3 }], - ], (model, disposables, accessor) => { + ], async (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); - const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); - const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { - cellsDiff: diffResult - }, undefined); - assert.strictEqual(diffViewModels.viewModels.length, 2); - assert.strictEqual(diffViewModels.viewModels[0].original!.textModel.equal(diffViewModels.viewModels[0].modified!.textModel), true); - assert.strictEqual(diffViewModels.viewModels[1].original!.textModel.equal(diffViewModels.viewModels[1].modified!.textModel), false); - diffViewModels.viewModels.forEach(vm => { - vm.original?.dispose(); - vm.modified?.dispose(); - vm.dispose(); - }); - model.original.notebook.dispose(); - model.modified.notebook.dispose(); + diffResult = diff.ComputeDiff(false); + + diffViewModel = disposables.add(new NotebookDiffViewModel(model, notebookEditorWorkerService, accessor, configurationService, eventDispatcher, accessor.get(INotebookService), undefined)); + await diffViewModel.computeDiff(token); + + assert.strictEqual(diffViewModel.items.length, 2); + assert.strictEqual(diffViewModel.items[0].type, 'placeholder'); + diffViewModel.items[0].showHiddenCells(); + assert.strictEqual((diffViewModel.items[0] as unknown as SideBySideDiffElementViewModel).original!.textModel.equal((diffViewModel.items[0] as any).modified!.textModel), true); + assert.strictEqual((diffViewModel.items[1] as any).original!.textModel.equal((diffViewModel.items[1] as any).modified!.textModel), false); + + await verifyChangeEventIsNotFired(diffViewModel); }); }); }); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts index 1bd5ca8f..8bfdf049 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts index 071017d8..a7849fd7 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -15,6 +15,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ILogService } from 'vs/platform/log/common/log'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellKind, IOutputDto, NotebookData, NotebookSetting, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -28,7 +29,10 @@ suite('NotebookFileWorkingCopyModel', function () { let disposables: DisposableStore; let instantiationService: TestInstantiationService; const configurationService = new TestConfigurationService(); - const telemetryService = new class extends mock() { }; + const telemetryService = new class extends mock() { + override publicLogError2() { } + }; + const logservice = new class extends mock() { }; teardown(() => disposables.dispose()); @@ -65,7 +69,8 @@ suite('NotebookFileWorkingCopyModel', function () { } ), configurationService, - telemetryService + telemetryService, + logservice )); await model.snapshot(SnapshotContext.Save, CancellationToken.None); @@ -88,7 +93,8 @@ suite('NotebookFileWorkingCopyModel', function () { } ), configurationService, - telemetryService + telemetryService, + logservice )); await model.snapshot(SnapshotContext.Save, CancellationToken.None); assert.strictEqual(callCount, 1); @@ -123,7 +129,8 @@ suite('NotebookFileWorkingCopyModel', function () { } ), configurationService, - telemetryService + telemetryService, + logservice )); await model.snapshot(SnapshotContext.Save, CancellationToken.None); @@ -147,6 +154,7 @@ suite('NotebookFileWorkingCopyModel', function () { ), configurationService, telemetryService, + logservice )); await model.snapshot(SnapshotContext.Save, CancellationToken.None); @@ -181,7 +189,8 @@ suite('NotebookFileWorkingCopyModel', function () { } ), configurationService, - telemetryService + telemetryService, + logservice )); await model.snapshot(SnapshotContext.Save, CancellationToken.None); @@ -204,7 +213,8 @@ suite('NotebookFileWorkingCopyModel', function () { } ), configurationService, - telemetryService + telemetryService, + logservice )); await model.snapshot(SnapshotContext.Save, CancellationToken.None); assert.strictEqual(callCount, 1); @@ -239,7 +249,8 @@ suite('NotebookFileWorkingCopyModel', function () { } ), configurationService, - telemetryService + telemetryService, + logservice )); try { @@ -282,7 +293,8 @@ suite('NotebookFileWorkingCopyModel', function () { notebook, notebookService, configurationService, - telemetryService + telemetryService, + logservice )); // the save method should not be set if the serializer is not yet resolved @@ -299,11 +311,25 @@ suite('NotebookFileWorkingCopyModel', function () { function mockNotebookService(notebook: NotebookTextModel, notebookSerializer: Promise | INotebookSerializer) { return new class extends mock() { + private serializer: INotebookSerializer | undefined = undefined; override async withNotebookDataProvider(viewType: string): Promise { - const serializer = await notebookSerializer; + this.serializer = await notebookSerializer; + return new SimpleNotebookProviderInfo( + notebook.viewType, + this.serializer, + { + id: new ExtensionIdentifier('test'), + location: undefined + } + ); + } + override tryGetDataProviderSync(viewType: string): SimpleNotebookProviderInfo | undefined { + if (!this.serializer) { + return undefined; + } return new SimpleNotebookProviderInfo( notebook.viewType, - serializer, + this.serializer, { id: new ExtensionIdentifier('test'), location: undefined diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index c4b00528..9a3bfdc3 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { AsyncIterableObject } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -11,7 +11,7 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; -import { assertThrowsAsync } from 'vs/base/test/common/utils'; +import { assertThrowsAsync, ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -26,6 +26,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernel, INotebookKernelHistoryService, INotebookKernelService, INotebookTextModelLike, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -40,6 +41,8 @@ suite('NotebookExecutionService', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + setup(function () { disposables = new DisposableStore(); @@ -52,6 +55,12 @@ suite('NotebookExecutionService', () => { override getNotebookTextModels() { return []; } }); + instantiationService.stub(INotebookLoggingService, new class extends mock() { + override debug(category: string, output: string): void { + // + } + }); + instantiationService.stub(IMenuService, new class extends mock() { override createMenu() { return new class extends mock() { @@ -80,8 +89,8 @@ suite('NotebookExecutionService', () => { contextKeyService = instantiationService.get(IContextKeyService); }); - async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel) => void | Promise) { - return _withTestNotebook(cells, (editor, viewModel) => callback(viewModel, viewModel.notebookDocument)); + async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel, disposables: DisposableStore) => void | Promise) { + return _withTestNotebook(cells, (editor, viewModel, disposables) => callback(viewModel, viewModel.notebookDocument, disposables)); } // test('ctor', () => { @@ -94,7 +103,7 @@ suite('NotebookExecutionService', () => { test('cell is not runnable when no kernel is selected', async () => { await withTestNotebook( [], - async (viewModel, textModel) => { + async (viewModel, textModel, disposables) => { const executionService = instantiationService.createInstance(NotebookExecutionService); const cell = insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); @@ -107,9 +116,9 @@ suite('NotebookExecutionService', () => { [], async (viewModel, textModel) => { - kernelService.registerKernel(new TestNotebookKernel({ languages: ['testlang'] })); - const executionService = instantiationService.createInstance(NotebookExecutionService); - const cell = insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); + disposables.add(kernelService.registerKernel(new TestNotebookKernel({ languages: ['testlang'] }))); + const executionService = disposables.add(instantiationService.createInstance(NotebookExecutionService)); + const cell = disposables.add(insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); await assertThrowsAsync(async () => await executionService.executeNotebookCells(textModel, [cell.model], contextKeyService)); }); @@ -120,13 +129,13 @@ suite('NotebookExecutionService', () => { [], async (viewModel, textModel) => { const kernel = new TestNotebookKernel({ languages: ['javascript'] }); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, textModel); - const executionService = instantiationService.createInstance(NotebookExecutionService); + const executionService = disposables.add(instantiationService.createInstance(NotebookExecutionService)); const executeSpy = sinon.spy(); kernel.executeNotebookCellsRequest = executeSpy; - const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); + const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); await executionService.executeNotebookCells(viewModel.notebookDocument, [cell.model], contextKeyService); assert.strictEqual(executeSpy.calledOnce, true); }); @@ -148,12 +157,12 @@ suite('NotebookExecutionService', () => { } }; - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, textModel); - const executionService = instantiationService.createInstance(NotebookExecutionService); + const executionService = disposables.add(instantiationService.createInstance(NotebookExecutionService)); const exeStateService = instantiationService.get(INotebookExecutionStateService); - const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); + const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); await executionService.executeNotebookCells(textModel, [cell.model], contextKeyService); assert.strictEqual(didExecute, true); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 7087aa9e..0a1fe3ad 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { AsyncIterableObject, DeferredPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -24,6 +25,7 @@ import { CellEditType, CellKind, CellUri, IOutputDto, NotebookCellMetadata, Note import { CellExecutionUpdateType, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernel, INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -38,6 +40,8 @@ suite('NotebookExecutionStateService', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + setup(function () { disposables = new DisposableStore(); @@ -62,6 +66,11 @@ suite('NotebookExecutionStateService', () => { }; } }); + instantiationService.stub(INotebookLoggingService, new class extends mock() { + override debug(category: string, output: string): void { + // + } + }); kernelService = disposables.add(instantiationService.createInstance(NotebookKernelService)); instantiationService.set(INotebookKernelService, kernelService); @@ -69,12 +78,12 @@ suite('NotebookExecutionStateService', () => { instantiationService.set(INotebookExecutionStateService, disposables.add(instantiationService.createInstance(NotebookExecutionStateService))); }); - async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel) => void | Promise) { - return _withTestNotebook(cells, (editor, viewModel) => callback(viewModel, viewModel.notebookDocument)); + async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel, disposables: DisposableStore) => void | Promise) { + return _withTestNotebook(cells, (editor, viewModel) => callback(viewModel, viewModel.notebookDocument, disposables)); } function testCancelOnDelete(expectedCancels: number, implementsInterrupt: boolean) { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; let cancels = 0; @@ -91,15 +100,15 @@ suite('NotebookExecutionStateService', () => { cancels += handles.length; } }; - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); // Should cancel executing and pending cells, when kernel does not implement interrupt - const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); - const cell2 = insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); - const cell3 = insertCellAtIndex(viewModel, 2, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); + const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); + const cell2 = disposables.add(insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); + const cell3 = disposables.add(insertCellAtIndex(viewModel, 2, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); insertCellAtIndex(viewModel, 3, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); // Not deleted const exe = executionStateService.createCellExecution(viewModel.uri, cell.handle); // Executing exe.confirm(); @@ -126,11 +135,11 @@ suite('NotebookExecutionStateService', () => { }); test('fires onDidChangeCellExecution when cell is completed while deleted', async function () { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; const kernel = new TestNotebookKernel(); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); @@ -153,15 +162,15 @@ suite('NotebookExecutionStateService', () => { }); test('does not fire onDidChangeCellExecution for output updates', async function () { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; const kernel = new TestNotebookKernel(); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); - const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); + const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); const exe = executionStateService.createCellExecution(viewModel.uri, cell.handle); let didFire = false; @@ -181,15 +190,15 @@ suite('NotebookExecutionStateService', () => { // #142466 test('getCellExecution and onDidChangeCellExecution', async function () { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; const kernel = new TestNotebookKernel(); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); - const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); + const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); const deferred = new DeferredPromise(); disposables.add(executionStateService.onDidChangeExecution(e => { @@ -213,11 +222,11 @@ suite('NotebookExecutionStateService', () => { }); }); test('getExecution and onDidChangeExecution', async function () { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; const kernel = new TestNotebookKernel(); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const eventRaisedWithExecution: boolean[] = []; @@ -243,11 +252,11 @@ suite('NotebookExecutionStateService', () => { }); test('getExecution and onDidChangeExecution 2', async function () { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; const kernel = new TestNotebookKernel(); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); @@ -283,15 +292,15 @@ suite('NotebookExecutionStateService', () => { }); test('force-cancel works for Cell Execution', async function () { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; const kernel = new TestNotebookKernel(); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); - const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); + const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true)); executionStateService.createCellExecution(viewModel.uri, cell.handle); const exe = executionStateService.getCellExecution(cell.uri); assert.ok(exe); @@ -302,11 +311,11 @@ suite('NotebookExecutionStateService', () => { }); }); test('force-cancel works for Notebook Execution', async function () { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; const kernel = new TestNotebookKernel(); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const eventRaisedWithExecution: boolean[] = []; @@ -324,11 +333,11 @@ suite('NotebookExecutionStateService', () => { }); }); test('force-cancel works for Cell and Notebook Execution', async function () { - return withTestNotebook([], async viewModel => { + return withTestNotebook([], async (viewModel, _document, disposables) => { testNotebookModel = viewModel.notebookDocument; const kernel = new TestNotebookKernel(); - kernelService.registerKernel(kernel); + disposables.add(kernelService.registerKernel(kernel)); kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument); const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts index 4acfd482..0d397b63 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts index a476f5ca..a037768b 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; +import { setupInstantiationService } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { Emitter, Event } from 'vs/base/common/event'; import { INotebookKernel, INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl'; @@ -66,8 +66,8 @@ suite('NotebookKernelHistoryService', () => { const u1 = URI.parse('foo:///one'); - const k1 = new TestNotebookKernel({ label: 'z', viewType: 'foo' }); - const k2 = new TestNotebookKernel({ label: 'a', viewType: 'foo' }); + const k1 = new TestNotebookKernel({ label: 'z', notebookType: 'foo' }); + const k2 = new TestNotebookKernel({ label: 'a', notebookType: 'foo' }); disposables.add(kernelService.registerKernel(k1)); disposables.add(kernelService.registerKernel(k2)); @@ -102,14 +102,14 @@ suite('NotebookKernelHistoryService', () => { const kernelHistoryService = disposables.add(instantiationService.createInstance(NotebookKernelHistoryService)); - let info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); + let info = kernelHistoryService.getKernels({ uri: u1, notebookType: 'foo' }); assert.equal(info.all.length, 0); assert.ok(!info.selected); // update priorities for u1 notebook kernelService.updateKernelNotebookAffinity(k2, u1, 2); - info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); + info = kernelHistoryService.getKernels({ uri: u1, notebookType: 'foo' }); assert.equal(info.all.length, 0); // MRU only auto selects kernel if there is only one assert.deepStrictEqual(info.selected, undefined); @@ -119,9 +119,9 @@ suite('NotebookKernelHistoryService', () => { const u1 = URI.parse('foo:///one'); - const k1 = new TestNotebookKernel({ label: 'z', viewType: 'foo' }); - const k2 = new TestNotebookKernel({ label: 'a', viewType: 'foo' }); - const k3 = new TestNotebookKernel({ label: 'b', viewType: 'foo' }); + const k1 = new TestNotebookKernel({ label: 'z', notebookType: 'foo' }); + const k2 = new TestNotebookKernel({ label: 'a', notebookType: 'foo' }); + const k3 = new TestNotebookKernel({ label: 'b', notebookType: 'foo' }); disposables.add(kernelService.registerKernel(k1)); disposables.add(kernelService.registerKernel(k2)); @@ -158,12 +158,12 @@ suite('NotebookKernelHistoryService', () => { }); const kernelHistoryService = disposables.add(instantiationService.createInstance(NotebookKernelHistoryService)); - let info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); + let info = kernelHistoryService.getKernels({ uri: u1, notebookType: 'foo' }); assert.equal(info.all.length, 1); assert.deepStrictEqual(info.selected, undefined); kernelHistoryService.addMostRecentKernel(k3); - info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); + info = kernelHistoryService.getKernels({ uri: u1, notebookType: 'foo' }); assert.deepStrictEqual(info.all, [k3, k2]); }); }); @@ -190,9 +190,9 @@ class TestNotebookKernel implements INotebookKernel { return AsyncIterableObject.EMPTY; } - constructor(opts?: { languages?: string[]; label?: string; viewType?: string }) { + constructor(opts?: { languages?: string[]; label?: string; notebookType?: string }) { this.supportedLanguages = opts?.languages ?? [PLAINTEXT_LANGUAGE_ID]; this.label = opts?.label ?? this.label; - this.viewType = opts?.viewType ?? this.viewType; + this.viewType = opts?.notebookType ?? this.viewType; } } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index 1ed657a1..b05cd81e 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; +import { setupInstantiationService } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { Emitter, Event } from 'vs/base/common/event'; import { INotebookKernel, INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl'; @@ -72,7 +72,7 @@ suite('NotebookKernelService', () => { disposables.add(kernelService.registerKernel(k2)); // equal priorities -> sort by name - let info = kernelService.getMatchingKernel({ uri: u1, viewType: 'foo' }); + let info = kernelService.getMatchingKernel({ uri: u1, notebookType: 'foo' }); assert.ok(info.all[0] === k2); assert.ok(info.all[1] === k1); @@ -81,18 +81,18 @@ suite('NotebookKernelService', () => { kernelService.updateKernelNotebookAffinity(k2, u2, 1); // updated - info = kernelService.getMatchingKernel({ uri: u1, viewType: 'foo' }); + info = kernelService.getMatchingKernel({ uri: u1, notebookType: 'foo' }); assert.ok(info.all[0] === k2); assert.ok(info.all[1] === k1); // NOT updated - info = kernelService.getMatchingKernel({ uri: u2, viewType: 'foo' }); + info = kernelService.getMatchingKernel({ uri: u2, notebookType: 'foo' }); assert.ok(info.all[0] === k2); assert.ok(info.all[1] === k1); // reset kernelService.updateKernelNotebookAffinity(k2, u1, undefined); - info = kernelService.getMatchingKernel({ uri: u1, viewType: 'foo' }); + info = kernelService.getMatchingKernel({ uri: u1, notebookType: 'foo' }); assert.ok(info.all[0] === k2); assert.ok(info.all[1] === k1); }); @@ -103,18 +103,18 @@ suite('NotebookKernelService', () => { const kernel = new TestNotebookKernel(); disposables.add(kernelService.registerKernel(kernel)); - let info = kernelService.getMatchingKernel({ uri: notebook, viewType: 'foo' }); + let info = kernelService.getMatchingKernel({ uri: notebook, notebookType: 'foo' }); assert.strictEqual(info.all.length, 1); assert.ok(info.all[0] === kernel); const betterKernel = new TestNotebookKernel(); disposables.add(kernelService.registerKernel(betterKernel)); - info = kernelService.getMatchingKernel({ uri: notebook, viewType: 'foo' }); + info = kernelService.getMatchingKernel({ uri: notebook, notebookType: 'foo' }); assert.strictEqual(info.all.length, 2); kernelService.updateKernelNotebookAffinity(betterKernel, notebook, 2); - info = kernelService.getMatchingKernel({ uri: notebook, viewType: 'foo' }); + info = kernelService.getMatchingKernel({ uri: notebook, notebookType: 'foo' }); assert.strictEqual(info.all.length, 2); assert.ok(info.all[0] === betterKernel); assert.ok(info.all[1] === kernel); @@ -123,8 +123,8 @@ suite('NotebookKernelService', () => { test('onDidChangeSelectedNotebooks not fired on initial notebook open #121904', function () { const uri = URI.parse('foo:///one'); - const jupyter = { uri, viewType: 'jupyter' }; - const dotnet = { uri, viewType: 'dotnet' }; + const jupyter = { uri, viewType: 'jupyter', notebookType: 'jupyter' }; + const dotnet = { uri, viewType: 'dotnet', notebookType: 'dotnet' }; const jupyterKernel = new TestNotebookKernel({ viewType: jupyter.viewType }); const dotnetKernel = new TestNotebookKernel({ viewType: dotnet.viewType }); @@ -144,8 +144,8 @@ suite('NotebookKernelService', () => { test('onDidChangeSelectedNotebooks not fired on initial notebook open #121904, p2', async function () { const uri = URI.parse('foo:///one'); - const jupyter = { uri, viewType: 'jupyter' }; - const dotnet = { uri, viewType: 'dotnet' }; + const jupyter = { uri, viewType: 'jupyter', notebookType: 'jupyter' }; + const dotnet = { uri, viewType: 'dotnet', notebookType: 'dotnet' }; const jupyterKernel = new TestNotebookKernel({ viewType: jupyter.viewType }); const dotnetKernel = new TestNotebookKernel({ viewType: dotnet.viewType }); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookRendererMessagingService.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookRendererMessagingService.test.ts index db9868ed..d40658ab 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookRendererMessagingService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookRendererMessagingService.test.ts @@ -6,7 +6,7 @@ import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { stub } from 'sinon'; import { NotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/browser/services/notebookRendererMessagingServiceImpl'; -import * as assert from 'assert'; +import assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts index 5bbbf50a..0fd1c9a9 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts index 647c9630..77ce4841 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -56,7 +56,6 @@ suite('NotebookProviderInfoStore', function () { displayName: 'foo', selectors: [{ filenamePattern: '*.foo' }], priority: RegisteredEditorPriority.default, - exclusive: false, providerDisplayName: 'foo', }); const barInfo = new NotebookProviderInfo({ @@ -65,7 +64,6 @@ suite('NotebookProviderInfoStore', function () { displayName: 'bar', selectors: [{ filenamePattern: '*.bar' }], priority: RegisteredEditorPriority.default, - exclusive: false, providerDisplayName: 'bar', }); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts index 0cb027d1..f8dae486 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts @@ -3,13 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; import { assertSnapshot } from 'vs/base/test/common/snapshot'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor'; import { NotebookCellOutline } from 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; import { INotebookEditor, INotebookEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; @@ -29,23 +32,25 @@ suite('NotebookEditorStickyScroll', () => { disposables.dispose(); }); - ensureNoDisposablesAreLeakedInTestSuite(); + const store = ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); + instantiationService.set(ILanguageFeaturesService, new LanguageFeaturesService()); }); function getOutline(editor: any) { if (!editor.hasModel()) { assert.ok(false, 'MUST have active text editor'); } - const outline = instantiationService.createInstance(NotebookCellOutline, new class extends mock() { + const outline = store.add(instantiationService.createInstance(NotebookCellOutline, new class extends mock() { override getControl() { return editor; } override onDidChangeModel: Event = Event.None; - }, OutlineTarget.QuickPick); + override onDidChangeSelection: Event = Event.None; + }, OutlineTarget.QuickPick)); return outline; } diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts index 4c250a43..8300d36d 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; @@ -1403,4 +1403,112 @@ suite('NotebookTextModel', () => { }); }); + + test('findNextMatch', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}], + ['var d = 4;', 'javascript', CellKind.Code, [], {}] + ], + (editor, viewModel) => { + const notebookModel = viewModel.notebookDocument; + + // Test case 1: Find 'var' starting from the first cell + let findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 1); + + // Test case 2: Find 'b' starting from the second cell + findMatch = notebookModel.findNextMatch('b', { cellIndex: 1, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 5); + + // Test case 3: Find 'c' starting from the third cell + findMatch = notebookModel.findNextMatch('c', { cellIndex: 2, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 5); + + // Test case 4: Find 'd' starting from the fourth cell + findMatch = notebookModel.findNextMatch('d', { cellIndex: 3, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 5); + + // Test case 5: No match found + findMatch = notebookModel.findNextMatch('e', { cellIndex: 0, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.strictEqual(findMatch, null); + } + ); + }); + + test('findNextMatch 2', async function () { + await withTestNotebook( + [ + ['var a = 1; var a = 2;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}], + ['var d = 4;', 'javascript', CellKind.Code, [], {}] + ], + (editor, viewModel) => { + const notebookModel = viewModel.notebookDocument; + + // Test case 1: Find 'var' starting from the first cell + let findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 1); + + // Test case 2: Find 'b' starting from the second cell + findMatch = notebookModel.findNextMatch('b', { cellIndex: 1, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 5); + + // Test case 3: Find 'c' starting from the third cell + findMatch = notebookModel.findNextMatch('c', { cellIndex: 2, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 5); + + // Test case 4: Find 'd' starting from the fourth cell + findMatch = notebookModel.findNextMatch('d', { cellIndex: 3, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 5); + + // Test case 5: No match found + findMatch = notebookModel.findNextMatch('e', { cellIndex: 0, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.strictEqual(findMatch, null); + + // Test case 6: Same keywords in the same cell + findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: { lineNumber: 1, column: 1 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 1); + + findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: { lineNumber: 1, column: 5 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 12); + + // Test case 7: Search from the middle of a cell with keyword before and after + findMatch = notebookModel.findNextMatch('a', { cellIndex: 0, position: { lineNumber: 1, column: 10 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 13); + + // Test case 8: Search from a cell and next match is in another cell below + findMatch = notebookModel.findNextMatch('var', { cellIndex: 0, position: { lineNumber: 1, column: 20 } }, false, false, null); + assert.ok(findMatch); + assert.strictEqual(findMatch!.match.range.startLineNumber, 1); + assert.strictEqual(findMatch!.match.range.startColumn, 1); + // assert.strictEqual(match!.cellIndex, 1); + } + ); + }); }); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts index fa082d78..ee359d70 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookVariablesDataSource.test.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 * as assert from 'assert'; +import assert from 'assert'; import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index f226b1c6..c9d92f4a 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; @@ -66,7 +66,7 @@ suite('NotebookViewModel', () => { test('ctor', function () { const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService, languageDetectionService); const model = new NotebookEditorTestModel(notebook); - const options = new NotebookOptions(mainWindow, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService), false); + const options = new NotebookOptions(mainWindow, false, undefined, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService)); const eventDispatcher = new NotebookEventDispatcher(); const viewContext = new ViewContext(options, eventDispatcher, () => ({} as IBaseCellEditorOptions)); const viewModel = new NotebookViewModel('notebook', model.notebook, viewContext, null, { isReadOnly: false }, instantiationService, bulkEditService, undoRedoService, textModelService, notebookExecutionStateService); diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts index f2af8c32..199ee022 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts index 34c0a8c1..8d530e9c 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts @@ -5,7 +5,7 @@ import { workbenchCalculateActions, workbenchDynamicCalculateActions } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar'; import { Action, IAction, Separator } from 'vs/base/common/actions'; -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; interface IActionModel { diff --git a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 6db24ac2..d35dde62 100644 --- a/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -52,7 +52,7 @@ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/vie import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { CellKind, CellUri, ICellDto2, INotebookDiffEditorModel, INotebookEditorModel, INotebookSearchOptions, IOutputDto, IResolvedNotebookEditorModel, NotebookCellExecutionState, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, ICellDto2, INotebookDiffEditorModel, INotebookEditorModel, INotebookFindOptions, IOutputDto, IResolvedNotebookEditorModel, NotebookCellExecutionState, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebookCellExecution, INotebookExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; @@ -65,7 +65,7 @@ import { EditorFontLigatures, EditorFontVariations } from 'vs/editor/common/conf import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { mainWindow } from 'vs/base/browser/window'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; -import { INotebookCellOutlineProviderFactory, NotebookCellOutlineProviderFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProviderFactory'; +import { INotebookCellOutlineDataSourceFactory, NotebookCellOutlineDataSourceFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineDataSourceFactory'; import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; export class TestCell extends NotebookCellTextModel { @@ -178,7 +178,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi } } -export function setupInstantiationService(disposables: DisposableStore) { +export function setupInstantiationService(disposables: Pick) { const instantiationService = disposables.add(new TestInstantiationService()); const testThemeService = new TestThemeService(); instantiationService.stub(ILanguageService, disposables.add(new LanguageService())); @@ -199,7 +199,7 @@ export function setupInstantiationService(disposables: DisposableStore) { instantiationService.stub(IKeybindingService, new MockKeybindingService()); instantiationService.stub(INotebookCellStatusBarService, disposables.add(new NotebookCellStatusBarService())); instantiationService.stub(ICodeEditorService, disposables.add(new TestCodeEditorService(testThemeService))); - instantiationService.stub(INotebookCellOutlineProviderFactory, instantiationService.createInstance(NotebookCellOutlineProviderFactory)); + instantiationService.stub(INotebookCellOutlineDataSourceFactory, instantiationService.createInstance(NotebookCellOutlineDataSourceFactory)); instantiationService.stub(ILanguageDetectionService, new class MockLanguageDetectionService implements ILanguageDetectionService { _serviceBrand: undefined; @@ -217,7 +217,7 @@ export function setupInstantiationService(disposables: DisposableStore) { function _createTestNotebookEditor(instantiationService: TestInstantiationService, disposables: DisposableStore, cells: MockNotebookCell[]): { editor: IActiveNotebookEditorDelegate; viewModel: NotebookViewModel } { const viewType = 'notebook'; - const notebook = disposables.add(instantiationService.createInstance(NotebookTextModel, viewType, URI.parse('test'), cells.map((cell): ICellDto2 => { + const notebook = disposables.add(instantiationService.createInstance(NotebookTextModel, viewType, URI.parse('test://test'), cells.map((cell): ICellDto2 => { return { source: cell[0], mime: undefined, @@ -229,8 +229,9 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic }), {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, cellContentMetadata: {}, transientOutputs: false })); const model = disposables.add(new NotebookEditorTestModel(notebook)); - const notebookOptions = disposables.add(new NotebookOptions(mainWindow, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService), false)); - const viewContext = new ViewContext(notebookOptions, disposables.add(new NotebookEventDispatcher()), () => ({} as IBaseCellEditorOptions)); + const notebookOptions = disposables.add(new NotebookOptions(mainWindow, false, undefined, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService))); + const baseCellEditorOptions = new class extends mock() { }; + const viewContext = new ViewContext(notebookOptions, disposables.add(new NotebookEventDispatcher()), () => baseCellEditorOptions); const viewModel: NotebookViewModel = disposables.add(instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, viewContext, null, { isReadOnly: false })); const cellList = disposables.add(createNotebookCellList(instantiationService, disposables, viewContext)); @@ -295,6 +296,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic override setCellEditorSelection() { } override async revealRangeInCenterIfOutsideViewportAsync() { } override async layoutNotebookCell() { } + override async createOutput() { } override async removeInset() { } override async focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') { cell.focusMode = focusItem === 'editor' ? CellFocusMode.Editor @@ -310,7 +312,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic override get onDidChangeSelection() { return viewModel.onDidChangeSelection as Event; } override get onDidChangeOptions() { return viewModel.onDidChangeOptions; } override get onDidChangeViewCells() { return viewModel.onDidChangeViewCells; } - override async find(query: string, options: INotebookSearchOptions): Promise { + override async find(query: string, options: INotebookFindOptions): Promise { const findMatches = viewModel.find(query, options).filter(match => match.length > 0); return findMatches; } @@ -376,12 +378,18 @@ export async function withTestNotebookDiffModel(originalCells: [source: override get notebook() { return originalNotebook.viewModel.notebookDocument; } + override get resource() { + return originalNotebook.viewModel.notebookDocument.uri; + } }; const modifiedResource = new class extends mock() { override get notebook() { return modifiedNotebook.viewModel.notebookDocument; } + override get resource() { + return modifiedNotebook.viewModel.notebookDocument.uri; + } }; const model = new class extends mock() { @@ -397,15 +405,19 @@ export async function withTestNotebookDiffModel(originalCells: [source: if (res instanceof Promise) { res.finally(() => { originalNotebook.editor.dispose(); + originalNotebook.viewModel.notebookDocument.dispose(); originalNotebook.viewModel.dispose(); modifiedNotebook.editor.dispose(); + modifiedNotebook.viewModel.notebookDocument.dispose(); modifiedNotebook.viewModel.dispose(); disposables.dispose(); }); } else { originalNotebook.editor.dispose(); + originalNotebook.viewModel.notebookDocument.dispose(); originalNotebook.viewModel.dispose(); modifiedNotebook.editor.dispose(); + modifiedNotebook.viewModel.notebookDocument.dispose(); modifiedNotebook.viewModel.dispose(); disposables.dispose(); } @@ -461,15 +473,16 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe getTemplateId() { return 'template'; } }; + const baseCellRenderTemplate = new class extends mock() { }; const renderer: IListRenderer = { templateId: 'template', - renderTemplate() { return {} as BaseCellRenderTemplate; }, + renderTemplate() { return baseCellRenderTemplate; }, renderElement() { }, disposeTemplate() { } }; const notebookOptions = !!viewContext ? viewContext.notebookOptions - : disposables.add(new NotebookOptions(mainWindow, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService), false)); + : disposables.add(new NotebookOptions(mainWindow, false, undefined, instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), instantiationService.get(ICodeEditorService))); const cellList: NotebookCellList = disposables.add(instantiationService.createInstance( NotebookCellList, 'NotebookCellList', diff --git a/patched-vscode/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/patched-vscode/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 0a21d1d0..794c8bdf 100644 --- a/patched-vscode/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -65,17 +65,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'default': 'alwaysExpand' }, [OutlineConfigKeys.problemsEnabled]: { - 'markdownDescription': localize('outline.showProblem', "Show errors and warnings on Outline elements. Overwritten by `#problems.visibility#` when it is off."), + 'markdownDescription': localize('outline.showProblem', "Show errors and warnings on Outline elements. Overwritten by {0} when it is off.", '`#problems.visibility#`'), 'type': 'boolean', 'default': true }, [OutlineConfigKeys.problemsColors]: { - 'markdownDescription': localize('outline.problem.colors', "Use colors for errors and warnings on Outline elements. Overwritten by `#problems.visibility#` when it is off."), + 'markdownDescription': localize('outline.problem.colors', "Use colors for errors and warnings on Outline elements. Overwritten by {0} when it is off.", '`#problems.visibility#`'), 'type': 'boolean', 'default': true }, [OutlineConfigKeys.problemsBadges]: { - 'markdownDescription': localize('outline.problems.badges', "Use badges for errors and warnings on Outline elements. Overwritten by `#problems.visibility#` when it is off."), + 'markdownDescription': localize('outline.problems.badges', "Use badges for errors and warnings on Outline elements. Overwritten by {0} when it is off.", '`#problems.visibility#`'), 'type': 'boolean', 'default': true }, diff --git a/patched-vscode/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts b/patched-vscode/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts index ca06b37e..5ac17a4b 100644 --- a/patched-vscode/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts +++ b/patched-vscode/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts @@ -9,26 +9,28 @@ import { IModelService } from 'vs/editor/common/services/model'; import { ILink } from 'vs/editor/common/languages'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/services/output/common/output'; -import { MonacoWebWorker, createWebWorker } from 'vs/editor/browser/services/webWorker'; -import { ICreateData, OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; +import { IWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { WorkerTextModelSyncClient } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; -export class OutputLinkProvider { +export class OutputLinkProvider extends Disposable { private static readonly DISPOSE_WORKER_TIME = 3 * 60 * 1000; // dispose worker after 3 minutes of inactivity - private worker?: MonacoWebWorker; + private worker?: OutputLinkWorkerClient; private disposeWorkerScheduler: RunOnceScheduler; private linkProviderRegistration: IDisposable | undefined; constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IModelService private readonly modelService: IModelService, - @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, ) { + super(); + this.disposeWorkerScheduler = new RunOnceScheduler(() => this.disposeWorker(), OutputLinkProvider.DISPOSE_WORKER_TIME); this.registerListeners(); @@ -36,7 +38,7 @@ export class OutputLinkProvider { } private registerListeners(): void { - this.contextService.onDidChangeWorkspaceFolders(() => this.updateLinkProviderWorker()); + this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateLinkProviderWorker())); } private updateLinkProviderWorker(): void { @@ -63,28 +65,18 @@ export class OutputLinkProvider { this.disposeWorkerScheduler.cancel(); } - private getOrCreateWorker(): MonacoWebWorker { + private getOrCreateWorker(): OutputLinkWorkerClient { this.disposeWorkerScheduler.schedule(); if (!this.worker) { - const createData: ICreateData = { - workspaceFolders: this.contextService.getWorkspace().folders.map(folder => folder.uri.toString()) - }; - - this.worker = createWebWorker(this.modelService, this.languageConfigurationService, { - moduleId: 'vs/workbench/contrib/output/common/outputLinkComputer', - createData, - label: 'outputLinkComputer' - }); + this.worker = new OutputLinkWorkerClient(this.contextService, this.modelService); } return this.worker; } private async provideLinks(modelUri: URI): Promise { - const linkComputer = await this.getOrCreateWorker().withSyncedResources([modelUri]); - - return linkComputer.computeLinks(modelUri.toString()); + return this.getOrCreateWorker().provideLinks(modelUri); } private disposeWorker(): void { @@ -94,3 +86,32 @@ export class OutputLinkProvider { } } } + +class OutputLinkWorkerClient extends Disposable { + private readonly _workerClient: IWorkerClient; + private readonly _workerTextModelSyncClient: WorkerTextModelSyncClient; + private readonly _initializeBarrier: Promise; + + constructor( + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IModelService modelService: IModelService, + ) { + super(); + this._workerClient = this._register(createWebWorker( + 'vs/workbench/contrib/output/common/outputLinkComputer', + 'OutputLinkDetectionWorker' + )); + this._workerTextModelSyncClient = WorkerTextModelSyncClient.create(this._workerClient, modelService); + this._initializeBarrier = this._ensureWorkspaceFolders(); + } + + private async _ensureWorkspaceFolders(): Promise { + await this._workerClient.proxy.$setWorkspaceFolders(this.contextService.getWorkspace().folders.map(folder => folder.uri.toString())); + } + + public async provideLinks(modelUri: URI): Promise { + await this._initializeBarrier; + await this._workerTextModelSyncClient.ensureSyncedResources([modelUri]); + return this._workerClient.proxy.$computeLinks(modelUri.toString()); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/output/browser/outputServices.ts b/patched-vscode/src/vs/workbench/contrib/output/browser/outputServices.ts index eb83c902..4307629d 100644 --- a/patched-vscode/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/patched-vscode/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; +import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT } from 'vs/workbench/services/output/common/output'; +import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, CONTEXT_ACTIVE_OUTPUT_LEVEL, CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT } from 'vs/workbench/services/output/common/output'; import { OutputLinkProvider } from 'vs/workbench/contrib/output/browser/outputLinkProvider'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { ITextModel } from 'vs/editor/common/model'; @@ -42,7 +43,7 @@ class OutputChannel extends Disposable implements IOutputChannel { super(); this.id = outputChannelDescriptor.id; this.label = outputChannelDescriptor.label; - this.uri = URI.from({ scheme: OUTPUT_SCHEME, path: this.id }); + this.uri = URI.from({ scheme: Schemas.outputChannel, path: this.id }); this.model = this._register(outputChannelModelService.createOutputChannelModel(this.id, this.uri, outputChannelDescriptor.languageId ? languageService.createById(outputChannelDescriptor.languageId) : languageService.createByMimeType(outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME), outputChannelDescriptor.file)); } @@ -103,8 +104,8 @@ export class OutputService extends Disposable implements IOutputService, ITextMo this.activeOutputChannelLevelIsDefaultContext = CONTEXT_ACTIVE_OUTPUT_LEVEL_IS_DEFAULT.bindTo(contextKeyService); // Register as text model content provider for output - textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this); - instantiationService.createInstance(OutputLinkProvider); + this._register(textModelResolverService.registerTextModelContentProvider(Schemas.outputChannel, this)); + this._register(instantiationService.createInstance(OutputLinkProvider)); // Create output channels for already registered channels const registry = Registry.as(Extensions.OutputChannels); diff --git a/patched-vscode/src/vs/workbench/contrib/output/browser/outputView.ts b/patched-vscode/src/vs/workbench/contrib/output/browser/outputView.ts index 19e5642f..9a23e945 100644 --- a/patched-vscode/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/patched-vscode/src/vs/workbench/contrib/output/browser/outputView.ts @@ -62,8 +62,8 @@ export class OutputViewPane extends ViewPane { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); this.scrollLockContextKey = CONTEXT_OUTPUT_SCROLL_LOCK.bindTo(this.contextKeyService); - const editorInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); - this.editor = editorInstantiationService.createInstance(OutputEditor); + const editorInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]))); + this.editor = this._register(editorInstantiationService.createInstance(OutputEditor)); this._register(this.editor.onTitleAreaUpdate(() => { this.updateTitle(this.editor.getTitle()); this.updateActions(); diff --git a/patched-vscode/src/vs/workbench/contrib/output/common/outputLinkComputer.esm.ts b/patched-vscode/src/vs/workbench/contrib/output/common/outputLinkComputer.esm.ts new file mode 100644 index 00000000..2d3fd96b --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/output/common/outputLinkComputer.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { create } from './outputLinkComputer'; +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; + +bootstrapSimpleWorker(create); diff --git a/patched-vscode/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/patched-vscode/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index 0de2f9f2..aad9864e 100644 --- a/patched-vscode/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/patched-vscode/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMirrorModel, IWorkerContext } from 'vs/editor/common/services/editorSimpleWorker'; import { ILink } from 'vs/editor/common/languages'; import { URI } from 'vs/base/common/uri'; import * as extpath from 'vs/base/common/extpath'; @@ -12,28 +11,33 @@ import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; - -export interface ICreateData { - workspaceFolders: string[]; -} +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { WorkerTextModelSyncServer, ICommonModel } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; export interface IResourceCreator { toResource: (folderRelativePath: string) => URI | null; } -export class OutputLinkComputer { +export class OutputLinkComputer implements IRequestHandler { + _requestHandlerBrand: any; + + private readonly workerTextModelSyncServer = new WorkerTextModelSyncServer(); private patterns = new Map(); - constructor(private ctx: IWorkerContext, createData: ICreateData) { - this.computePatterns(createData); + constructor(workerServer: IWorkerServer) { + this.workerTextModelSyncServer.bindToServer(workerServer); } - private computePatterns(createData: ICreateData): void { + $setWorkspaceFolders(workspaceFolders: string[]) { + this.computePatterns(workspaceFolders); + } + + private computePatterns(_workspaceFolders: string[]): void { // Produce patterns for each workspace root we are configured with // This means that we will be able to detect links for paths that // contain any of the workspace roots as segments. - const workspaceFolders = createData.workspaceFolders + const workspaceFolders = _workspaceFolders .sort((resourceStrA, resourceStrB) => resourceStrB.length - resourceStrA.length) // longest paths first (for https://github.com/microsoft/vscode/issues/88121) .map(resourceStr => URI.parse(resourceStr)); @@ -43,13 +47,11 @@ export class OutputLinkComputer { } } - private getModel(uri: string): IMirrorModel | undefined { - const models = this.ctx.getMirrorModels(); - - return models.find(model => model.uri.toString() === uri); + private getModel(uri: string): ICommonModel | undefined { + return this.workerTextModelSyncServer.getModel(uri); } - computeLinks(uri: string): ILink[] { + $computeLinks(uri: string): ILink[] { const model = this.getModel(uri); if (!model) { return []; @@ -179,7 +181,10 @@ export class OutputLinkComputer { } } -// Export this function because this will be called by the web worker for computing links -export function create(ctx: IWorkerContext, createData: ICreateData): OutputLinkComputer { - return new OutputLinkComputer(ctx, createData); +/** + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle + */ +export function create(workerServer: IWorkerServer): OutputLinkComputer { + return new OutputLinkComputer(workerServer); } diff --git a/patched-vscode/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts b/patched-vscode/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts index 2f7988f5..722c8094 100644 --- a/patched-vscode/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; import { OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; diff --git a/patched-vscode/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/patched-vscode/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index 81653b58..082f44a4 100644 --- a/patched-vscode/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -200,13 +200,13 @@ class PerfModelContentProvider implements ITextModelContentProvider { const table: Array> = []; table.push(['start => app.isReady', metrics.timers.ellapsedAppReady, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['nls:start => nls:end', metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`]); - table.push(['require(main.bundle.js)', metrics.timers.ellapsedLoadMainBundle, '[main]', `initial startup: ${metrics.initialStartup}`]); + table.push(['import(main.bundle.js)', metrics.timers.ellapsedLoadMainBundle, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['start crash reporter', metrics.timers.ellapsedCrashReporter, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['serve main IPC handle', metrics.timers.ellapsedMainServer, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['create window', metrics.timers.ellapsedWindowCreate, '[main]', `initial startup: ${metrics.initialStartup}, ${metrics.initialStartup ? `state: ${metrics.timers.ellapsedWindowRestoreState}ms, widget: ${metrics.timers.ellapsedBrowserWindowCreate}ms, show: ${metrics.timers.ellapsedWindowMaximize}ms` : ''}`]); table.push(['app.isReady => window.loadUrl()', metrics.timers.ellapsedWindowLoad, '[main]', `initial startup: ${metrics.initialStartup}`]); - table.push(['window.loadUrl() => begin to require(workbench.desktop.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); - table.push(['require(workbench.desktop.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); + table.push(['window.loadUrl() => begin to import(workbench.desktop.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); + table.push(['import(workbench.desktop.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); table.push(['wait for window config', metrics.timers.ellapsedWaitForWindowConfig, '[renderer]', undefined]); table.push(['init storage (global & workspace)', metrics.timers.ellapsedStorageInit, '[renderer]', undefined]); table.push(['init workspace service', metrics.timers.ellapsedWorkspaceServiceInit, '[renderer]', undefined]); @@ -332,7 +332,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { map.set(LoaderEventType.CachedDataFound, []); map.set(LoaderEventType.CachedDataMissed, []); map.set(LoaderEventType.CachedDataRejected, []); - if (typeof require.getStats === 'function') { + if (!isESM && typeof require.getStats === 'function') { for (const stat of require.getStats()) { if (map.has(stat.type)) { map.get(stat.type)!.push(stat.detail); diff --git a/patched-vscode/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts b/patched-vscode/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts index 200f6380..028a744f 100644 --- a/patched-vscode/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts +++ b/patched-vscode/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts @@ -179,11 +179,9 @@ export class NativeStartupTimings extends StartupTimings implements IWorkbenchCo // Major/Minor GC Events case 'MinorGC': minorGCs++; + break; case 'MajorGC': majorGCs++; - if (event.args && typeof event.args.usedHeapSizeAfter === 'number' && typeof event.args.usedHeapSizeBefore === 'number') { - garbage += (event.args.usedHeapSizeBefore - event.args.usedHeapSizeAfter); - } break; // GC Events that block the main thread @@ -193,6 +191,12 @@ export class NativeStartupTimings extends StartupTimings implements IWorkbenchCo duration += event.dur; break; } + + if (event.name === 'MajorGC' || event.name === 'MinorGC') { + if (typeof event.args?.usedHeapSizeAfter === 'number' && typeof event.args.usedHeapSizeBefore === 'number') { + garbage += (event.args.usedHeapSizeBefore - event.args.usedHeapSizeAfter); + } + } } return { minorGCs, majorGCs, used, garbage, duration: Math.round(duration / 1000) }; diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 2c8e669e..0a710773 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -60,8 +60,9 @@ import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetN import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; const $ = DOM.$; @@ -122,7 +123,8 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @IStorageService storageService: IStorageService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { super(KeybindingsEditor.ID, group, telemetryService, themeService, storageService); this.delayedFiltering = new Delayer(300); @@ -590,7 +592,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP if (this.keybindingsEditorModel) { const filter = this.searchWidget.getValue(); const keybindingsEntries: IKeybindingItemEntry[] = this.keybindingsEditorModel.fetch(filter, this.sortByPrecedenceAction.checked); - + this.accessibilityService.alert(localize('foundResults', "{0} results", keybindingsEntries.length)); this.ariaLabelElement.setAttribute('aria-label', this.getAriaLabel(keybindingsEntries)); if (keybindingsEntries.length === 0) { @@ -903,7 +905,7 @@ class ActionsColumnRenderer implements ITableRenderer(extensionContainer, $('a.extension-label', { tabindex: 0 })); @@ -1213,7 +1215,7 @@ class WhenColumnRenderer implements ITableRenderer { const foregroundColor = theme.getColor(foreground); diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/patched-vscode/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 74caeb8c..26034cdf 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -162,8 +162,14 @@ } .settings-editor > .settings-body .settings-tree-container .setting-item-extension-toggle .setting-item-extension-toggle-button { - display: block; + display: inline-block; + width: fit-content; +} + +.settings-editor > .settings-body .settings-tree-container .setting-item-extension-toggle .setting-item-extension-dismiss-button { + display: inline-block; width: fit-content; + margin-left: 8px; } .settings-editor.no-results > .settings-body .settings-toc-container, @@ -408,7 +414,8 @@ } .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides a.modified-scope { - text-decoration: underline; + color: var(--vscode-textLink-foreground); + text-decoration: var(--text-link-decoration); cursor: pointer; } @@ -512,6 +519,11 @@ color: var(--vscode-textLink-foreground); } +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button { + text-decoration: var(--text-link-decoration); +} + .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown a:focus, .settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button:focus { outline: 1px solid -webkit-focus-ring-color; diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 66ca47c4..ccf46637 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -1246,6 +1246,7 @@ class SettingsEditorTitleContribution extends Disposable implements IWorkbenchCo ResourceContextKey.Resource.isEqualTo(this.userDataProfilesService.defaultProfile.settingsResource.toString())), ContextKeyExpr.not('isInDiffEditor')); const registerOpenUserSettingsEditorFromJsonAction = () => { + registerOpenUserSettingsEditorFromJsonActionDisposables.value = undefined; registerOpenUserSettingsEditorFromJsonActionDisposables.value = registerAction2(class extends Action2 { constructor() { super({ diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 1ce23e61..dc76d156 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -500,6 +500,7 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc this._register(this.editor.getModel()!.onDidChangeContent(() => this.delayedRender())); this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.source === ConfigurationTarget.DEFAULT)(() => this.delayedRender())); this._register(languageFeaturesService.codeActionProvider.register({ pattern: settingsEditorModel.uri.path }, this)); + this._register(userDataProfileService.onDidChangeCurrentProfile(() => this.delayedRender())); } private delayedRender(): void { diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index 7f8bb17a..616e7f6e 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -57,7 +57,7 @@ export class PreferencesSearchService extends Disposable implements IPreferences return workbenchSettings.enableNaturalLanguageSearch; } - getRemoteSearchProvider(filter: string, newExtensionsOnly = false): IRemoteSearchProvider | undefined { + getRemoteSearchProvider(filter: string): IRemoteSearchProvider | undefined { if (!this.remoteSearchAllowed) { return undefined; } @@ -523,9 +523,8 @@ class RemoteSearchProvider implements IRemoteSearchProvider { private initializeSearchProviders() { if (this.aiRelatedInformationService.isEnabled()) { this.adaSearchProvider ??= new AiRelatedInformationSearchProvider(this.aiRelatedInformationService); - } else { - this.tfIdfSearchProvider ??= new TfIdfSearchProvider(); } + this.tfIdfSearchProvider ??= new TfIdfSearchProvider(); } setFilter(filter: string): void { @@ -533,9 +532,8 @@ class RemoteSearchProvider implements IRemoteSearchProvider { this.filter = filter; if (this.adaSearchProvider) { this.adaSearchProvider.setFilter(filter); - } else { - this.tfIdfSearchProvider!.setFilter(filter); } + this.tfIdfSearchProvider!.setFilter(filter); } searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise { @@ -543,11 +541,14 @@ class RemoteSearchProvider implements IRemoteSearchProvider { return Promise.resolve(null); } - if (this.adaSearchProvider) { - return this.adaSearchProvider.searchModel(preferencesModel, token); - } else { + if (!this.adaSearchProvider) { return this.tfIdfSearchProvider!.searchModel(preferencesModel, token); } + + // Use TF-IDF search as a fallback, ref https://github.com/microsoft/vscode/issues/224946 + return this.adaSearchProvider.searchModel(preferencesModel, token).then((results) => { + return results?.filterMatches.length ? results : this.tfIdfSearchProvider!.searchModel(preferencesModel, token); + }); } } diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 585b89b4..cac09b8c 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -35,7 +35,7 @@ import { settingsEditIcon, settingsScopeDropDownIcon } from 'vs/workbench/contri import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; -import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover'; +import type { IManagedHover } from 'vs/base/browser/ui/hover/hover'; import { IHoverService } from 'vs/platform/hover/browser/hover'; export class FolderSettingsActionViewItem extends BaseActionViewItem { @@ -45,7 +45,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { private container!: HTMLElement; private anchorElement!: HTMLElement; - private anchorElementHover!: IUpdatableHover; + private anchorElementHover!: IManagedHover; private labelElement!: HTMLElement; private detailsElement!: HTMLElement; private dropDownElement!: HTMLElement; @@ -93,7 +93,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { 'aria-haspopup': 'true', 'tabindex': '0' }, this.labelElement, this.detailsElement, this.dropDownElement); - this.anchorElementHover = this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.anchorElement, '')); + this.anchorElementHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.anchorElement, '')); this._register(DOM.addDisposableListener(this.anchorElement, DOM.EventType.MOUSE_DOWN, e => DOM.EventHelper.stop(e))); this._register(DOM.addDisposableListener(this.anchorElement, DOM.EventType.CLICK, e => this.onClick(e))); this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, e => this.onKeyUp(e))); @@ -261,6 +261,7 @@ export class SettingsTargetsWidget extends Widget { orientation: ActionsOrientation.HORIZONTAL, focusOnlyEnabledItems: true, ariaLabel: localize('settingsSwitcherBarAriaLabel', "Settings Switcher"), + ariaRole: 'tablist', actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => action.id === 'folderSettings' ? this.folderSettings : undefined })); @@ -436,8 +437,7 @@ export class SearchWidget extends Widget { protected createInputBox(parent: HTMLElement): HistoryInputBox { const showHistoryHint = () => showHistoryKeybindingHint(this.keybindingService); - const box = this._register(new ContextScopedHistoryInputBox(parent, this.contextViewService, { ...this.options, showHistoryHint }, this.contextKeyService)); - return box; + return new ContextScopedHistoryInputBox(parent, this.contextViewService, { ...this.options, showHistoryHint }, this.contextKeyService); } showMessage(message: string): void { diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 0b0441c9..3b381a5c 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -10,14 +10,14 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; -import { Delayer, ThrottledDelayer } from 'vs/base/common/async'; +import { Delayer } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { fromNow } from 'vs/base/common/date'; import { isCancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose, type IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/settingsEditor2'; @@ -67,6 +67,7 @@ import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetN import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { CodeWindow } from 'vs/base/browser/window'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export const enum SettingsFocusContext { @@ -109,7 +110,7 @@ export class SettingsEditor2 extends EditorPane { private static TOC_RESET_WIDTH: number = 200; private static EDITOR_MIN_WIDTH: number = 500; // Below NARROW_TOTAL_WIDTH, we only render the editor rather than the ToC. - private static NARROW_TOTAL_WIDTH: number = SettingsEditor2.TOC_RESET_WIDTH + SettingsEditor2.EDITOR_MIN_WIDTH; + private static NARROW_TOTAL_WIDTH: number = this.TOC_RESET_WIDTH + this.EDITOR_MIN_WIDTH; private static SUGGESTIONS: string[] = [ `@${MODIFIED_SETTING_TAG}`, @@ -180,8 +181,7 @@ export class SettingsEditor2 extends EditorPane { private tocTree!: TOCTree; private delayedFilterLogging: Delayer; - private localSearchDelayer: Delayer; - private remoteSearchThrottle: ThrottledDelayer; + private searchDelayer: Delayer; private searchInProgress: CancellationTokenSource | null = null; private searchInputDelayer: Delayer; @@ -218,6 +218,12 @@ export class SettingsEditor2 extends EditorPane { private dimension!: DOM.Dimension; private installedExtensionIds: string[] = []; + private dismissedExtensionSettings: string[] = []; + + private readonly DISMISSED_EXTENSION_SETTINGS_STORAGE_KEY = 'settingsEditor2.dismissedExtensionSettings'; + private readonly DISMISSED_EXTENSION_SETTINGS_DELIMITER = '\t'; + + private readonly inputChangeListener: MutableDisposable; constructor( group: IEditorGroup, @@ -241,11 +247,11 @@ export class SettingsEditor2 extends EditorPane { @IProductService private readonly productService: IProductService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IEditorProgressService private readonly editorProgressService: IEditorProgressService, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, ) { super(SettingsEditor2.ID, group, telemetryService, themeService, storageService); this.delayedFilterLogging = new Delayer(1000); - this.localSearchDelayer = new Delayer(300); - this.remoteSearchThrottle = new ThrottledDelayer(200); + this.searchDelayer = new Delayer(300); this.viewState = { settingsTarget: ConfigurationTarget.USER_LOCAL }; this.settingFastUpdateDelayer = new Delayer(SettingsEditor2.SETTING_UPDATE_FAST_DEBOUNCE); @@ -263,12 +269,20 @@ export class SettingsEditor2 extends EditorPane { this.editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, SETTINGS_EDITOR_STATE_KEY); + this.dismissedExtensionSettings = this.storageService + .get(this.DISMISSED_EXTENSION_SETTINGS_STORAGE_KEY, StorageScope.PROFILE, '') + .split(this.DISMISSED_EXTENSION_SETTINGS_DELIMITER); + this._register(configurationService.onDidChangeConfiguration(e => { if (e.source !== ConfigurationTarget.DEFAULT) { this.onConfigUpdate(e.affectedKeys); } })); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => { + e.join(this.whenCurrentProfileChanged()); + })); + this._register(workspaceTrustManagementService.onDidChangeTrust(() => { this.searchResultModel?.updateWorkspaceTrust(workspaceTrustManagementService.isWorkspaceTrusted()); @@ -284,11 +298,28 @@ export class SettingsEditor2 extends EditorPane { } })); + this._register(extensionManagementService.onDidInstallExtensions(() => { + this.refreshInstalledExtensionsList(); + })); + this._register(extensionManagementService.onDidUninstallExtension(() => { + this.refreshInstalledExtensionsList(); + })); + this.modelDisposables = this._register(new DisposableStore()); if (ENABLE_LANGUAGE_FILTER && !SettingsEditor2.SUGGESTIONS.includes(`@${LANGUAGE_SETTING_TAG}`)) { SettingsEditor2.SUGGESTIONS.push(`@${LANGUAGE_SETTING_TAG}`); } + this.inputChangeListener = this._register(new MutableDisposable()); + } + + private async whenCurrentProfileChanged(): Promise { + this.updatedConfigSchemaDelayer.trigger(() => { + this.dismissedExtensionSettings = this.storageService + .get(this.DISMISSED_EXTENSION_SETTINGS_STORAGE_KEY, StorageScope.PROFILE, '') + .split(this.DISMISSED_EXTENSION_SETTINGS_DELIMITER); + this.onConfigUpdate(undefined, true); + }); } override get minimumWidth(): number { return SettingsEditor2.EDITOR_MIN_WIDTH; } @@ -382,9 +413,9 @@ export class SettingsEditor2 extends EditorPane { // Don't block setInput on render (which can trigger an async search) this.onConfigUpdate(undefined, true).then(() => { - this._register(input.onWillDispose(() => { + this.inputChangeListener.value = input.onWillDispose(() => { this.searchWidget.setValue(''); - })); + }); // Init TOC selection this.updateTreeScrollSync(); @@ -396,7 +427,7 @@ export class SettingsEditor2 extends EditorPane { private async refreshInstalledExtensionsList(): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); this.installedExtensionIds = installedExtensions - .filter(ext => ext.manifest && ext.manifest.contributes && ext.manifest.contributes.configuration) + .filter(ext => ext.manifest.contributes?.configuration) .map(ext => ext.identifier.id); } @@ -451,8 +482,7 @@ export class SettingsEditor2 extends EditorPane { const target: SettingsTarget | undefined = options.folderUri ?? recoveredViewState?.settingsTarget ?? options.target; if (target) { - this.settingsTargetsWidget.settingsTarget = target; - this.viewState.settingsTarget = target; + this.settingsTargetsWidget.updateTarget(target); } } @@ -676,6 +706,19 @@ export class SettingsEditor2 extends EditorPane { this.onConfigUpdate(undefined, true); } + private onDidDismissExtensionSetting(extensionId: string): void { + if (!this.dismissedExtensionSettings.includes(extensionId)) { + this.dismissedExtensionSettings.push(extensionId); + } + this.storageService.store( + this.DISMISSED_EXTENSION_SETTINGS_STORAGE_KEY, + this.dismissedExtensionSettings.join(this.DISMISSED_EXTENSION_SETTINGS_DELIMITER), + StorageScope.PROFILE, + StorageTarget.USER + ); + this.onConfigUpdate(undefined, true); + } + private onDidClickSetting(evt: ISettingLinkClickEvent, recursed?: boolean): void { const targetElement = this.currentSettingsModel.getElementsByName(evt.targetKey)?.[0]; let revealFailed = false; @@ -791,10 +834,10 @@ export class SettingsEditor2 extends EditorPane { this.createTOC(this.tocTreeContainer); this.createSettingsTree(this.settingsTreeContainer); - this.splitView = new SplitView(this.bodyContainer, { + this.splitView = this._register(new SplitView(this.bodyContainer, { orientation: Orientation.HORIZONTAL, proportionalLayout: true - }); + })); const startingWidth = this.storageService.getNumber('settingsEditor2.splitViewWidth', StorageScope.PROFILE, SettingsEditor2.TOC_RESET_WIDTH); this.splitView.addView({ onDidChange: Event.None, @@ -911,8 +954,9 @@ export class SettingsEditor2 extends EditorPane { } private createSettingsTree(container: HTMLElement): void { - this.settingRenderers = this.instantiationService.createInstance(SettingTreeRenderers); + this.settingRenderers = this._register(this.instantiationService.createInstance(SettingTreeRenderers)); this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type, e.manualReset, e.scope))); + this._register(this.settingRenderers.onDidDismissExtensionSetting((e) => this.onDidDismissExtensionSetting(e))); this._register(this.settingRenderers.onDidOpenSettings(settingKey => { this.openSettingsFile({ revealSetting: { key: settingKey, edit: true } }); })); @@ -1207,43 +1251,6 @@ export class SettingsEditor2 extends EditorPane { }); } - private addOrRemoveManageExtensionSetting(setting: ISetting, extension: IGalleryExtension, groups: ISettingsGroup[]): ISettingsGroup | undefined { - const matchingGroups = groups.filter(g => { - const lowerCaseId = g.extensionInfo?.id.toLowerCase(); - return (lowerCaseId === setting.stableExtensionId!.toLowerCase() || - lowerCaseId === setting.prereleaseExtensionId!.toLowerCase()); - }); - - const extensionId = setting.displayExtensionId!; - const extensionInstalled = this.installedExtensionIds.includes(extensionId); - if (!matchingGroups.length && !extensionInstalled) { - // Only show the recommendation when the extension hasn't been installed. - const newGroup: ISettingsGroup = { - sections: [{ - settings: [setting], - }], - id: extensionId, - title: setting.extensionGroupTitle!, - titleRange: nullRange, - range: nullRange, - extensionInfo: { - id: extensionId, - displayName: extension?.displayName, - } - }; - groups.push(newGroup); - return newGroup; - } else if (matchingGroups.length >= 2 || extensionInstalled) { - // Remove the group with the manage extension setting. - const matchingGroupIndex = matchingGroups.findIndex(group => - group.sections.length === 1 && group.sections[0].settings.length === 1 && group.sections[0].settings[0].displayExtensionId); - if (matchingGroupIndex !== -1) { - groups.splice(matchingGroupIndex, 1); - } - } - return undefined; - } - private createSettingsOrderByTocIndex(resolvedSettingsRoot: ITOCEntry): Map { const index = new Map(); function indexSettings(resolvedSettingsRoot: ITOCEntry, counter = 0): number { @@ -1298,10 +1305,37 @@ export class SettingsEditor2 extends EditorPane { } const additionalGroups: ISettingsGroup[] = []; + let setAdditionalGroups = false; const toggleData = await getExperimentalExtensionToggleData(this.extensionGalleryService, this.productService); if (toggleData && groups.filter(g => g.extensionInfo).length) { for (const key in toggleData.settingsEditorRecommendedExtensions) { - const extension = toggleData.recommendedExtensionsGalleryInfo[key]; + const extension: IGalleryExtension = toggleData.recommendedExtensionsGalleryInfo[key]; + if (!extension) { + continue; + } + + const extensionId = extension.identifier.id; + const extensionInstalled = this.installedExtensionIds.includes(extensionId); + + // Drill down to see whether the group and setting already exist + // and need to be removed. + const matchingGroupIndex = groups.findIndex(g => + g.extensionInfo && g.extensionInfo!.id.toLowerCase() === extensionId.toLowerCase() && + g.sections.length === 1 && g.sections[0].settings.length === 1 && g.sections[0].settings[0].displayExtensionId + ); + if (extensionInstalled || this.dismissedExtensionSettings.includes(extensionId)) { + if (matchingGroupIndex !== -1) { + groups.splice(matchingGroupIndex, 1); + setAdditionalGroups = true; + } + continue; + } + + if (matchingGroupIndex !== -1) { + continue; + } + + // Create the entry. extensionInstalled is false in this case. let manifest: IExtensionManifest | null = null; try { manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None); @@ -1319,7 +1353,8 @@ export class SettingsEditor2 extends EditorPane { groupTitle = contributesConfiguration[0].title; } - const extensionName = extension?.displayName ?? extension?.name ?? extension.identifier.id; + const recommendationInfo = toggleData.settingsEditorRecommendedExtensions[key]; + const extensionName = extension.displayName ?? extension.name ?? extensionId; const settingKey = `${key}.manageExtension`; const setting: ISetting = { range: nullRange, @@ -1327,21 +1362,32 @@ export class SettingsEditor2 extends EditorPane { keyRange: nullRange, value: null, valueRange: nullRange, - description: [extension?.description || ''], + description: [recommendationInfo.onSettingsEditorOpen?.descriptionOverride ?? extension.description], descriptionIsMarkdown: false, descriptionRanges: [], - title: extensionName, scope: ConfigurationScope.WINDOW, type: 'null', - displayExtensionId: extension.identifier.id, - prereleaseExtensionId: key, - stableExtensionId: key, - extensionGroupTitle: groupTitle ?? extensionName + displayExtensionId: extensionId, + extensionGroupTitle: groupTitle ?? extensionName, + categoryLabel: 'Extensions', + title: extensionName }; - const additionalGroup = this.addOrRemoveManageExtensionSetting(setting, extension, groups); - if (additionalGroup) { - additionalGroups.push(additionalGroup); - } + const additionalGroup: ISettingsGroup = { + sections: [{ + settings: [setting], + }], + id: extensionId, + title: setting.extensionGroupTitle!, + titleRange: nullRange, + range: nullRange, + extensionInfo: { + id: extensionId, + displayName: extension.displayName, + } + }; + groups.push(additionalGroup); + additionalGroups.push(additionalGroup); + setAdditionalGroups = true; } } @@ -1351,7 +1397,7 @@ export class SettingsEditor2 extends EditorPane { const commonlyUsed = resolveSettingsTree(commonlyUsedDataToUse, groups, this.logService); resolvedSettingsRoot.children!.unshift(commonlyUsed.tree); - if (toggleData) { + if (toggleData && setAdditionalGroups) { // Add the additional groups to the model to help with searching. this.defaultSettingsEditorModel.setAdditionalGroups(additionalGroups); } @@ -1405,9 +1451,7 @@ export class SettingsEditor2 extends EditorPane { keys.forEach(key => this.settingsTreeModel.updateElementsByName(key)); } - // Attempt to render the tree once rather than - // once for each key to avoid redundant calls to this.refreshTree() - this.renderTree(); + keys.forEach(key => this.renderTree(key)); } else { this.renderTree(); } @@ -1447,7 +1491,6 @@ export class SettingsEditor2 extends EditorPane { // update `list`s live, as they have a separate "submit edit" step built in before this (focusedSetting.parentElement && !focusedSetting.parentElement.classList.contains('setting-item-list')) ) { - this.updateModifiedLabelForKey(key); this.scheduleRefresh(focusedSetting, key); return; @@ -1463,8 +1506,10 @@ export class SettingsEditor2 extends EditorPane { if (key) { const elements = this.currentSettingsModel.getElementsByName(key); if (elements && elements.length) { - // TODO https://github.com/microsoft/vscode/issues/57360 - this.refreshTree(); + if (elements.length >= 2) { + console.warn('More than one setting with key ' + key + ' found'); + } + this.refreshSingleElement(elements[0]); } else { // Refresh requested for a key that we don't know about return; @@ -1480,6 +1525,12 @@ export class SettingsEditor2 extends EditorPane { return !!DOM.findParentWithClass(this.rootElement.ownerDocument.activeElement, 'context-view'); } + private refreshSingleElement(element: SettingsTreeSettingElement): void { + if (this.isVisible()) { + this.settingsTree.rerender(element); + } + } + private refreshTree(): void { if (this.isVisible()) { this.settingsTree.setChildren(null, createGroupIterator(this.currentSettingsModel.root)); @@ -1573,8 +1624,7 @@ export class SettingsEditor2 extends EditorPane { this.searchResultModel = null; } - this.localSearchDelayer.cancel(); - this.remoteSearchThrottle.cancel(); + this.searchDelayer.cancel(); if (this.searchInProgress) { this.searchInProgress.cancel(); this.searchInProgress.dispose(); @@ -1665,7 +1715,7 @@ export class SettingsEditor2 extends EditorPane { this.telemetryService.publicLog2('settingsEditor.filter', data); } - private triggerFilterPreferences(query: string): Promise { + private async triggerFilterPreferences(query: string): Promise { if (this.searchInProgress) { this.searchInProgress.cancel(); this.searchInProgress = null; @@ -1673,33 +1723,39 @@ export class SettingsEditor2 extends EditorPane { // Trigger the local search. If it didn't find an exact match, trigger the remote search. const searchInProgress = this.searchInProgress = new CancellationTokenSource(); - return this.localSearchDelayer.trigger(async () => { - if (searchInProgress && !searchInProgress.token.isCancellationRequested) { - const result = await this.localFilterPreferences(query); - if (result && !result.exactMatch) { - this.remoteSearchThrottle.trigger(async () => { - if (searchInProgress && !searchInProgress.token.isCancellationRequested) { - await this.remoteSearchPreferences(query, this.searchInProgress?.token); - } - }); + return this.searchDelayer.trigger(async () => { + if (!searchInProgress.token.isCancellationRequested) { + const localResults = await this.localFilterPreferences(query, searchInProgress.token); + if (localResults && !localResults.exactMatch && !searchInProgress.token.isCancellationRequested) { + await this.remoteSearchPreferences(query, searchInProgress.token); } + + // Update UI only after all the search results are in + // ref https://github.com/microsoft/vscode/issues/224946 + this.onDidFinishSearch(); } }); } + private onDidFinishSearch() { + this.tocTreeModel.currentSearchModel = this.searchResultModel; + this.tocTreeModel.update(); + this.tocTree.setFocus([]); + this.viewState.filterToCategory = undefined; + this.tocTree.expandAll(); + this.settingsTree.scrollTop = 0; + this.refreshTOCTree(); + this.renderTree(undefined, true); + } + private localFilterPreferences(query: string, token?: CancellationToken): Promise { const localSearchProvider = this.preferencesSearchService.getLocalSearchProvider(query); return this.filterOrSearchPreferences(query, SearchResultIdx.Local, localSearchProvider, token); } - private remoteSearchPreferences(query: string, token?: CancellationToken): Promise { + private remoteSearchPreferences(query: string, token?: CancellationToken): Promise { const remoteSearchProvider = this.preferencesSearchService.getRemoteSearchProvider(query); - const newExtSearchProvider = this.preferencesSearchService.getRemoteSearchProvider(query, true); - - return Promise.all([ - this.filterOrSearchPreferences(query, SearchResultIdx.Remote, remoteSearchProvider, token), - this.filterOrSearchPreferences(query, SearchResultIdx.NewExtensions, newExtSearchProvider, token) - ]).then(() => { }); + return this.filterOrSearchPreferences(query, SearchResultIdx.Remote, remoteSearchProvider, token); } private async filterOrSearchPreferences(query: string, type: SearchResultIdx, searchProvider?: ISearchProvider, token?: CancellationToken): Promise { @@ -1708,24 +1764,8 @@ export class SettingsEditor2 extends EditorPane { // Handle cancellation like this because cancellation is lost inside the search provider due to async/await return null; } - if (!this.searchResultModel) { - this.searchResultModel = this.instantiationService.createInstance(SearchResultModel, this.viewState, this.settingsOrderByTocIndex, this.workspaceTrustManagementService.isWorkspaceTrusted()); - // Must be called before this.renderTree() - // to make sure the search results count is set. - this.searchResultModel.setResult(type, result); - this.tocTreeModel.currentSearchModel = this.searchResultModel; - } else { - this.searchResultModel.setResult(type, result); - this.tocTreeModel.update(); - } - if (type === SearchResultIdx.Local) { - this.tocTree.setFocus([]); - this.viewState.filterToCategory = undefined; - this.tocTree.expandAll(); - } - this.settingsTree.scrollTop = 0; - this.refreshTOCTree(); - this.renderTree(undefined, true); + this.searchResultModel ??= this.instantiationService.createInstance(SearchResultModel, this.viewState, this.settingsOrderByTocIndex, this.workspaceTrustManagementService.isWorkspaceTrusted()); + this.searchResultModel.setResult(type, result); return result; } diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 6db2e788..59b31491 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -9,7 +9,7 @@ import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -448,15 +448,27 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { updateDefaultOverrideIndicator(element: SettingsTreeSettingElement) { this.defaultOverrideIndicator.element.style.display = 'none'; - const sourceToDisplay = getDefaultValueSourceToDisplay(element); + let sourceToDisplay = getDefaultValueSourceToDisplay(element); if (sourceToDisplay !== undefined) { this.defaultOverrideIndicator.element.style.display = 'inline'; this.defaultOverrideIndicator.disposables.clear(); - const defaultOverrideHoverContent = localize('defaultOverriddenDetails', "Default setting value overridden by {0}", sourceToDisplay); + // Show source of default value when hovered + if (Array.isArray(sourceToDisplay) && sourceToDisplay.length === 1) { + sourceToDisplay = sourceToDisplay[0]; + } + + let defaultOverrideHoverContent; + if (!Array.isArray(sourceToDisplay)) { + defaultOverrideHoverContent = localize('defaultOverriddenDetails', "Default setting value overridden by `{0}`", sourceToDisplay); + } else { + sourceToDisplay = sourceToDisplay.map(source => `\`${source}\``); + defaultOverrideHoverContent = localize('multipledefaultOverriddenDetails', "A default values has been set by {0}", sourceToDisplay.slice(0, -1).join(', ') + ' & ' + sourceToDisplay.slice(-1)); + } + const showHover = (focus: boolean) => { return this.hoverService.showHover({ - content: defaultOverrideHoverContent, + content: new MarkdownString().appendMarkdown(defaultOverrideHoverContent), target: this.defaultOverrideIndicator.element, position: { hoverPosition: HoverPosition.BELOW, @@ -473,14 +485,22 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { } } -function getDefaultValueSourceToDisplay(element: SettingsTreeSettingElement): string | undefined { - let sourceToDisplay: string | undefined; +function getDefaultValueSourceToDisplay(element: SettingsTreeSettingElement): string | undefined | string[] { + let sourceToDisplay: string | undefined | string[]; const defaultValueSource = element.defaultValueSource; if (defaultValueSource) { - if (typeof defaultValueSource !== 'string') { - sourceToDisplay = defaultValueSource.displayName ?? defaultValueSource.id; + if (defaultValueSource instanceof Map) { + sourceToDisplay = []; + for (const [, value] of defaultValueSource) { + const newValue = typeof value !== 'string' ? value.displayName ?? value.id : value; + if (!sourceToDisplay.includes(newValue)) { + sourceToDisplay.push(newValue); + } + } } else if (typeof defaultValueSource === 'string') { sourceToDisplay = defaultValueSource; + } else { + sourceToDisplay = defaultValueSource.displayName ?? defaultValueSource.id; } } return sourceToDisplay; @@ -538,9 +558,19 @@ export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, } // Add default override indicator text - const sourceToDisplay = getDefaultValueSourceToDisplay(element); + let sourceToDisplay = getDefaultValueSourceToDisplay(element); if (sourceToDisplay !== undefined) { - ariaLabelSections.push(localize('defaultOverriddenDetailsAriaLabel', "{0} overrides the default value", sourceToDisplay)); + if (Array.isArray(sourceToDisplay) && sourceToDisplay.length === 1) { + sourceToDisplay = sourceToDisplay[0]; + } + + let overriddenDetailsText; + if (!Array.isArray(sourceToDisplay)) { + overriddenDetailsText = localize('defaultOverriddenDetailsAriaLabel', "{0} overrides the default value", sourceToDisplay); + } else { + overriddenDetailsText = localize('multipleDefaultOverriddenDetailsAriaLabel', "{0} override the default value", sourceToDisplay.slice(0, -1).join(', ') + ' & ' + sourceToDisplay.slice(-1)); + } + ariaLabelSections.push(overriddenDetailsText); } // Add text about default values being overridden in other languages diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index a47b4ff6..0073edc8 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -235,6 +235,11 @@ export const tocData: ITOCEntry = { id: 'features/chat', label: localize('chat', 'Chat'), settings: ['chat.*', 'inlineChat.*'] + }, + { + id: 'features/issueReporter', + label: localize('issueReporter', 'Issue Reporter'), + settings: ['issueReporter.*'] } ] }, diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 80ce14bc..95dabc4d 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -60,28 +60,42 @@ import { settingsMoreActionIcon } from 'vs/workbench/contrib/preferences/browser import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { ISettingOverrideClickEvent, SettingsTreeIndicatorsLabel, getIndicatorsLabelAriaLabel } from 'vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; -import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement, inspectSetting, settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; -import { ExcludeSettingWidget, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, ISettingListChangeEvent, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; +import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement, inspectSetting, objectSettingSupportsRemoveDefaultValue, settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; +import { ExcludeSettingWidget, IBoolObjectDataItem, IIncludeExcludeDataItem, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue, SettingListEvent } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { LANGUAGE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, compareTwoNullableNumbers } from 'vs/workbench/contrib/preferences/common/preferences'; import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; +import { ISetting, ISettingsGroup, SETTINGS_AUTHORITY, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { getInvalidTypeError } from 'vs/workbench/services/preferences/common/preferencesValidation'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { URI } from 'vs/base/common/uri'; const $ = DOM.$; -function getIncludeExcludeDisplayValue(element: SettingsTreeSettingElement): IListDataItem[] { +function getIncludeExcludeDisplayValue(element: SettingsTreeSettingElement): IIncludeExcludeDataItem[] { + const elementDefaultValue: Record = typeof element.defaultValue === 'object' + ? element.defaultValue ?? {} + : {}; + const data = element.isConfigured ? - { ...element.defaultValue, ...element.scopeValue } : - element.defaultValue; + { ...elementDefaultValue, ...element.scopeValue } : + elementDefaultValue; return Object.keys(data) .filter(key => !!data[key]) .map(key => { + const defaultValue = elementDefaultValue[key]; + + // Get source if it's a default value + let source: string | undefined; + if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) { + const defaultSource = element.defaultValueSource.get(`${element.setting.key}.${key}`); + source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName; + } + const value = data[key]; const sibling = typeof value === 'boolean' ? undefined : value.when; return { @@ -90,7 +104,8 @@ function getIncludeExcludeDisplayValue(element: SettingsTreeSettingElement): ILi data: key }, sibling, - elementType: element.valueType + elementType: element.valueType, + source }; }); } @@ -135,6 +150,16 @@ function getObjectValueType(schema: IJSONSchema): ObjectValue['type'] { } } +function getObjectEntryValueDisplayValue(type: ObjectValue['type'], data: unknown, options: IObjectEnumOption[]): ObjectValue { + if (type === 'boolean') { + return { type, data: !!data }; + } else if (type === 'enum') { + return { type, data: '' + data, options }; + } else { + return { type, data: '' + data }; + } +} + function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectDataItem[] { const elementDefaultValue: Record = typeof element.defaultValue === 'object' ? element.defaultValue ?? {} @@ -162,22 +187,15 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData return Object.keys(data).map(key => { const defaultValue = elementDefaultValue[key]; - if (isDefined(objectProperties) && key in objectProperties) { - if (element.setting.allKeysAreBoolean) { - return { - key: { - type: 'string', - data: key - }, - value: { - type: 'boolean', - data: data[key] - }, - keyDescription: objectProperties[key].description, - removable: false - } as IObjectDataItem; - } + // Get source if it's a default value + let source: string | undefined; + if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) { + const defaultSource = element.defaultValueSource.get(`${element.setting.key}.${key}`); + source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName; + } + + if (isDefined(objectProperties) && key in objectProperties) { const valueEnumOptions = getEnumOptionsFromSchema(objectProperties[key]); return { key: { @@ -185,32 +203,29 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData data: key, options: wellDefinedKeyEnumOptions, }, - value: { - type: getObjectValueType(objectProperties[key]), - data: data[key], - options: valueEnumOptions, - }, + value: getObjectEntryValueDisplayValue(getObjectValueType(objectProperties[key]), data[key], valueEnumOptions), keyDescription: objectProperties[key].description, removable: isUndefinedOrNull(defaultValue), - } as IObjectDataItem; + resetable: !isUndefinedOrNull(defaultValue), + source + } satisfies IObjectDataItem; } - // The row is removable if it doesn't have a default value assigned. - // Otherwise, it is not removable, but its value can be reset to the default. - const removable = !defaultValue; + // The row is removable if it doesn't have a default value assigned or the setting supports removing the default value. + // If a default value is assigned and the user modified the default, it can be reset back to the default. + const removable = defaultValue === undefined || objectSettingSupportsRemoveDefaultValue(element.setting.key); + const resetable = !!defaultValue && defaultValue !== data[key]; const schema = patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema; if (schema) { const valueEnumOptions = getEnumOptionsFromSchema(schema); return { key: { type: 'string', data: key }, - value: { - type: getObjectValueType(schema), - data: data[key], - options: valueEnumOptions, - }, + value: getObjectEntryValueDisplayValue(getObjectValueType(schema), data[key], valueEnumOptions), keyDescription: schema.description, removable, - } as IObjectDataItem; + resetable, + source + } satisfies IObjectDataItem; } const additionalValueEnums = getEnumOptionsFromSchema( @@ -221,17 +236,62 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData return { key: { type: 'string', data: key }, - value: { - type: typeof objectAdditionalProperties === 'object' ? getObjectValueType(objectAdditionalProperties) : 'string', - data: data[key], - options: additionalValueEnums, - }, + value: getObjectEntryValueDisplayValue( + typeof objectAdditionalProperties === 'object' ? getObjectValueType(objectAdditionalProperties) : 'string', + data[key], + additionalValueEnums, + ), keyDescription: typeof objectAdditionalProperties === 'object' ? objectAdditionalProperties.description : undefined, removable, - } as IObjectDataItem; + resetable, + source + } satisfies IObjectDataItem; }).filter(item => !isUndefinedOrNull(item.value.data)); } +function getBoolObjectDisplayValue(element: SettingsTreeSettingElement): IBoolObjectDataItem[] { + const elementDefaultValue: Record = typeof element.defaultValue === 'object' + ? element.defaultValue ?? {} + : {}; + + const elementScopeValue: Record = typeof element.scopeValue === 'object' + ? element.scopeValue ?? {} + : {}; + + const data = element.isConfigured ? + { ...elementDefaultValue, ...elementScopeValue } : + elementDefaultValue; + + const { objectProperties } = element.setting; + const displayValues: IBoolObjectDataItem[] = []; + for (const key in objectProperties) { + const defaultValue = elementDefaultValue[key]; + + // Get source if it's a default value + let source: string | undefined; + if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) { + const defaultSource = element.defaultValueSource.get(key); + source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName; + } + + displayValues.push({ + key: { + type: 'string', + data: key + }, + value: { + type: 'boolean', + data: !!data[key] + }, + keyDescription: objectProperties[key].description, + removable: false, + resetable: true, + source + }); + } + return displayValues; +} + function createArraySuggester(element: SettingsTreeSettingElement): IObjectKeySuggester { return (keys, idx) => { const enumOptions: IObjectEnumOption[] = []; @@ -608,6 +668,7 @@ interface ISettingBoolItemTemplate extends ISettingItemTemplate { interface ISettingExtensionToggleItemTemplate extends ISettingItemTemplate { actionButton: Button; + dismissButton: Button; } interface ISettingTextItemTemplate extends ISettingItemTemplate { @@ -629,12 +690,12 @@ interface ISettingComplexItemTemplate extends ISettingItemTemplate { } interface ISettingListItemTemplate extends ISettingItemTemplate { - listWidget: ListSettingWidget; + listWidget: ListSettingWidget; validationErrorMessageElement: HTMLElement; } interface ISettingIncludeExcludeItemTemplate extends ISettingItemTemplate { - includeExcludeWidget: ListSettingWidget; + includeExcludeWidget: ListSettingWidget; } interface ISettingObjectItemTemplate extends ISettingItemTemplate | undefined> { @@ -719,9 +780,9 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre abstract get templateId(): string; static readonly CONTROL_CLASS = 'setting-control-focus-target'; - static readonly CONTROL_SELECTOR = '.' + AbstractSettingRenderer.CONTROL_CLASS; + static readonly CONTROL_SELECTOR = '.' + this.CONTROL_CLASS; static readonly CONTENTS_CLASS = 'setting-item-contents'; - static readonly CONTENTS_SELECTOR = '.' + AbstractSettingRenderer.CONTENTS_CLASS; + static readonly CONTENTS_SELECTOR = '.' + this.CONTENTS_CLASS; static readonly ALL_ROWS_SELECTOR = '.monaco-list-row'; static readonly SETTING_KEY_ATTR = 'data-key'; @@ -805,7 +866,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const descriptionElement = DOM.append(container, $('.setting-item-description')); const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); - toDispose.add(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), modifiedIndicatorElement, () => localize('modified', "The setting has been configured in the current scope."))); + toDispose.add(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), modifiedIndicatorElement, () => localize('modified', "The setting has been configured in the current scope."))); const valueElement = DOM.append(container, $('.setting-item-value')); const controlElement = DOM.append(valueElement, $('div.setting-item-control')); @@ -892,7 +953,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : ''); template.categoryElement.textContent = element.displayCategory ? (element.displayCategory + ': ') : ''; - template.elementDisposables.add(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), template.categoryElement, titleTooltip)); + template.elementDisposables.add(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), template.categoryElement, titleTooltip)); template.labelElement.text = element.displayLabel; template.labelElement.title = titleTooltip; @@ -996,7 +1057,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } } -export class SettingGroupRenderer implements ITreeRenderer { +class SettingGroupRenderer implements ITreeRenderer { templateId = SETTINGS_ELEMENT_TEMPLATE_ID; renderTemplate(container: HTMLElement): IGroupTitleTemplate { @@ -1174,7 +1235,7 @@ class SettingArrayRenderer extends AbstractSettingRenderer implements ITreeRende return template; } - private computeNewList(template: ISettingListItemTemplate, e: ISettingListChangeEvent): string[] | undefined { + private computeNewList(template: ISettingListItemTemplate, e: SettingListEvent): string[] | undefined { if (template.context) { let newValue: string[] = []; if (Array.isArray(template.context.scopeValue)) { @@ -1183,33 +1244,28 @@ class SettingArrayRenderer extends AbstractSettingRenderer implements ITreeRende newValue = [...template.context.value]; } - if (e.sourceIndex !== undefined) { + if (e.type === 'move') { // A drag and drop occurred const sourceIndex = e.sourceIndex; - const targetIndex = e.targetIndex!; + const targetIndex = e.targetIndex; const splicedElem = newValue.splice(sourceIndex, 1)[0]; newValue.splice(targetIndex, 0, splicedElem); - } else if (e.targetIndex !== undefined) { - const itemValueData = e.item?.value.data.toString() ?? ''; - // Delete value - if (!e.item?.value.data && e.originalItem.value.data && e.targetIndex > -1) { - newValue.splice(e.targetIndex, 1); - } + } else if (e.type === 'remove' || e.type === 'reset') { + newValue.splice(e.targetIndex, 1); + } else if (e.type === 'change') { + const itemValueData = e.newItem.value.data.toString(); + // Update value - else if (e.item?.value.data && e.originalItem.value.data) { - if (e.targetIndex > -1) { - newValue[e.targetIndex] = itemValueData; - } - // For some reason, we are updating and cannot find original value - // Just append the value in this case - else { - newValue.push(itemValueData); - } + if (e.targetIndex > -1) { + newValue[e.targetIndex] = itemValueData; } - // Add value - else if (e.item?.value.data && !e.originalItem.value.data && e.targetIndex >= newValue.length) { + // For some reason, we are updating and cannot find original value + // Just append the value in this case + else { newValue.push(itemValueData); } + } else if (e.type === 'add') { + newValue.push(e.newItem.value.data.toString()); } if ( @@ -1280,17 +1336,31 @@ abstract class AbstractSettingObjectRenderer extends AbstractSettingRenderer imp } this.addSettingElementFocusHandler(template); + return template; + } + renderElement(element: ITreeNode, index: number, templateData: ISettingObjectItemTemplate): void { + super.renderSettingElement(element, index, templateData); + } +} + +class SettingObjectRenderer extends AbstractSettingObjectRenderer implements ITreeRenderer { + override templateId = SETTINGS_OBJECT_TEMPLATE_ID; + + renderTemplate(container: HTMLElement): ISettingObjectItemTemplate { + const common = this.renderCommonTemplate(null, container, 'list'); + const widget = this._instantiationService.createInstance(ObjectSettingDropdownWidget, common.controlElement); + const template = this.renderTemplateWithWidget(common, widget); common.toDispose.add(widget.onDidChangeList(e => { this.onDidChangeObject(template, e); })); - return template; } - protected onDidChangeObject(template: ISettingObjectItemTemplate, e: ISettingListChangeEvent): void { - const widget = (template.objectCheckboxWidget ?? template.objectDropdownWidget)!; + private onDidChangeObject(template: ISettingObjectItemTemplate, e: SettingListEvent): void { + const widget = template.objectDropdownWidget!; if (template.context) { + const settingSupportsRemoveDefault = objectSettingSupportsRemoveDefaultValue(template.context.setting.key); const defaultValue: Record = typeof template.context.defaultValue === 'object' ? template.context.defaultValue ?? {} : {}; @@ -1299,75 +1369,67 @@ abstract class AbstractSettingObjectRenderer extends AbstractSettingRenderer imp ? template.context.scopeValue ?? {} : {}; - const newValue: Record = {}; + const newValue: Record = { ...template.context.scopeValue }; // Initialize with scoped values as removed default values are not rendered const newItems: IObjectDataItem[] = []; widget.items.forEach((item, idx) => { // Item was updated - if (isDefined(e.item) && e.targetIndex === idx) { - newValue[e.item.key.data] = e.item.value.data; - newItems.push(e.item); + if ((e.type === 'change' || e.type === 'move') && e.targetIndex === idx) { + // If the key of the default value is changed, remove the default value + if (e.originalItem.key.data !== e.newItem.key.data && settingSupportsRemoveDefault && e.originalItem.key.data in defaultValue) { + newValue[e.originalItem.key.data] = null; + } else { + delete newValue[e.originalItem.key.data]; + } + newValue[e.newItem.key.data] = e.newItem.value.data; + newItems.push(e.newItem); } // All remaining items, but skip the one that we just updated - else if (isUndefinedOrNull(e.item) || e.item.key.data !== item.key.data) { + else if ((e.type !== 'change' && e.type !== 'move') || e.newItem.key.data !== item.key.data) { newValue[item.key.data] = item.value.data; newItems.push(item); } }); // Item was deleted - if (isUndefinedOrNull(e.item)) { - delete newValue[e.originalItem.key.data]; + if (e.type === 'remove' || e.type === 'reset') { + const objectKey = e.originalItem.key.data; + const removingDefaultValue = e.type === 'remove' && settingSupportsRemoveDefault && defaultValue[objectKey] === e.originalItem.value.data; + if (removingDefaultValue) { + newValue[objectKey] = null; + } else { + delete newValue[objectKey]; + } - const itemToDelete = newItems.findIndex(item => item.key.data === e.originalItem.key.data); - const defaultItemValue = defaultValue[e.originalItem.key.data] as string | boolean; + const itemToDelete = newItems.findIndex(item => item.key.data === objectKey); + const defaultItemValue = defaultValue[objectKey] as string | boolean; - // Item does not have a default - if (isUndefinedOrNull(defaultValue[e.originalItem.key.data]) && itemToDelete > -1) { + // Item does not have a default or default is bing removed + if (removingDefaultValue || isUndefinedOrNull(defaultValue[objectKey]) && itemToDelete > -1) { newItems.splice(itemToDelete, 1); - } else if (itemToDelete > -1) { + } else if (!removingDefaultValue && itemToDelete > -1) { newItems[itemToDelete].value.data = defaultItemValue; } } // New item was added - else if (widget.isItemNew(e.originalItem) && e.item.key.data !== '') { - newValue[e.item.key.data] = e.item.value.data; - newItems.push(e.item); + else if (e.type === 'add') { + newValue[e.newItem.key.data] = e.newItem.value.data; + newItems.push(e.newItem); } Object.entries(newValue).forEach(([key, value]) => { // value from the scope has changed back to the default - if (scopeValue[key] !== value && defaultValue[key] === value) { + if (scopeValue[key] !== value && defaultValue[key] === value && !(settingSupportsRemoveDefault && value === null)) { delete newValue[key]; } }); const newObject = Object.keys(newValue).length === 0 ? undefined : newValue; - - if (template.objectCheckboxWidget) { - template.objectCheckboxWidget.setValue(newItems); - } else { - template.objectDropdownWidget!.setValue(newItems); - } - + template.objectDropdownWidget!.setValue(newItems); template.onChange?.(newObject); } } - renderElement(element: ITreeNode, index: number, templateData: ISettingObjectItemTemplate): void { - super.renderSettingElement(element, index, templateData); - } -} - -class SettingObjectRenderer extends AbstractSettingObjectRenderer implements ITreeRenderer { - override templateId = SETTINGS_OBJECT_TEMPLATE_ID; - - renderTemplate(container: HTMLElement): ISettingObjectItemTemplate { - const common = this.renderCommonTemplate(null, container, 'list'); - const widget = this._instantiationService.createInstance(ObjectSettingDropdownWidget, common.controlElement); - return this.renderTemplateWithWidget(common, widget); - } - protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingObjectItemTemplate, onChange: (value: Record | undefined) => void): void { const items = getObjectDisplayValue(dataElement); const { key, objectProperties, objectPatternProperties, objectAdditionalProperties } = dataElement.setting; @@ -1410,12 +1472,55 @@ class SettingBoolObjectRenderer extends AbstractSettingObjectRenderer implements renderTemplate(container: HTMLElement): ISettingObjectItemTemplate { const common = this.renderCommonTemplate(null, container, 'list'); const widget = this._instantiationService.createInstance(ObjectSettingCheckboxWidget, common.controlElement); - return this.renderTemplateWithWidget(common, widget); + const template = this.renderTemplateWithWidget(common, widget); + common.toDispose.add(widget.onDidChangeList(e => { + this.onDidChangeObject(template, e); + })); + return template; } - protected override onDidChangeObject(template: ISettingObjectItemTemplate, e: ISettingListChangeEvent): void { + protected onDidChangeObject(template: ISettingObjectItemTemplate, e: SettingListEvent): void { if (template.context) { - super.onDidChangeObject(template, e); + const widget = template.objectCheckboxWidget!; + const defaultValue: Record = typeof template.context.defaultValue === 'object' + ? template.context.defaultValue ?? {} + : {}; + + const scopeValue: Record = typeof template.context.scopeValue === 'object' + ? template.context.scopeValue ?? {} + : {}; + + const newValue: Record = { ...template.context.scopeValue }; // Initialize with scoped values as removed default values are not rendered + const newItems: IBoolObjectDataItem[] = []; + + if (e.type !== 'change') { + console.warn('Unexpected event type', e.type, 'for bool object setting', template.context.setting.key); + return; + } + + widget.items.forEach((item, idx) => { + // Item was updated + if (e.targetIndex === idx) { + newValue[e.newItem.key.data] = e.newItem.value.data; + newItems.push(e.newItem); + } + // All remaining items, but skip the one that we just updated + else if (e.newItem.key.data !== item.key.data) { + newValue[item.key.data] = item.value.data; + newItems.push(item); + } + }); + + Object.entries(newValue).forEach(([key, value]) => { + // value from the scope has changed back to the default + if (scopeValue[key] !== value && defaultValue[key] === value) { + delete newValue[key]; + } + }); + + const newObject = Object.keys(newValue).length === 0 ? undefined : newValue; + template.objectCheckboxWidget!.setValue(newItems); + template.onChange?.(newObject); // Focus this setting explicitly, in case we were previously // focused on another setting and clicked a checkbox/value container @@ -1425,7 +1530,7 @@ class SettingBoolObjectRenderer extends AbstractSettingObjectRenderer implements } protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingObjectItemTemplate, onChange: (value: Record | undefined) => void): void { - const items = getObjectDisplayValue(dataElement); + const items = getBoolObjectDisplayValue(dataElement); const { key } = dataElement.setting; template.objectCheckboxWidget!.setValue(items, { @@ -1462,25 +1567,27 @@ abstract class SettingIncludeExcludeRenderer extends AbstractSettingRenderer imp return template; } - private onDidChangeIncludeExclude(template: ISettingIncludeExcludeItemTemplate, e: ISettingListChangeEvent): void { + private onDidChangeIncludeExclude(template: ISettingIncludeExcludeItemTemplate, e: SettingListEvent): void { if (template.context) { const newValue = { ...template.context.scopeValue }; // first delete the existing entry, if present - if (e.originalItem.value.data.toString() in template.context.defaultValue) { - // delete a default by overriding it - newValue[e.originalItem.value.data.toString()] = false; - } else { - delete newValue[e.originalItem.value.data.toString()]; + if (e.type !== 'add') { + if (e.originalItem.value.data.toString() in template.context.defaultValue) { + // delete a default by overriding it + newValue[e.originalItem.value.data.toString()] = false; + } else { + delete newValue[e.originalItem.value.data.toString()]; + } } // then add the new or updated entry, if present - if (e.item?.value) { - if (e.item.value.data.toString() in template.context.defaultValue && !e.item.sibling) { + if (e.type === 'change' || e.type === 'add' || e.type === 'move') { + if (e.newItem.value.data.toString() in template.context.defaultValue && !e.newItem.sibling) { // add a default by deleting its override - delete newValue[e.item.value.data.toString()]; + delete newValue[e.newItem.value.data.toString()]; } else { - newValue[e.item.value.data.toString()] = e.item.sibling ? { when: e.item.sibling } : true; + newValue[e.newItem.value.data.toString()] = e.newItem.sibling ? { when: e.newItem.sibling } : true; } } @@ -1519,7 +1626,7 @@ abstract class SettingIncludeExcludeRenderer extends AbstractSettingRenderer imp } } -export class SettingExcludeRenderer extends SettingIncludeExcludeRenderer { +class SettingExcludeRenderer extends SettingIncludeExcludeRenderer { templateId = SETTINGS_EXCLUDE_TEMPLATE_ID; protected override isExclude(): boolean { @@ -1527,7 +1634,7 @@ export class SettingExcludeRenderer extends SettingIncludeExcludeRenderer { } } -export class SettingIncludeRenderer extends SettingIncludeExcludeRenderer { +class SettingIncludeRenderer extends SettingIncludeExcludeRenderer { templateId = SETTINGS_INCLUDE_TEMPLATE_ID; protected override isExclude(): boolean { @@ -1642,7 +1749,7 @@ class SettingMultilineTextRenderer extends AbstractSettingTextRenderer implement } } -export class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRenderer { +class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRenderer { templateId = SETTINGS_ENUM_TEMPLATE_ID; renderTemplate(container: HTMLElement): ISettingEnumItemTemplate { @@ -1698,7 +1805,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre const enumDescriptionsAreMarkdown = dataElement.setting.enumDescriptionsAreMarkdown; const disposables = new DisposableStore(); - template.toDispose.add(disposables); + template.elementDisposables.add(disposables); let createdDefault = false; if (!settingEnum.includes(dataElement.defaultValue)) { @@ -1759,7 +1866,7 @@ const settingsNumberInputBoxStyles = getInputBoxStyle({ inputBorder: settingsNumberInputBorder }); -export class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer { +class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer { templateId = SETTINGS_NUMBER_TEMPLATE_ID; renderTemplate(_container: HTMLElement): ISettingNumberItemTemplate { @@ -1813,7 +1920,7 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT } } -export class SettingBoolRenderer extends AbstractSettingRenderer implements ITreeRenderer { +class SettingBoolRenderer extends AbstractSettingRenderer implements ITreeRenderer { templateId = SETTINGS_BOOL_TEMPLATE_ID; renderTemplate(_container: HTMLElement): ISettingBoolItemTemplate { @@ -1835,7 +1942,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control')); const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description')); const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); - toDispose.add(this._hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), modifiedIndicatorElement, localize('modified', "The setting has been configured in the current scope."))); + toDispose.add(this._hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), modifiedIndicatorElement, localize('modified', "The setting has been configured in the current scope."))); const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); @@ -1905,12 +2012,15 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre type ManageExtensionClickTelemetryClassification = { extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension the user went to manage.' }; owner: 'rzhao271'; - comment: 'Event used to gain insights into when users are using an experimental extension management setting'; + comment: 'Event used to gain insights into when users interact with an extension management setting'; }; -export class SettingsExtensionToggleRenderer extends AbstractSettingRenderer implements ITreeRenderer { +class SettingsExtensionToggleRenderer extends AbstractSettingRenderer implements ITreeRenderer { templateId = SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID; + private readonly _onDidDismissExtensionSetting = this._register(new Emitter()); + readonly onDidDismissExtensionSetting = this._onDidDismissExtensionSetting.event; + renderTemplate(_container: HTMLElement): ISettingExtensionToggleItemTemplate { const common = super.renderCommonTemplate(null, _container, 'extension-toggle'); @@ -1921,9 +2031,18 @@ export class SettingsExtensionToggleRenderer extends AbstractSettingRenderer imp actionButton.element.classList.add('setting-item-extension-toggle-button'); actionButton.label = localize('showExtension', "Show Extension"); + const dismissButton = new Button(common.containerElement, { + title: false, + secondary: true, + ...defaultButtonStyles + }); + dismissButton.element.classList.add('setting-item-extension-dismiss-button'); + dismissButton.label = localize('dismiss', "Dismiss"); + const template: ISettingExtensionToggleItemTemplate = { ...common, - actionButton + actionButton, + dismissButton }; this.addSettingElementFocusHandler(template); @@ -1943,15 +2062,22 @@ export class SettingsExtensionToggleRenderer extends AbstractSettingRenderer imp this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('ManageExtensionClick', { extensionId }); this._commandService.executeCommand('extension.open', extensionId); })); + + template.elementDisposables.add(template.dismissButton.onDidClick(async () => { + this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('DismissExtensionClick', { extensionId }); + this._onDidDismissExtensionSetting.fire(extensionId); + })); } } -export class SettingTreeRenderers { +export class SettingTreeRenderers extends Disposable { readonly onDidClickOverrideElement: Event; - private readonly _onDidChangeSetting = new Emitter(); + private readonly _onDidChangeSetting = this._register(new Emitter()); readonly onDidChangeSetting: Event; + readonly onDidDismissExtensionSetting: Event; + readonly onDidOpenSettings: Event; readonly onDidClickSettingLink: Event; @@ -1973,6 +2099,7 @@ export class SettingTreeRenderers { @IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService, @IUserDataSyncEnablementService private readonly _userDataSyncEnablementService: IUserDataSyncEnablementService, ) { + super(); this.settingActions = [ new Action('settings.resetSetting', localize('resetSettingLabel', "Reset Setting"), undefined, undefined, async context => { if (context instanceof SettingsTreeSettingElement) { @@ -1990,10 +2117,12 @@ export class SettingTreeRenderers { new Separator(), this._instantiationService.createInstance(CopySettingIdAction), this._instantiationService.createInstance(CopySettingAsJSONAction), + this._instantiationService.createInstance(CopySettingAsURLAction), ]; const actionFactory = (setting: ISetting, settingTarget: SettingsTarget) => this.getActionsForSetting(setting, settingTarget); const emptyActionFactory = (_: ISetting) => []; + const extensionRenderer = this._instantiationService.createInstance(SettingsExtensionToggleRenderer, [], emptyActionFactory); const settingRenderers = [ this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions, actionFactory), @@ -2006,7 +2135,7 @@ export class SettingTreeRenderers { this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingObjectRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingBoolObjectRenderer, this.settingActions, actionFactory), - this._instantiationService.createInstance(SettingsExtensionToggleRenderer, [], emptyActionFactory) + extensionRenderer ]; this.onDidClickOverrideElement = Event.any(...settingRenderers.map(r => r.onDidClickOverrideElement)); @@ -2014,6 +2143,7 @@ export class SettingTreeRenderers { ...settingRenderers.map(r => r.onDidChangeSetting), this._onDidChangeSetting.event ); + this.onDidDismissExtensionSetting = extensionRenderer.onDidDismissExtensionSetting; this.onDidOpenSettings = Event.any(...settingRenderers.map(r => r.onDidOpenSettings)); this.onDidClickSettingLink = Event.any(...settingRenderers.map(r => r.onDidClickSettingLink)); this.onDidFocusSetting = Event.any(...settingRenderers.map(r => r.onDidFocusSetting)); @@ -2078,6 +2208,20 @@ export class SettingTreeRenderers { const settingElement = this.getSettingDOMElementForDOMElement(element); return settingElement && settingElement.getAttribute(AbstractSettingRenderer.SETTING_ID_ATTR); } + + override dispose(): void { + super.dispose(); + this.settingActions.forEach(action => { + if (isDisposable(action)) { + action.dispose(); + } + }); + this.allRenderers.forEach(renderer => { + if (isDisposable(renderer)) { + renderer.dispose(); + } + }); + } } /** @@ -2134,7 +2278,7 @@ function cleanRenderedMarkdown(element: Node): void { const tagName = (child).tagName && (child).tagName.toLowerCase(); if (tagName === 'img') { - element.removeChild(child); + child.remove(); } else { cleanRenderedMarkdown(child); } @@ -2459,6 +2603,29 @@ class CopySettingAsJSONAction extends Action { } } +class CopySettingAsURLAction extends Action { + static readonly ID = 'settings.copySettingAsURL'; + static readonly LABEL = localize('copySettingAsURLLabel', "Copy Setting as URL"); + + constructor( + @IClipboardService private readonly clipboardService: IClipboardService, + @IProductService private readonly productService: IProductService, + ) { + super(CopySettingAsURLAction.ID, CopySettingAsURLAction.LABEL); + } + + override async run(context: SettingsTreeSettingElement): Promise { + if (context) { + const settingKey = context.setting.key; + const product = this.productService.urlProtocol; + const uri = URI.from({ scheme: product, authority: SETTINGS_AUTHORITY, path: `/${settingKey}` }, true); + await this.clipboardService.writeText(uri.toString()); + } + + return Promise.resolve(undefined); + } +} + class SyncSettingAction extends Action { static readonly ID = 'settings.stopSyncingSetting'; static readonly LABEL = localize('stopSyncingSetting', "Sync This Setting"); diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index e30c3bb5..eccc7360 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -17,7 +17,7 @@ import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_S import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { ConfigurationScope, EditPresentationTypes, Extensions, IConfigurationRegistry, IExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationDefaultValueSource, ConfigurationScope, EditPresentationTypes, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { Registry } from 'vs/platform/registry/common/platform'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; @@ -135,7 +135,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { * The source of the default value to display. * This value also accounts for extension-contributed language-specific default value overrides. */ - defaultValueSource: string | IExtensionInfo | undefined; + defaultValueSource: ConfigurationDefaultValueSource | undefined; /** * Whether the setting is configured in the selected scope. @@ -203,7 +203,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { private initLabels(): void { if (this.setting.title) { this._displayLabel = this.setting.title; - this._displayCategory = ''; + this._displayCategory = this.setting.categoryLabel ?? null; return; } const displayKeyFormat = settingKeyToDisplayFormat(this.setting.key, this.parent!.id, this.setting.isLanguageTagSetting); @@ -351,7 +351,8 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { this.defaultValue = overrideValues.defaultValue ?? inspected.defaultValue; const registryValues = Registry.as(Extensions.Configuration).getConfigurationDefaultsOverrides(); - const overrideValueSource = registryValues.get(`[${languageSelector}]`)?.valuesSources?.get(this.setting.key); + const source = registryValues.get(`[${languageSelector}]`)?.source; + const overrideValueSource = source instanceof Map ? source.get(this.setting.key) : undefined; if (overrideValueSource) { this.defaultValueSource = overrideValueSource; } @@ -792,11 +793,25 @@ function isIncludeSetting(setting: ISetting): boolean { return setting.key === 'files.readonlyInclude'; } -function isObjectRenderableSchema({ type }: IJSONSchema): boolean { - return type === 'string' || type === 'boolean' || type === 'integer' || type === 'number'; +// The values of the following settings when a default values has been removed +export function objectSettingSupportsRemoveDefaultValue(key: string): boolean { + return key === 'workbench.editor.customLabels.patterns'; +} + +function isObjectRenderableSchema({ type }: IJSONSchema, key: string): boolean { + if (type === 'string' || type === 'boolean' || type === 'integer' || type === 'number') { + return true; + } + + if (objectSettingSupportsRemoveDefaultValue(key) && Array.isArray(type) && type.length === 2) { + return type.includes('null') && (type.includes('string') || type.includes('boolean') || type.includes('integer') || type.includes('number')); + } + + return false; } function isObjectSetting({ + key, type, objectProperties, objectPatternProperties, @@ -838,7 +853,7 @@ function isObjectSetting({ return [schema]; }).flat(); - return flatSchemas.every(isObjectRenderableSchema); + return flatSchemas.every((schema) => isObjectRenderableSchema(schema, key)); } function settingTypeEnumRenderable(_type: string | string[]) { diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index c2f0f7b7..d8feba0e 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -29,6 +29,9 @@ import { settingsSelectBackground, settingsSelectBorder, settingsSelectForegroun import { defaultButtonStyles, getInputBoxStyle, getSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { IManagedHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; +import { SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; const $ = DOM.$; @@ -110,21 +113,49 @@ export class ListSettingListModel { } export interface ISettingListChangeEvent { + type: 'change'; originalItem: TDataItem; - item?: TDataItem; - targetIndex?: number; - sourceIndex?: number; + newItem: TDataItem; + targetIndex: number; } +export interface ISettingListAddEvent { + type: 'add'; + newItem: TDataItem; + targetIndex: number; +} + +export interface ISettingListMoveEvent { + type: 'move'; + originalItem: TDataItem; + newItem: TDataItem; + targetIndex: number; + sourceIndex: number; +} + +export interface ISettingListRemoveEvent { + type: 'remove'; + originalItem: TDataItem; + targetIndex: number; +} + +export interface ISettingListResetEvent { + type: 'reset'; + originalItem: TDataItem; + targetIndex: number; +} + +export type SettingListEvent = ISettingListChangeEvent | ISettingListAddEvent | ISettingListMoveEvent | ISettingListRemoveEvent | ISettingListResetEvent; + export abstract class AbstractListSettingWidget extends Disposable { private listElement: HTMLElement; private rowElements: HTMLElement[] = []; - protected readonly _onDidChangeList = this._register(new Emitter>()); + protected readonly _onDidChangeList = this._register(new Emitter>()); protected readonly model = new ListSettingListModel(this.getEmptyItem()); protected readonly listDisposables = this._register(new DisposableStore()); - readonly onDidChangeList: Event> = this._onDidChangeList.event; + readonly onDidChangeList: Event> = this._onDidChangeList.event; get domNode(): HTMLElement { return this.listElement; @@ -217,6 +248,7 @@ export abstract class AbstractListSettingWidget extend this.rowElements = this.model.items.map((item, i) => this.renderDataOrEditItem(item, i, focused)); this.rowElements.forEach(rowElement => this.listElement.appendChild(rowElement)); + } protected createBasicSelectBox(value: IObjectEnumData): SelectBox { @@ -250,11 +282,20 @@ export abstract class AbstractListSettingWidget extend protected handleItemChange(originalItem: TDataItem, changedItem: TDataItem, idx: number) { this.model.setEditKey('none'); - this._onDidChangeList.fire({ - originalItem, - item: changedItem, - targetIndex: idx, - }); + if (this.isItemNew(originalItem)) { + this._onDidChangeList.fire({ + type: 'add', + newItem: changedItem, + targetIndex: idx, + }); + } else { + this._onDidChangeList.fire({ + type: 'change', + originalItem, + newItem: changedItem, + targetIndex: idx, + }); + } this.renderList(); } @@ -396,17 +437,17 @@ export interface IListDataItem { sibling?: string; } -interface ListSettingWidgetDragDetails { +interface ListSettingWidgetDragDetails { element: HTMLElement; - item: IListDataItem; + item: TListDataItem; itemIndex: number; } -export class ListSettingWidget extends AbstractListSettingWidget { +export class ListSettingWidget extends AbstractListSettingWidget { private keyValueSuggester: IObjectKeySuggester | undefined; private showAddButton: boolean = true; - override setValue(listData: IListDataItem[], options?: IListSetValueOptions) { + override setValue(listData: TListDataItem[], options?: IListSetValueOptions) { this.keyValueSuggester = options?.keySuggester; this.showAddButton = options?.showAddButton ?? true; super.setValue(listData); @@ -421,13 +462,13 @@ export class ListSettingWidget extends AbstractListSettingWidget super(container, themeService, contextViewService); } - protected getEmptyItem(): IListDataItem { + protected getEmptyItem(): TListDataItem { return { value: { type: 'string', data: '' } - }; + } as TListDataItem; } protected override isAddButtonVisible(): boolean { @@ -438,7 +479,7 @@ export class ListSettingWidget extends AbstractListSettingWidget return ['setting-list-widget']; } - protected getActionsForItem(item: IListDataItem, idx: number): IAction[] { + protected getActionsForItem(item: TListDataItem, idx: number): IAction[] { return [ { class: ThemeIcon.asClassName(settingsEditIcon), @@ -452,20 +493,20 @@ export class ListSettingWidget extends AbstractListSettingWidget enabled: true, id: 'workbench.action.removeListItem', tooltip: this.getLocalizedStrings().deleteActionTooltip, - run: () => this._onDidChangeList.fire({ originalItem: item, item: undefined, targetIndex: idx }) + run: () => this._onDidChangeList.fire({ type: 'remove', originalItem: item, targetIndex: idx }) } ] as IAction[]; } - private dragDetails: ListSettingWidgetDragDetails | undefined; + private dragDetails: ListSettingWidgetDragDetails | undefined; - private getDragImage(item: IListDataItem): HTMLElement { + private getDragImage(item: TListDataItem): HTMLElement { const dragImage = $('.monaco-drag-image'); dragImage.textContent = item.value.data; return dragImage; } - protected renderItem(item: IListDataItem, idx: number): RowElementGroup { + protected renderItem(item: TListDataItem, idx: number): RowElementGroup { const rowElement = $('.setting-list-row'); const valueElement = DOM.append(rowElement, $('.setting-list-value')); const siblingElement = DOM.append(rowElement, $('.setting-list-sibling')); @@ -477,7 +518,7 @@ export class ListSettingWidget extends AbstractListSettingWidget return { rowElement, keyElement: valueElement, valueElement: siblingElement }; } - protected addDragAndDrop(rowElement: HTMLElement, item: IListDataItem, idx: number) { + protected addDragAndDrop(rowElement: HTMLElement, item: TListDataItem, idx: number) { if (this.inReadMode) { rowElement.draggable = true; rowElement.classList.add('draggable'); @@ -497,7 +538,7 @@ export class ListSettingWidget extends AbstractListSettingWidget const dragImage = this.getDragImage(item); rowElement.ownerDocument.body.appendChild(dragImage); ev.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => rowElement.ownerDocument.body.removeChild(dragImage), 0); + setTimeout(() => dragImage.remove(), 0); } })); this.listDisposables.add(DOM.addDisposableListener(rowElement, DOM.EventType.DRAG_OVER, (ev) => { @@ -530,9 +571,10 @@ export class ListSettingWidget extends AbstractListSettingWidget counter = 0; if (this.dragDetails.element !== rowElement) { this._onDidChangeList.fire({ + type: 'move', originalItem: this.dragDetails.item, sourceIndex: this.dragDetails.itemIndex, - item, + newItem: item, targetIndex: idx }); } @@ -548,7 +590,7 @@ export class ListSettingWidget extends AbstractListSettingWidget })); } - protected renderEdit(item: IListDataItem, idx: number): HTMLElement { + protected renderEdit(item: TListDataItem, idx: number): HTMLElement { const rowElement = $('.setting-list-edit-row'); let valueInput: InputBox | SelectBox; let currentDisplayValue: string; @@ -580,7 +622,7 @@ export class ListSettingWidget extends AbstractListSettingWidget break; } - const updatedInputBoxItem = (): IListDataItem => { + const updatedInputBoxItem = (): TListDataItem => { const inputBox = valueInput as InputBox; return { value: { @@ -588,16 +630,16 @@ export class ListSettingWidget extends AbstractListSettingWidget data: inputBox.value }, sibling: siblingInput?.value - }; + } as TListDataItem; }; - const updatedSelectBoxItem = (selectedValue: string): IListDataItem => { + const updatedSelectBoxItem = (selectedValue: string): TListDataItem => { return { value: { type: 'enum', data: selectedValue, options: currentEnumOptions ?? [] } - }; + } as TListDataItem; }; const onKeyDown = (e: StandardKeyboardEvent) => { if (e.equals(KeyCode.Enter)) { @@ -644,7 +686,7 @@ export class ListSettingWidget extends AbstractListSettingWidget valueInput.element.classList.add('no-sibling'); } - const okButton = this._register(new Button(rowElement, defaultButtonStyles)); + const okButton = this.listDisposables.add(new Button(rowElement, defaultButtonStyles)); okButton.label = localize('okButton', "OK"); okButton.element.classList.add('setting-list-ok-button'); @@ -656,7 +698,7 @@ export class ListSettingWidget extends AbstractListSettingWidget } })); - const cancelButton = this._register(new Button(rowElement, { secondary: true, ...defaultButtonStyles })); + const cancelButton = this.listDisposables.add(new Button(rowElement, { secondary: true, ...defaultButtonStyles })); cancelButton.label = localize('cancelButton', "Cancel"); cancelButton.element.classList.add('setting-list-cancel-button'); @@ -674,17 +716,17 @@ export class ListSettingWidget extends AbstractListSettingWidget return rowElement; } - override isItemNew(item: IListDataItem): boolean { + override isItemNew(item: TListDataItem): boolean { return item.value.data === ''; } - protected addTooltipsToRow(rowElementGroup: RowElementGroup, { value, sibling }: IListDataItem) { + protected addTooltipsToRow(rowElementGroup: RowElementGroup, { value, sibling }: TListDataItem) { const title = isUndefinedOrNull(sibling) ? localize('listValueHintLabel', "List item `{0}`", value.data) : localize('listSiblingHintLabel', "List item `{0}` with sibling `${1}`", value.data, sibling); const { rowElement } = rowElementGroup; - this.listDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), rowElement, title)); + this.listDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), rowElement, title)); rowElement.setAttribute('aria-label', title); } @@ -729,22 +771,28 @@ export class ListSettingWidget extends AbstractListSettingWidget } } -export class ExcludeSettingWidget extends ListSettingWidget { +export class ExcludeSettingWidget extends ListSettingWidget { protected override getContainerClasses() { return ['setting-list-include-exclude-widget']; } - protected override addDragAndDrop(rowElement: HTMLElement, item: IListDataItem, idx: number) { + protected override addDragAndDrop(rowElement: HTMLElement, item: IIncludeExcludeDataItem, idx: number) { return; } - protected override addTooltipsToRow(rowElementGroup: RowElementGroup, { value, sibling }: IListDataItem): void { - const title = isUndefinedOrNull(sibling) - ? localize('excludePatternHintLabel', "Exclude files matching `{0}`", value.data) - : localize('excludeSiblingHintLabel', "Exclude files matching `{0}`, only when a file matching `{1}` is present", value.data, sibling); + protected override addTooltipsToRow(rowElementGroup: RowElementGroup, item: IIncludeExcludeDataItem): void { + let title = isUndefinedOrNull(item.sibling) + ? localize('excludePatternHintLabel', "Exclude files matching `{0}`", item.value.data) + : localize('excludeSiblingHintLabel', "Exclude files matching `{0}`, only when a file matching `{1}` is present", item.value.data, item.sibling); + + if (item.source) { + title += localize('excludeIncludeSource', ". Default value provided by `{0}`", item.source); + } + + const markdownTitle = new MarkdownString().appendMarkdown(title); const { rowElement } = rowElementGroup; - this.listDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), rowElement, title)); + this.listDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), rowElement, { markdown: markdownTitle, markdownNotSupportedFallback: title })); rowElement.setAttribute('aria-label', title); } @@ -759,22 +807,28 @@ export class ExcludeSettingWidget extends ListSettingWidget { } } -export class IncludeSettingWidget extends ListSettingWidget { +export class IncludeSettingWidget extends ListSettingWidget { protected override getContainerClasses() { return ['setting-list-include-exclude-widget']; } - protected override addDragAndDrop(rowElement: HTMLElement, item: IListDataItem, idx: number) { + protected override addDragAndDrop(rowElement: HTMLElement, item: IIncludeExcludeDataItem, idx: number) { return; } - protected override addTooltipsToRow(rowElementGroup: RowElementGroup, { value, sibling }: IListDataItem): void { - const title = isUndefinedOrNull(sibling) - ? localize('includePatternHintLabel', "Include files matching `{0}`", value.data) - : localize('includeSiblingHintLabel', "Include files matching `{0}`, only when a file matching `{1}` is present", value.data, sibling); + protected override addTooltipsToRow(rowElementGroup: RowElementGroup, item: IIncludeExcludeDataItem): void { + let title = isUndefinedOrNull(item.sibling) + ? localize('includePatternHintLabel', "Include files matching `{0}`", item.value.data) + : localize('includeSiblingHintLabel', "Include files matching `{0}`, only when a file matching `{1}` is present", item.value.data, item.sibling); + + if (item.source) { + title += localize('excludeIncludeSource', ". Default value provided by `{0}`", item.source); + } + + const markdownTitle = new MarkdownString().appendMarkdown(title); const { rowElement } = rowElementGroup; - this.listDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), rowElement, title)); + this.listDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), rowElement, { markdown: markdownTitle, markdownNotSupportedFallback: title })); rowElement.setAttribute('aria-label', title); } @@ -818,7 +872,16 @@ export interface IObjectDataItem { key: ObjectKey; value: ObjectValue; keyDescription?: string; + source?: string; removable: boolean; + resetable: boolean; +} + +export interface IIncludeExcludeDataItem { + value: ObjectKey; + elementType: SettingValueType; + sibling?: string; + source?: string; } export interface IObjectValueSuggester { @@ -886,6 +949,7 @@ export class ObjectSettingDropdownWidget extends AbstractListSettingWidget this._onDidChangeList.fire({ originalItem: item, item: undefined, targetIndex: idx }) + tooltip: this.getLocalizedStrings().resetActionTooltip, + run: () => this._onDidChangeList.fire({ type: 'reset', originalItem: item, targetIndex: idx }) }); - } else { + } + + if (item.removable) { actions.push({ - class: ThemeIcon.asClassName(settingsDiscardIcon), + class: ThemeIcon.asClassName(settingsRemoveIcon), enabled: true, - id: 'workbench.action.resetListItem', + id: 'workbench.action.removeListItem', label: '', - tooltip: this.getLocalizedStrings().resetActionTooltip, - run: () => this._onDidChangeList.fire({ originalItem: item, item: undefined, targetIndex: idx }) + tooltip: this.getLocalizedStrings().deleteActionTooltip, + run: () => this._onDidChangeList.fire({ type: 'remove', originalItem: item, targetIndex: idx }) }); } @@ -1022,14 +1088,14 @@ export class ObjectSettingDropdownWidget extends AbstractListSettingWidget this.handleItemChange(item, changedItem, idx))); - const cancelButton = this._register(new Button(rowElement, { secondary: true, ...defaultButtonStyles })); + const cancelButton = this.listDisposables.add(new Button(rowElement, { secondary: true, ...defaultButtonStyles })); cancelButton.label = localize('cancelButton', "Cancel"); cancelButton.element.classList.add('setting-list-cancel-button'); @@ -1181,13 +1247,21 @@ export class ObjectSettingDropdownWidget extends AbstractListSettingWidget { +export interface IBoolObjectDataItem { + key: IObjectStringData; + value: IObjectBoolData; + keyDescription?: string; + source?: string; + removable: false; + resetable: boolean; +} + +export class ObjectSettingCheckboxWidget extends AbstractListSettingWidget { private currentSettingKey: string = ''; constructor( @@ -1227,7 +1310,7 @@ export class ObjectSettingCheckboxWidget extends AbstractListSettingWidget, idx: number, listFocused: boolean): HTMLElement { + protected override renderDataOrEditItem(item: IListViewItem, idx: number, listFocused: boolean): HTMLElement { const rowElement = this.renderEdit(item, idx); rowElement.setAttribute('role', 'listitem'); return rowElement; } - protected renderItem(item: IObjectDataItem, idx: number): RowElementGroup { + protected renderItem(item: IBoolObjectDataItem, idx: number): RowElementGroup { // Return just the containers, since we always render in edit mode anyway const rowElement = $('.blank-row'); const keyElement = $('.blank-row-key'); return { rowElement, keyElement }; } - protected renderEdit(item: IObjectDataItem, idx: number): HTMLElement { + protected renderEdit(item: IBoolObjectDataItem, idx: number): HTMLElement { const rowElement = $('.setting-list-edit-row.setting-list-object-row.setting-item-bool'); const changedItem = { ...item }; @@ -1342,12 +1426,12 @@ export class ObjectSettingCheckboxWidget extends AbstractListSettingWidget(JSONContributionRegistry.Extensions.JSONContribution); - -export class PreferencesContribution implements IWorkbenchContribution { +export class PreferencesContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.preferences'; private editorOpeningListener: IDisposable | undefined; - private settingsListener: IDisposable; constructor( - @IModelService private readonly modelService: IModelService, - @ITextModelService private readonly textModelResolverService: ITextModelService, + @IFileService fileService: IFileService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @ILanguageService private readonly languageService: ILanguageService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, - @ITextEditorService private readonly textEditorService: ITextEditorService + @ITextEditorService private readonly textEditorService: ITextEditorService, ) { - this.settingsListener = this.configurationService.onDidChangeConfiguration(e => { + super(); + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(USE_SPLIT_JSON_SETTING) || e.affectsConfiguration(DEFAULT_SETTINGS_EDITOR_SETTING)) { this.handleSettingsEditorRegistration(); } - }); + })); this.handleSettingsEditorRegistration(); - this.start(); + const fileSystemProvider = this._register(this.instantiationService.createInstance(SettingsFileSystemProvider)); + this._register(fileService.registerProvider(SettingsFileSystemProvider.SCHEMA, fileSystemProvider)); } private handleSettingsEditorRegistration(): void { @@ -102,44 +97,13 @@ export class PreferencesContribution implements IWorkbenchContribution { ); } } - - private start(): void { - - this.textModelResolverService.registerTextModelContentProvider('vscode', { - provideTextContent: async (uri: URI): Promise => { - if (uri.scheme !== 'vscode') { - return null; - } - if (uri.authority === 'schemas') { - return this.getSchemaModel(uri); - } - return this.preferencesService.resolveModel(uri); - } - }); - } - - private getSchemaModel(uri: URI): ITextModel { - let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()] ?? {} /* Use empty schema if not yet registered */; - const modelContent = JSON.stringify(schema); - const languageSelection = this.languageService.createById('jsonc'); - const model = this.modelService.createModel(modelContent, languageSelection, uri); - const disposables = new DisposableStore(); - disposables.add(schemaRegistry.onDidChangeSchema(schemaUri => { - if (schemaUri === uri.toString()) { - schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; - model.setValue(JSON.stringify(schema)); - } - })); - disposables.add(model.onWillDispose(() => disposables.dispose())); - return model; - } - - dispose(): void { + override dispose(): void { dispose(this.editorOpeningListener); - dispose(this.settingsListener); + super.dispose(); } } + const registry = Registry.as(Extensions.Configuration); registry.registerConfiguration({ ...workbenchConfigurationNodeBase, @@ -147,7 +111,7 @@ registry.registerConfiguration({ 'workbench.settings.enableNaturalLanguageSearch': { 'type': 'boolean', 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), - 'default': false, + 'default': true, 'scope': ConfigurationScope.WINDOW, 'tags': ['usesOnlineServices'] }, diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/common/settingsEditorColorRegistry.ts b/patched-vscode/src/vs/workbench/contrib/preferences/common/settingsEditorColorRegistry.ts index 29841a28..bd5e4ef2 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/common/settingsEditorColorRegistry.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/common/settingsEditorColorRegistry.ts @@ -10,36 +10,36 @@ import { PANEL_BORDER } from 'vs/workbench/common/theme'; // General setting colors export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hcDark: '#ffffff', hcLight: '#292929' }, localize('headerForeground', "The foreground color for a section header or active title.")); -export const settingsHeaderHoverForeground = registerColor('settings.settingsHeaderHoverForeground', { light: transparent(settingsHeaderForeground, 0.7), dark: transparent(settingsHeaderForeground, 0.7), hcDark: transparent(settingsHeaderForeground, 0.7), hcLight: transparent(settingsHeaderForeground, 0.7) }, localize('settingsHeaderHoverForeground', "The foreground color for a section header or hovered title.")); +export const settingsHeaderHoverForeground = registerColor('settings.settingsHeaderHoverForeground', transparent(settingsHeaderForeground, 0.7), localize('settingsHeaderHoverForeground', "The foreground color for a section header or hovered title.")); export const modifiedItemIndicator = registerColor('settings.modifiedItemIndicator', { light: new Color(new RGBA(102, 175, 224)), dark: new Color(new RGBA(12, 125, 157)), hcDark: new Color(new RGBA(0, 73, 122)), hcLight: new Color(new RGBA(102, 175, 224)), }, localize('modifiedItemForeground', "The color of the modified setting indicator.")); -export const settingsHeaderBorder = registerColor('settings.headerBorder', { dark: PANEL_BORDER, light: PANEL_BORDER, hcDark: PANEL_BORDER, hcLight: PANEL_BORDER }, localize('settingsHeaderBorder', "The color of the header container border.")); -export const settingsSashBorder = registerColor('settings.sashBorder', { dark: PANEL_BORDER, light: PANEL_BORDER, hcDark: PANEL_BORDER, hcLight: PANEL_BORDER }, localize('settingsSashBorder', "The color of the Settings editor splitview sash border.")); +export const settingsHeaderBorder = registerColor('settings.headerBorder', PANEL_BORDER, localize('settingsHeaderBorder', "The color of the header container border.")); +export const settingsSashBorder = registerColor('settings.sashBorder', PANEL_BORDER, localize('settingsSashBorder', "The color of the Settings editor splitview sash border.")); // Enum control colors -export const settingsSelectBackground = registerColor(`settings.dropdownBackground`, { dark: selectBackground, light: selectBackground, hcDark: selectBackground, hcLight: selectBackground }, localize('settingsDropdownBackground', "Settings editor dropdown background.")); -export const settingsSelectForeground = registerColor('settings.dropdownForeground', { dark: selectForeground, light: selectForeground, hcDark: selectForeground, hcLight: selectForeground }, localize('settingsDropdownForeground', "Settings editor dropdown foreground.")); -export const settingsSelectBorder = registerColor('settings.dropdownBorder', { dark: selectBorder, light: selectBorder, hcDark: selectBorder, hcLight: selectBorder }, localize('settingsDropdownBorder', "Settings editor dropdown border.")); -export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('settingsDropdownListBorder', "Settings editor dropdown list border. This surrounds the options and separates the options from the description.")); +export const settingsSelectBackground = registerColor(`settings.dropdownBackground`, selectBackground, localize('settingsDropdownBackground', "Settings editor dropdown background.")); +export const settingsSelectForeground = registerColor('settings.dropdownForeground', selectForeground, localize('settingsDropdownForeground', "Settings editor dropdown foreground.")); +export const settingsSelectBorder = registerColor('settings.dropdownBorder', selectBorder, localize('settingsDropdownBorder', "Settings editor dropdown border.")); +export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', editorWidgetBorder, localize('settingsDropdownListBorder', "Settings editor dropdown list border. This surrounds the options and separates the options from the description.")); // Bool control colors -export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: checkboxBackground, light: checkboxBackground, hcDark: checkboxBackground, hcLight: checkboxBackground }, localize('settingsCheckboxBackground', "Settings editor checkbox background.")); -export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: checkboxForeground, light: checkboxForeground, hcDark: checkboxForeground, hcLight: checkboxForeground }, localize('settingsCheckboxForeground', "Settings editor checkbox foreground.")); -export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: checkboxBorder, light: checkboxBorder, hcDark: checkboxBorder, hcLight: checkboxBorder }, localize('settingsCheckboxBorder', "Settings editor checkbox border.")); +export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', checkboxBackground, localize('settingsCheckboxBackground', "Settings editor checkbox background.")); +export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', checkboxForeground, localize('settingsCheckboxForeground', "Settings editor checkbox foreground.")); +export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', checkboxBorder, localize('settingsCheckboxBorder', "Settings editor checkbox border.")); // Text control colors -export const settingsTextInputBackground = registerColor('settings.textInputBackground', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('textInputBoxBackground', "Settings editor text input box background.")); -export const settingsTextInputForeground = registerColor('settings.textInputForeground', { dark: inputForeground, light: inputForeground, hcDark: inputForeground, hcLight: inputForeground }, localize('textInputBoxForeground', "Settings editor text input box foreground.")); -export const settingsTextInputBorder = registerColor('settings.textInputBorder', { dark: inputBorder, light: inputBorder, hcDark: inputBorder, hcLight: inputBorder }, localize('textInputBoxBorder', "Settings editor text input box border.")); +export const settingsTextInputBackground = registerColor('settings.textInputBackground', inputBackground, localize('textInputBoxBackground', "Settings editor text input box background.")); +export const settingsTextInputForeground = registerColor('settings.textInputForeground', inputForeground, localize('textInputBoxForeground', "Settings editor text input box foreground.")); +export const settingsTextInputBorder = registerColor('settings.textInputBorder', inputBorder, localize('textInputBoxBorder', "Settings editor text input box border.")); // Number control colors -export const settingsNumberInputBackground = registerColor('settings.numberInputBackground', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('numberInputBoxBackground', "Settings editor number input box background.")); -export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', { dark: inputForeground, light: inputForeground, hcDark: inputForeground, hcLight: inputForeground }, localize('numberInputBoxForeground', "Settings editor number input box foreground.")); -export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', { dark: inputBorder, light: inputBorder, hcDark: inputBorder, hcLight: inputBorder }, localize('numberInputBoxBorder', "Settings editor number input box border.")); +export const settingsNumberInputBackground = registerColor('settings.numberInputBackground', inputBackground, localize('numberInputBoxBackground', "Settings editor number input box background.")); +export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', inputForeground, localize('numberInputBoxForeground', "Settings editor number input box foreground.")); +export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', inputBorder, localize('numberInputBoxBorder', "Settings editor number input box border.")); export const focusedRowBackground = registerColor('settings.focusedRowBackground', { dark: transparent(listHoverBackground, .6), @@ -55,9 +55,4 @@ export const rowHoverBackground = registerColor('settings.rowHoverBackground', { hcLight: null }, localize('settings.rowHoverBackground', "The background color of a settings row when hovered.")); -export const focusedRowBorder = registerColor('settings.focusedRowBorder', { - dark: focusBorder, - light: focusBorder, - hcDark: focusBorder, - hcLight: focusBorder -}, localize('settings.focusedRowBorder', "The color of the row's top and bottom border when the row is focused.")); +export const focusedRowBorder = registerColor('settings.focusedRowBorder', focusBorder, localize('settings.focusedRowBorder', "The color of the row's top and bottom border when the row is focused.")); diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/common/settingsFilesystemProvider.ts b/patched-vscode/src/vs/workbench/contrib/preferences/common/settingsFilesystemProvider.ts new file mode 100644 index 00000000..f0a80db8 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/preferences/common/settingsFilesystemProvider.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { NotSupportedError } from 'vs/base/common/errors'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { FileChangeType, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileChange, IFileDeleteOptions, IFileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { Event, Emitter } from 'vs/base/common/event'; +import { Registry } from 'vs/platform/registry/common/platform'; +import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ILogService, LogLevel } from 'vs/platform/log/common/log'; + +const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); + + +export class SettingsFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { + + static readonly SCHEMA = Schemas.vscode; + + protected readonly _onDidChangeFile = this._register(new Emitter()); + readonly onDidChangeFile = this._onDidChangeFile.event; + + constructor( + @IPreferencesService private readonly preferencesService: IPreferencesService, + @ILogService private readonly logService: ILogService + ) { + super(); + this._register(schemaRegistry.onDidChangeSchema(schemaUri => { + this._onDidChangeFile.fire([{ resource: URI.parse(schemaUri), type: FileChangeType.UPDATED }]); + })); + this._register(preferencesService.onDidDefaultSettingsContentChanged(uri => { + this._onDidChangeFile.fire([{ resource: uri, type: FileChangeType.UPDATED }]); + })); + } + + readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly + FileSystemProviderCapabilities.FileReadWrite; + + async readFile(uri: URI): Promise { + if (uri.scheme !== SettingsFileSystemProvider.SCHEMA) { + throw new NotSupportedError(); + } + let content: string | undefined; + if (uri.authority === 'schemas') { + content = this.getSchemaContent(uri); + } else if (uri.authority === 'defaultsettings') { + content = this.preferencesService.getDefaultSettingsContent(uri); + } + if (content) { + return VSBuffer.fromString(content).buffer; + } + throw FileSystemProviderErrorCode.FileNotFound; + } + + async stat(uri: URI): Promise { + if (schemaRegistry.hasSchemaContent(uri.toString()) || this.preferencesService.hasDefaultSettingsContent(uri)) { + const currentTime = Date.now(); + return { + type: FileType.File, + permissions: FilePermission.Readonly, + mtime: currentTime, + ctime: currentTime, + size: 0 + }; + } + throw FileSystemProviderErrorCode.FileNotFound; + } + + readonly onDidChangeCapabilities = Event.None; + + watch(resource: URI, opts: IWatchOptions): IDisposable { return Disposable.None; } + + async mkdir(resource: URI): Promise { } + async readdir(resource: URI): Promise<[string, FileType][]> { return []; } + + async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise { } + async delete(resource: URI, opts: IFileDeleteOptions): Promise { } + + async writeFile() { + throw new NotSupportedError(); + } + + private getSchemaContent(uri: URI): string { + const startTime = Date.now(); + const content = schemaRegistry.getSchemaContent(uri.toString()) ?? '{}' /* Use empty schema if not yet registered */; + const logLevel = this.logService.getLevel(); + if (logLevel === LogLevel.Debug || logLevel === LogLevel.Trace) { + const endTime = Date.now(); + const uncompressed = JSON.stringify(schemaRegistry.getSchemaContributions().schemas[uri.toString()]); + this.logService.debug(`${uri.toString()}: ${uncompressed.length} -> ${content.length} (${Math.round((uncompressed.length - content.length) / uncompressed.length * 100)}%) Took ${endTime - startTime}ms`); + } + return content; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts b/patched-vscode/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts index e5b24e6b..55c3c4db 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { KeybindingEditorDecorationsRenderer } from 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts b/patched-vscode/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts index 6bb2d93f..c465c047 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { settingKeyToDisplayFormat, parseQuery, IParsedQuery } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; diff --git a/patched-vscode/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts b/patched-vscode/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts index 26d0c30d..d8d614fb 100644 --- a/patched-vscode/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { Position } from 'vs/editor/common/core/position'; diff --git a/patched-vscode/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/patched-vscode/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 17b7b7ac..2cb85b71 100644 --- a/patched-vscode/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/patched-vscode/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -214,8 +214,8 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce private getGlobalCommandPicks(): ICommandQuickPick[] { const globalCommandPicks: ICommandQuickPick[] = []; const scopedContextKeyService = this.editorService.activeEditorPane?.scopedContextKeyService || this.editorGroupService.activeGroup.scopedContextKeyService; - const globalCommandsMenu = this.menuService.createMenu(MenuId.CommandPalette, scopedContextKeyService); - const globalCommandsMenuActions = globalCommandsMenu.getActions() + const globalCommandsMenu = this.menuService.getMenuActions(MenuId.CommandPalette, scopedContextKeyService); + const globalCommandsMenuActions = globalCommandsMenu .reduce((r, [, actions]) => [...r, ...actions], >[]) .filter(action => action instanceof MenuItemAction && action.enabled) as MenuItemAction[]; @@ -251,9 +251,6 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce }); } - // Cleanup - globalCommandsMenu.dispose(); - return globalCommandPicks; } } diff --git a/patched-vscode/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/patched-vscode/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index a75a1b4b..8b83d9b4 100644 --- a/patched-vscode/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -29,6 +29,7 @@ interface IConfiguration extends IWindowsConfiguration { window: IWindowSettings; workbench?: { enableExperiments?: boolean }; _extensionsGallery?: { enablePPE?: boolean }; + accessibility?: { verbosity?: { debug?: boolean } }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -38,24 +39,28 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo 'window.nativeTabs', 'window.nativeFullScreen', 'window.clickThroughInactive', + 'window.experimentalControlOverlay', 'update.mode', 'editor.accessibilitySupport', 'security.workspace.trust.enabled', 'workbench.enableExperiments', '_extensionsGallery.enablePPE', - 'security.restrictUNCAccess' + 'security.restrictUNCAccess', + 'accessibility.verbosity.debug' ]; private readonly titleBarStyle = new ChangeObserver('string'); private readonly nativeTabs = new ChangeObserver('boolean'); private readonly nativeFullScreen = new ChangeObserver('boolean'); private readonly clickThroughInactive = new ChangeObserver('boolean'); + private readonly linuxWindowControlOverlay = new ChangeObserver('boolean'); private readonly updateMode = new ChangeObserver('string'); private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; private readonly workspaceTrustEnabled = new ChangeObserver('boolean'); private readonly experimentsEnabled = new ChangeObserver('boolean'); private readonly enablePPEExtensionsGallery = new ChangeObserver('boolean'); private readonly restrictUNCAccess = new ChangeObserver('boolean'); + private readonly accessibilityVerbosityDebug = new ChangeObserver('boolean'); constructor( @IHostService private readonly hostService: IHostService, @@ -96,6 +101,9 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo // macOS: Click through (accept first mouse) processChanged(isMacintosh && this.clickThroughInactive.handleChange(config.window?.clickThroughInactive)); + // Linux: WCO + processChanged(isLinux && this.linuxWindowControlOverlay.handleChange(config.window?.experimentalControlOverlay)); + // Update mode processChanged(this.updateMode.handleChange(config.update?.mode)); @@ -112,6 +120,9 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo // UNC host access restrictions processChanged(this.restrictUNCAccess.handleChange(config?.security?.restrictUNCAccess)); + + // Debug accessibility verbosity + processChanged(this.accessibilityVerbosityDebug.handleChange(config?.accessibility?.verbosity?.debug)); } // Experiments diff --git a/patched-vscode/src/vs/workbench/contrib/remote/browser/media/tunnelView.css b/patched-vscode/src/vs/workbench/contrib/remote/browser/media/tunnelView.css index 70a8ba93..b52d1154 100644 --- a/patched-vscode/src/vs/workbench/contrib/remote/browser/media/tunnelView.css +++ b/patched-vscode/src/vs/workbench/contrib/remote/browser/media/tunnelView.css @@ -44,6 +44,11 @@ margin-top: -40px; } +.ports-view .monaco-list .monaco-list-row .ports-view-actionbar-cell .ports-view-actionbar-cell-localaddress { + color: var(--vscode-textLink-foreground); + text-decoration: var(--text-link-decoration); +} + .ports-view .monaco-list .monaco-list-row .ports-view-actionbar-cell .ports-view-actionbar-cell-localaddress:hover { text-decoration: underline; } diff --git a/patched-vscode/src/vs/workbench/contrib/remote/browser/remote.ts b/patched-vscode/src/vs/workbench/contrib/remote/browser/remote.ts index 13ee310b..c6abb8e8 100644 --- a/patched-vscode/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/patched-vscode/src/vs/workbench/contrib/remote/browser/remote.ts @@ -22,7 +22,7 @@ import { VIEWLET_ID } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -290,7 +290,7 @@ abstract class HelpItemBase implements IHelpItem { label: string; url: string; description: string; - extensionDescription: Readonly; + extensionDescription: IExtensionDescription; }[]> { return (await Promise.all(this.values.map(async (value) => { return { @@ -419,7 +419,7 @@ class IssueReporterItem extends HelpItemBase { label: string; description: string; url: string; - extensionDescription: Readonly; + extensionDescription: IExtensionDescription; }[]> { return Promise.all(this.values.map(async (value) => { return { @@ -799,8 +799,8 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I const connection = remoteAgentService.getConnection(); if (connection) { let quickInputVisible = false; - quickInputService.onShow(() => quickInputVisible = true); - quickInputService.onHide(() => quickInputVisible = false); + this._register(quickInputService.onShow(() => quickInputVisible = true)); + this._register(quickInputService.onHide(() => quickInputVisible = false)); let visibleProgress: VisibleProgress | null = null; let reconnectWaitEvent: ReconnectionWaitEvent | null = null; diff --git a/patched-vscode/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/patched-vscode/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 08dd88b0..fc3263eb 100644 --- a/patched-vscode/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/patched-vscode/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { IRemoteAgentService, remoteConnectionLatencyMeasurer } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RunOnceScheduler, retry } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { MenuId, IMenuService, MenuItemAction, MenuRegistry, registerAction2, Action2, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; @@ -778,12 +778,13 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr return items; }; - const quickPick = this.quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const quickPick = disposables.add(this.quickInputService.createQuickPick({ useSeparators: true })); quickPick.placeholder = nls.localize('remoteActions', "Select an option to open a Remote Window"); quickPick.items = computeItems(); quickPick.sortByLabel = false; quickPick.canSelectMany = false; - Event.once(quickPick.onDidAccept)((async _ => { + disposables.add(Event.once(quickPick.onDidAccept)((async _ => { const selectedItems = quickPick.selectedItems; if (selectedItems.length === 1) { const commandId = selectedItems[0].id!; @@ -806,21 +807,20 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr quickPick.hide(); } } - })); + }))); - Event.once(quickPick.onDidTriggerItemButton)(async (e) => { + disposables.add(Event.once(quickPick.onDidTriggerItemButton)(async (e) => { const remoteExtension = this.remoteExtensionMetadata.find(value => ExtensionIdentifier.equals(value.id, e.item.id)); if (remoteExtension) { await this.openerService.open(URI.parse(remoteExtension.helpLink)); } - }); + })); // refresh the items when actions change - const legacyItemUpdater = this.legacyIndicatorMenu.onDidChange(() => quickPick.items = computeItems()); - quickPick.onDidHide(legacyItemUpdater.dispose); + disposables.add(this.legacyIndicatorMenu.onDidChange(() => quickPick.items = computeItems())); + disposables.add(this.remoteIndicatorMenu.onDidChange(() => quickPick.items = computeItems())); - const itemUpdater = this.remoteIndicatorMenu.onDidChange(() => quickPick.items = computeItems()); - quickPick.onDidHide(itemUpdater.dispose); + disposables.add(quickPick.onDidHide(() => disposables.dispose())); if (!this.remoteMetadataInitialized) { quickPick.busy = true; diff --git a/patched-vscode/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/patched-vscode/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 4e826cec..a1b6e3ae 100644 --- a/patched-vscode/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/patched-vscode/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -1562,24 +1562,25 @@ namespace SetTunnelProtocolAction { export const LABEL_HTTP = nls.localize('remote.tunnel.protocolHttp', "HTTP"); export const LABEL_HTTPS = nls.localize('remote.tunnel.protocolHttps', "HTTPS"); - async function handler(arg: any, protocol: TunnelProtocol, remoteExplorerService: IRemoteExplorerService) { + async function handler(arg: any, protocol: TunnelProtocol, remoteExplorerService: IRemoteExplorerService, environmentService: IWorkbenchEnvironmentService) { if (isITunnelItem(arg)) { const attributes: Partial = { protocol }; - return remoteExplorerService.tunnelModel.configPortsAttributes.addAttributes(arg.remotePort, attributes, ConfigurationTarget.USER_REMOTE); + const target = environmentService.remoteAuthority ? ConfigurationTarget.USER_REMOTE : ConfigurationTarget.USER_LOCAL; + return remoteExplorerService.tunnelModel.configPortsAttributes.addAttributes(arg.remotePort, attributes, target); } } export function handlerHttp(): ICommandHandler { return async (accessor, arg) => { - return handler(arg, TunnelProtocol.Http, accessor.get(IRemoteExplorerService)); + return handler(arg, TunnelProtocol.Http, accessor.get(IRemoteExplorerService), accessor.get(IWorkbenchEnvironmentService)); }; } export function handlerHttps(): ICommandHandler { return async (accessor, arg) => { - return handler(arg, TunnelProtocol.Https, accessor.get(IRemoteExplorerService)); + return handler(arg, TunnelProtocol.Https, accessor.get(IRemoteExplorerService), accessor.get(IWorkbenchEnvironmentService)); }; } } @@ -1817,10 +1818,5 @@ MenuRegistry.appendMenuItem(MenuId.TunnelLocalAddressInline, ({ when: isForwardedOrDetectedExpr })); -registerColor('ports.iconRunningProcessForeground', { - light: STATUS_BAR_REMOTE_ITEM_BACKGROUND, - dark: STATUS_BAR_REMOTE_ITEM_BACKGROUND, - hcDark: STATUS_BAR_REMOTE_ITEM_BACKGROUND, - hcLight: STATUS_BAR_REMOTE_ITEM_BACKGROUND -}, nls.localize('portWithRunningProcess.foreground', "The color of the icon for a port that has an associated running process.")); +registerColor('ports.iconRunningProcessForeground', STATUS_BAR_REMOTE_ITEM_BACKGROUND, nls.localize('portWithRunningProcess.foreground', "The color of the icon for a port that has an associated running process.")); diff --git a/patched-vscode/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/patched-vscode/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts index 0f750170..e65b86ba 100644 --- a/patched-vscode/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts @@ -363,19 +363,20 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo private async getAuthenticationSession(): Promise { const sessions = await this.getAllSessions(); - const quickpick = this.quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const quickpick = disposables.add(this.quickInputService.createQuickPick({ useSeparators: true })); quickpick.ok = false; quickpick.placeholder = localize('accountPreference.placeholder', "Sign in to an account to enable remote access"); quickpick.ignoreFocusOut = true; quickpick.items = await this.createQuickpickItems(sessions); return new Promise((resolve, reject) => { - quickpick.onDidHide((e) => { + disposables.add(quickpick.onDidHide((e) => { resolve(undefined); - quickpick.dispose(); - }); + disposables.dispose(); + })); - quickpick.onDidAccept(async (e) => { + disposables.add(quickpick.onDidAccept(async (e) => { const selection = quickpick.selectedItems[0]; if ('provider' in selection) { const session = await this.authenticationService.createSession(selection.provider.id, selection.provider.scopes); @@ -386,7 +387,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo resolve(undefined); } quickpick.hide(); - }); + })); quickpick.show(); }); @@ -750,7 +751,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo return new Promise((c, e) => { const disposables = new DisposableStore(); - const quickPick = this.quickInputService.createQuickPick(); + const quickPick = this.quickInputService.createQuickPick({ useSeparators: true }); quickPick.placeholder = localize('manage.placeholder', 'Select a command to invoke'); disposables.add(quickPick); const items: Array = []; diff --git a/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/interactiveEditor.css b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/interactiveEditor.css new file mode 100644 index 00000000..d43c6a6e --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/interactiveEditor.css @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.interactive-editor .input-cell-container:focus-within .input-editor-container .monaco-editor { + outline: solid 1px var(--vscode-notebook-focusedCellBorder); +} + +.interactive-editor .input-cell-container .input-editor-container .monaco-editor { + outline: solid 1px var(--vscode-notebook-inactiveFocusedCellBorder); +} + +.interactive-editor .input-cell-container .input-focus-indicator { + top: 8px; +} + +.interactive-editor .input-cell-container .monaco-editor-background, +.interactive-editor .input-cell-container .margin-view-overlays { + background-color: var(--vscode-notebook-cellEditorBackground, var(--vscode-editor-background)); +} diff --git a/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/media/interactive.css b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/media/interactive.css new file mode 100644 index 00000000..f0f5cd48 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/media/interactive.css @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.interactive-editor .input-cell-container { + box-sizing: border-box; +} + +.interactive-editor .input-cell-container .input-focus-indicator { + position: absolute; + left: 0px; + height: 19px; +} + +.interactive-editor .input-cell-container .input-focus-indicator::before { + border-left: 3px solid transparent; + border-radius: 2px; + margin-left: 4px; + content: ""; + position: absolute; + width: 0px; + height: 100%; + z-index: 10; + left: 0px; + top: 0px; + height: 100%; +} + +.interactive-editor .input-cell-container .run-button-container { + position: absolute; +} + +.interactive-editor .input-cell-container .run-button-container .monaco-toolbar .actions-container { + justify-content: center; +} diff --git a/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts new file mode 100644 index 00000000..683d23c9 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts @@ -0,0 +1,339 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; +import { EditorExtensions, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor'; +import { parse } from 'vs/base/common/marshalling'; +import { assertType } from 'vs/base/common/types'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { CellEditType, CellKind, NotebookSetting, NotebookWorkingCopyTypeIdentifier, REPL_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookEditorInputOptions } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { ReplEditor } from 'vs/workbench/contrib/replNotebook/browser/replEditor'; +import { ReplEditorInput } from 'vs/workbench/contrib/replNotebook/browser/replEditorInput'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { extname, isEqual } from 'vs/base/common/resources'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; +import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { getReplView } from 'vs/workbench/contrib/debug/browser/repl'; +import { REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { localize2 } from 'vs/nls'; +import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +type SerializedNotebookEditorData = { resource: URI; preferredResource: URI; viewType: string; options?: NotebookEditorInputOptions; label?: string }; +class ReplEditorSerializer implements IEditorSerializer { + canSerialize(input: EditorInput): boolean { + return input.typeId === ReplEditorInput.ID; + } + serialize(input: EditorInput): string { + assertType(input instanceof ReplEditorInput); + const data: SerializedNotebookEditorData = { + resource: input.resource, + preferredResource: input.preferredResource, + viewType: input.viewType, + options: input.options, + label: input.getName() + }; + return JSON.stringify(data); + } + deserialize(instantiationService: IInstantiationService, raw: string) { + const data = parse(raw); + if (!data) { + return undefined; + } + const { resource, viewType } = data; + if (!data || !URI.isUri(resource) || typeof viewType !== 'string') { + return undefined; + } + + const input = instantiationService.createInstance(ReplEditorInput, resource, data.label); + return input; + } +} + +Registry.as(EditorExtensions.EditorPane).registerEditorPane( + EditorPaneDescriptor.create( + ReplEditor, + REPL_EDITOR_ID, + 'REPL Editor' + ), + [ + new SyncDescriptor(ReplEditorInput) + ] +); + +Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( + ReplEditorInput.ID, + ReplEditorSerializer +); + +export class ReplDocumentContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.replDocument'; + + constructor( + @INotebookService notebookService: INotebookService, + @IEditorResolverService editorResolverService: IEditorResolverService, + @INotebookEditorModelResolverService private readonly notebookEditorModelResolverService: INotebookEditorModelResolverService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + + editorResolverService.registerEditor( + // don't match anything, we don't need to support re-opening files as REPL editor at this point + ` `, + { + id: 'repl', + label: 'repl Editor', + priority: RegisteredEditorPriority.option + }, + { + // We want to support all notebook types which could have any file extension, + // so we just check if the resource corresponds to a notebook + canSupportResource: uri => notebookService.getNotebookTextModel(uri) !== undefined, + singlePerResource: true + }, + { + createUntitledEditorInput: async ({ resource, options }) => { + const scratchpad = this.configurationService.getValue(NotebookSetting.InteractiveWindowPromptToSave) !== true; + const ref = await this.notebookEditorModelResolverService.resolve({ untitledResource: resource }, 'jupyter-notebook', { scratchpad }); + + // untitled notebooks are disposed when they get saved. we should not hold a reference + // to such a disposed notebook and therefore dispose the reference as well + ref.object.notebook.onWillDispose(() => { + ref.dispose(); + }); + const label = (options as INotebookEditorOptions)?.label ?? undefined; + return { editor: this.instantiationService.createInstance(ReplEditorInput, resource!, label), options }; + }, + createEditorInput: async ({ resource, options }) => { + const label = (options as INotebookEditorOptions)?.label ?? undefined; + return { editor: this.instantiationService.createInstance(ReplEditorInput, resource, label), options }; + } + } + ); + } +} + +class ReplWindowWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { + + static readonly ID = 'workbench.contrib.replWorkingCopyEditorHandler'; + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService, + @IExtensionService private readonly extensionService: IExtensionService, + ) { + super(); + + this._installHandler(); + } + + handles(workingCopy: IWorkingCopyIdentifier): boolean { + const viewType = this._getViewType(workingCopy); + return !!viewType && viewType === 'jupyter-notebook' && extname(workingCopy.resource) === '.replNotebook'; + + } + + isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean { + if (!this.handles(workingCopy)) { + return false; + } + + return editor instanceof ReplEditorInput && isEqual(workingCopy.resource, editor.resource); + } + + createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput { + return this.instantiationService.createInstance(ReplEditorInput, workingCopy.resource, undefined); + } + + private async _installHandler(): Promise { + await this.extensionService.whenInstalledExtensionsRegistered(); + + this._register(this.workingCopyEditorService.registerHandler(this)); + } + + private _getViewType(workingCopy: IWorkingCopyIdentifier): string | undefined { + return NotebookWorkingCopyTypeIdentifier.parse(workingCopy.typeId); + } +} + +registerWorkbenchContribution2(ReplWindowWorkingCopyEditorHandler.ID, ReplWindowWorkingCopyEditorHandler, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(ReplDocumentContribution.ID, ReplDocumentContribution, WorkbenchPhase.BlockRestore); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'repl.execute', + title: localize2('repl.execute', 'Execute REPL input'), + category: 'REPL', + keybinding: [{ + when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.repl'), + primary: KeyMod.CtrlCmd | KeyCode.Enter, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }, { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('activeEditor', 'workbench.editor.repl'), + ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', true) + ), + primary: KeyMod.Shift | KeyCode.Enter, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }, { + when: ContextKeyExpr.and( + ContextKeyExpr.equals('activeEditor', 'workbench.editor.repl'), + ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', false) + ), + primary: KeyCode.Enter, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }], + menu: [ + { + id: MenuId.ReplInputExecute + } + ], + icon: icons.executeIcon, + f1: false, + metadata: { + description: 'Execute the Contents of the Input Box', + args: [ + { + name: 'resource', + description: 'Interactive resource Uri', + isOptional: true + } + ] + } + }); + } + + async run(accessor: ServicesAccessor, context?: UriComponents): Promise { + const editorService = accessor.get(IEditorService); + const bulkEditService = accessor.get(IBulkEditService); + const historyService = accessor.get(IInteractiveHistoryService); + const notebookEditorService = accessor.get(INotebookEditorService); + let editorControl: { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + if (context) { + const resourceUri = URI.revive(context); + const editors = editorService.findEditors(resourceUri); + for (const found of editors) { + if (found.editor.typeId === ReplEditorInput.ID) { + const editor = await editorService.openEditor(found.editor, found.groupId); + editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + break; + } + } + } + else { + editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; + } + + if (editorControl) { + executeReplInput(bulkEditService, historyService, notebookEditorService, editorControl); + } + } +}); + +async function executeReplInput( + bulkEditService: IBulkEditService, + historyService: IInteractiveHistoryService, + notebookEditorService: INotebookEditorService, + editorControl: { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget }) { + + if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) { + const notebookDocument = editorControl.notebookEditor.textModel; + const textModel = editorControl.codeEditor.getModel(); + const activeKernel = editorControl.notebookEditor.activeKernel; + const language = activeKernel?.supportedLanguages[0] ?? PLAINTEXT_LANGUAGE_ID; + + if (notebookDocument && textModel) { + const index = notebookDocument.length - 1; + const value = textModel.getValue(); + + if (isFalsyOrWhitespace(value)) { + return; + } + + historyService.replaceLast(notebookDocument.uri, value); + historyService.addToHistory(notebookDocument.uri, ''); + textModel.setValue(''); + notebookDocument.cells[index].resetTextBuffer(textModel.getTextBuffer()); + + const collapseState = editorControl.notebookEditor.notebookOptions.getDisplayOptions().interactiveWindowCollapseCodeCells === 'fromEditor' ? + { + inputCollapsed: false, + outputCollapsed: false + } : + undefined; + + await bulkEditService.apply([ + new ResourceNotebookCellEdit(notebookDocument.uri, + { + editType: CellEditType.Replace, + index: index, + count: 0, + cells: [{ + cellKind: CellKind.Code, + mime: undefined, + language, + source: value, + outputs: [], + metadata: {}, + collapseState + }] + } + ) + ]); + + // reveal the cell into view first + const range = { start: index, end: index + 1 }; + editorControl.notebookEditor.revealCellRangeInView(range); + await editorControl.notebookEditor.executeNotebookCells(editorControl.notebookEditor.getCellsInRange({ start: index, end: index + 1 })); + + // update the selection and focus in the extension host model + const editor = notebookEditorService.getNotebookEditor(editorControl.notebookEditor.getId()); + if (editor) { + editor.setSelections([range]); + editor.setFocus(range); + } + } + } +} + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.find.replInputFocus', + weight: KeybindingWeight.WorkbenchContrib + 1, + when: ContextKeyExpr.equals('view', REPL_VIEW_ID), + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF, + secondary: [KeyCode.F3], + handler: (accessor) => { + getReplView(accessor.get(IViewsService))?.openFind(); + } +}); diff --git a/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts new file mode 100644 index 00000000..e41ac91b --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/replEditor.ts @@ -0,0 +1,710 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/interactive'; +import * as DOM from 'vs/base/browser/dom'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneScrollPosition, IEditorPaneSelectionChangeEvent, IEditorPaneWithScrolling } from 'vs/workbench/common/editor'; +import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; +import { ICellViewModel, INotebookEditorOptions, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ExecutionStateCellStatusBarContrib, TimerCellStatusBarContrib } from 'vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IAction } from 'vs/base/common/actions'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { ParameterHintsController } from 'vs/editor/contrib/parameterHints/browser/parameterHints'; +import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; +import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; +import { MarkerController } from 'vs/editor/contrib/gotoError/browser/gotoError'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { ITextEditorOptions, TextEditorSelectionSource } from 'vs/platform/editor/common/editor'; +import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { isEqual } from 'vs/base/common/resources'; +import { NotebookFindContrib } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget'; +import { REPL_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import 'vs/css!./interactiveEditor'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { deepClone } from 'vs/base/common/objects'; +import { MarginHoverController } from 'vs/editor/contrib/hover/browser/marginHoverController'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController2'; +import { ReplEditorInput } from 'vs/workbench/contrib/replNotebook/browser/replEditorInput'; +import { ReplInputHintContentWidget } from 'vs/workbench/contrib/interactive/browser/replInputHintContentWidget'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; + +const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; + +const INPUT_CELL_VERTICAL_PADDING = 8; +const INPUT_CELL_HORIZONTAL_PADDING_RIGHT = 10; +const INPUT_EDITOR_PADDING = 8; + +export interface InteractiveEditorViewState { + readonly notebook?: INotebookEditorViewState; + readonly input?: ICodeEditorViewState | null; +} + +export interface InteractiveEditorOptions extends ITextEditorOptions { + readonly viewState?: InteractiveEditorViewState; +} + +export class ReplEditor extends EditorPane implements IEditorPaneWithScrolling { + private _rootElement!: HTMLElement; + private _styleElement!: HTMLStyleElement; + private _notebookEditorContainer!: HTMLElement; + private _notebookWidget: IBorrowValue = { value: undefined }; + private _inputCellContainer!: HTMLElement; + private _inputFocusIndicator!: HTMLElement; + private _inputRunButtonContainer!: HTMLElement; + private _inputEditorContainer!: HTMLElement; + private _codeEditorWidget!: CodeEditorWidget; + private _notebookWidgetService: INotebookEditorService; + private _instantiationService: IInstantiationService; + private _languageService: ILanguageService; + private _contextKeyService: IContextKeyService; + private _configurationService: IConfigurationService; + private _notebookKernelService: INotebookKernelService; + private _keybindingService: IKeybindingService; + private _menuService: IMenuService; + private _contextMenuService: IContextMenuService; + private _editorGroupService: IEditorGroupsService; + private _extensionService: IExtensionService; + private readonly _widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); + private _lastLayoutDimensions?: { readonly dimension: DOM.Dimension; readonly position: DOM.IDomPosition }; + private _editorOptions: IEditorOptions; + private _notebookOptions: NotebookOptions; + private _editorMemento: IEditorMemento; + private readonly _groupListener = this._register(new MutableDisposable()); + private _runbuttonToolbar: ToolBar | undefined; + private _hintElement: ReplInputHintContentWidget | undefined; + + private _onDidFocusWidget = this._register(new Emitter()); + override get onDidFocus(): Event { return this._onDidFocusWidget.event; } + private _onDidChangeSelection = this._register(new Emitter()); + readonly onDidChangeSelection = this._onDidChangeSelection.event; + private _onDidChangeScroll = this._register(new Emitter()); + readonly onDidChangeScroll = this._onDidChangeScroll.event; + + constructor( + group: IEditorGroup, + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @INotebookEditorService notebookWidgetService: INotebookEditorService, + @IContextKeyService contextKeyService: IContextKeyService, + @ICodeEditorService codeEditorService: ICodeEditorService, + @INotebookKernelService notebookKernelService: INotebookKernelService, + @ILanguageService languageService: ILanguageService, + @IKeybindingService keybindingService: IKeybindingService, + @IConfigurationService configurationService: IConfigurationService, + @IMenuService menuService: IMenuService, + @IContextMenuService contextMenuService: IContextMenuService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, + @IExtensionService extensionService: IExtensionService, + ) { + super( + REPL_EDITOR_ID, + group, + telemetryService, + themeService, + storageService + ); + this._notebookWidgetService = notebookWidgetService; + this._configurationService = configurationService; + this._notebookKernelService = notebookKernelService; + this._languageService = languageService; + this._keybindingService = keybindingService; + this._menuService = menuService; + this._contextMenuService = contextMenuService; + this._editorGroupService = editorGroupService; + this._extensionService = extensionService; + + this._rootElement = DOM.$('.interactive-editor'); + this._contextKeyService = this._register(contextKeyService.createScoped(this._rootElement)); + this._contextKeyService.createKey('isCompositeNotebook', true); + this._instantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService]))); + + this._editorOptions = this._computeEditorOptions(); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) { + this._editorOptions = this._computeEditorOptions(); + } + })); + this._notebookOptions = instantiationService.createInstance(NotebookOptions, this.window, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false }); + this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); + + this._register(this._keybindingService.onDidUpdateKeybindings(this._updateInputHint, this)); + this._register(notebookExecutionStateService.onDidChangeExecution((e) => { + if (e.type === NotebookExecutionType.cell && isEqual(e.notebook, this._notebookWidget.value?.viewModel?.notebookDocument.uri)) { + const cell = this._notebookWidget.value?.getCellByHandle(e.cellHandle); + if (cell && e.changed?.state) { + this._scrollIfNecessary(cell); + } + } + })); + } + + private get inputCellContainerHeight() { + return 19 + 2 + INPUT_CELL_VERTICAL_PADDING * 2 + INPUT_EDITOR_PADDING * 2; + } + + private get inputCellEditorHeight() { + return 19 + INPUT_EDITOR_PADDING * 2; + } + + protected createEditor(parent: HTMLElement): void { + DOM.append(parent, this._rootElement); + this._rootElement.style.position = 'relative'; + this._notebookEditorContainer = DOM.append(this._rootElement, DOM.$('.notebook-editor-container')); + this._inputCellContainer = DOM.append(this._rootElement, DOM.$('.input-cell-container')); + this._inputCellContainer.style.position = 'absolute'; + this._inputCellContainer.style.height = `${this.inputCellContainerHeight}px`; + this._inputFocusIndicator = DOM.append(this._inputCellContainer, DOM.$('.input-focus-indicator')); + this._inputRunButtonContainer = DOM.append(this._inputCellContainer, DOM.$('.run-button-container')); + this._setupRunButtonToolbar(this._inputRunButtonContainer); + this._inputEditorContainer = DOM.append(this._inputCellContainer, DOM.$('.input-editor-container')); + this._createLayoutStyles(); + } + + private _setupRunButtonToolbar(runButtonContainer: HTMLElement) { + const menu = this._register(this._menuService.createMenu(MenuId.ReplInputExecute, this._contextKeyService)); + this._runbuttonToolbar = this._register(new ToolBar(runButtonContainer, this._contextMenuService, { + getKeyBinding: action => this._keybindingService.lookupKeybinding(action.id), + actionViewItemProvider: (action, options) => { + return createActionViewItem(this._instantiationService, action, options); + }, + renderDropdownAsChildElement: true + })); + + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + + createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result); + this._runbuttonToolbar.setActions([...primary, ...secondary]); + } + + private _createLayoutStyles(): void { + this._styleElement = DOM.createStyleSheet(this._rootElement); + const styleSheets: string[] = []; + + const { + codeCellLeftMargin, + cellRunGutter + } = this._notebookOptions.getLayoutConfiguration(); + const { + focusIndicator + } = this._notebookOptions.getDisplayOptions(); + const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin(); + + styleSheets.push(` + .interactive-editor .input-cell-container { + padding: ${INPUT_CELL_VERTICAL_PADDING}px ${INPUT_CELL_HORIZONTAL_PADDING_RIGHT}px ${INPUT_CELL_VERTICAL_PADDING}px ${leftMargin}px; + } + `); + if (focusIndicator === 'gutter') { + styleSheets.push(` + .interactive-editor .input-cell-container:focus-within .input-focus-indicator::before { + border-color: var(--vscode-notebook-focusedCellBorder) !important; + } + .interactive-editor .input-focus-indicator::before { + border-color: var(--vscode-notebook-inactiveFocusedCellBorder) !important; + } + .interactive-editor .input-cell-container .input-focus-indicator { + display: block; + top: ${INPUT_CELL_VERTICAL_PADDING}px; + } + .interactive-editor .input-cell-container { + border-top: 1px solid var(--vscode-notebook-inactiveFocusedCellBorder); + } + `); + } else { + // border + styleSheets.push(` + .interactive-editor .input-cell-container { + border-top: 1px solid var(--vscode-notebook-inactiveFocusedCellBorder); + } + .interactive-editor .input-cell-container .input-focus-indicator { + display: none; + } + `); + } + + styleSheets.push(` + .interactive-editor .input-cell-container .run-button-container { + width: ${cellRunGutter}px; + left: ${codeCellLeftMargin}px; + margin-top: ${INPUT_EDITOR_PADDING - 2}px; + } + `); + + this._styleElement.textContent = styleSheets.join('\n'); + } + + private _computeEditorOptions(): IEditorOptions { + let overrideIdentifier: string | undefined = undefined; + if (this._codeEditorWidget) { + overrideIdentifier = this._codeEditorWidget.getModel()?.getLanguageId(); + } + const editorOptions = deepClone(this._configurationService.getValue('editor', { overrideIdentifier })); + const editorOptionsOverride = getSimpleEditorOptions(this._configurationService); + const computed = Object.freeze({ + ...editorOptions, + ...editorOptionsOverride, + ...{ + glyphMargin: true, + padding: { + top: INPUT_EDITOR_PADDING, + bottom: INPUT_EDITOR_PADDING + }, + hover: { + enabled: true + } + } + }); + + return computed; + } + + protected override saveState(): void { + this._saveEditorViewState(this.input); + super.saveState(); + } + + override getViewState(): InteractiveEditorViewState | undefined { + const input = this.input; + if (!(input instanceof ReplEditorInput)) { + return undefined; + } + + this._saveEditorViewState(input); + return this._loadNotebookEditorViewState(input); + } + + private _saveEditorViewState(input: EditorInput | undefined): void { + if (this._notebookWidget.value && input instanceof ReplEditorInput) { + if (this._notebookWidget.value.isDisposed) { + return; + } + + const state = this._notebookWidget.value.getEditorViewState(); + const editorState = this._codeEditorWidget.saveViewState(); + this._editorMemento.saveEditorState(this.group, input.resource, { + notebook: state, + input: editorState + }); + } + } + + private _loadNotebookEditorViewState(input: ReplEditorInput): InteractiveEditorViewState | undefined { + const result = this._editorMemento.loadEditorState(this.group, input.resource); + if (result) { + return result; + } + // when we don't have a view state for the group/input-tuple then we try to use an existing + // editor for the same resource. + for (const group of this._editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + if (group.activeEditorPane !== this && group.activeEditorPane === this && group.activeEditor?.matches(input)) { + const notebook = this._notebookWidget.value?.getEditorViewState(); + const input = this._codeEditorWidget.saveViewState(); + return { + notebook, + input + }; + } + } + return; + } + + override async setInput(input: ReplEditorInput, options: InteractiveEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + // there currently is a widget which we still own so + // we need to hide it before getting a new widget + this._notebookWidget.value?.onWillHide(); + + this._codeEditorWidget?.dispose(); + + this._widgetDisposableStore.clear(); + + this._notebookWidget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group.id, input, { + isEmbedded: true, + isReadOnly: true, + forRepl: true, + contributions: NotebookEditorExtensionsRegistry.getSomeEditorContributions([ + ExecutionStateCellStatusBarContrib.id, + TimerCellStatusBarContrib.id, + NotebookFindContrib.id + ]), + menuIds: { + notebookToolbar: MenuId.InteractiveToolbar, + cellTitleToolbar: MenuId.InteractiveCellTitle, + cellDeleteToolbar: MenuId.InteractiveCellDelete, + cellInsertToolbar: MenuId.NotebookCellBetween, + cellTopInsertToolbar: MenuId.NotebookCellListTop, + cellExecuteToolbar: MenuId.InteractiveCellExecute, + cellExecutePrimary: undefined + }, + cellEditorContributions: EditorExtensionsRegistry.getSomeEditorContributions([ + SelectionClipboardContributionID, + ContextMenuController.ID, + ContentHoverController.ID, + MarginHoverController.ID, + MarkerController.ID + ]), + options: this._notebookOptions, + codeWindow: this.window + }, undefined, this.window); + + this._codeEditorWidget = this._instantiationService.createInstance(CodeEditorWidget, this._inputEditorContainer, this._editorOptions, { + ...{ + isSimpleWidget: false, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + MenuPreventer.ID, + SelectionClipboardContributionID, + ContextMenuController.ID, + SuggestController.ID, + ParameterHintsController.ID, + SnippetController2.ID, + TabCompletionController.ID, + ContentHoverController.ID, + MarginHoverController.ID, + MarkerController.ID + ]) + } + }); + + if (this._lastLayoutDimensions) { + this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`; + this._notebookWidget.value!.layout(new DOM.Dimension(this._lastLayoutDimensions.dimension.width, this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight), this._notebookEditorContainer); + const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin(); + const maxHeight = Math.min(this._lastLayoutDimensions.dimension.height / 2, this.inputCellEditorHeight); + this._codeEditorWidget.layout(this._validateDimension(this._lastLayoutDimensions.dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight)); + this._inputFocusIndicator.style.height = `${this.inputCellEditorHeight}px`; + this._inputCellContainer.style.top = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`; + this._inputCellContainer.style.width = `${this._lastLayoutDimensions.dimension.width}px`; + } + + await super.setInput(input, options, context, token); + const model = await input.resolve(); + if (this._runbuttonToolbar) { + this._runbuttonToolbar.context = input.resource; + } + + if (model === null) { + throw new Error('The REPL model could not be resolved'); + } + + this._notebookWidget.value?.setParentContextKeyService(this._contextKeyService); + + const viewState = options?.viewState ?? this._loadNotebookEditorViewState(input); + await this._extensionService.whenInstalledExtensionsRegistered(); + await this._notebookWidget.value!.setModel(model.notebook, viewState?.notebook); + model.notebook.setCellCollapseDefault(this._notebookOptions.getCellCollapseDefault()); + this._notebookWidget.value!.setOptions({ + isReadOnly: true + }); + this._widgetDisposableStore.add(this._notebookWidget.value!.onDidResizeOutput((cvm) => { + this._scrollIfNecessary(cvm); + })); + this._widgetDisposableStore.add(this._notebookWidget.value!.onDidFocusWidget(() => this._onDidFocusWidget.fire())); + this._widgetDisposableStore.add(this._notebookOptions.onDidChangeOptions(e => { + if (e.compactView || e.focusIndicator) { + // update the styling + this._styleElement?.remove(); + this._createLayoutStyles(); + } + + if (this._lastLayoutDimensions && this.isVisible()) { + this.layout(this._lastLayoutDimensions.dimension, this._lastLayoutDimensions.position); + } + + if (e.interactiveWindowCollapseCodeCells) { + model.notebook.setCellCollapseDefault(this._notebookOptions.getCellCollapseDefault()); + } + })); + + const editorModel = await input.resolveInput(model.notebook); + this._codeEditorWidget.setModel(editorModel); + if (viewState?.input) { + this._codeEditorWidget.restoreViewState(viewState.input); + } + this._editorOptions = this._computeEditorOptions(); + this._codeEditorWidget.updateOptions(this._editorOptions); + + this._widgetDisposableStore.add(this._codeEditorWidget.onDidFocusEditorWidget(() => this._onDidFocusWidget.fire())); + this._widgetDisposableStore.add(this._codeEditorWidget.onDidContentSizeChange(e => { + if (!e.contentHeightChanged) { + return; + } + + if (this._lastLayoutDimensions) { + this._layoutWidgets(this._lastLayoutDimensions.dimension, this._lastLayoutDimensions.position); + } + })); + + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeCursorPosition(e => this._onDidChangeSelection.fire({ reason: this._toEditorPaneSelectionChangeReason(e) }))); + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT }))); + + + this._widgetDisposableStore.add(this._notebookKernelService.onDidChangeNotebookAffinity(this._syncWithKernel, this)); + this._widgetDisposableStore.add(this._notebookKernelService.onDidChangeSelectedNotebooks(this._syncWithKernel, this)); + + this._widgetDisposableStore.add(this.themeService.onDidColorThemeChange(() => { + if (this.isVisible()) { + this._updateInputHint(); + } + })); + + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => { + if (this.isVisible()) { + this._updateInputHint(); + } + })); + + const cursorAtBoundaryContext = INTERACTIVE_INPUT_CURSOR_BOUNDARY.bindTo(this._contextKeyService); + if (input.resource && input.historyService.has(input.resource)) { + cursorAtBoundaryContext.set('top'); + } else { + cursorAtBoundaryContext.set('none'); + } + + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeCursorPosition(({ position }) => { + const viewModel = this._codeEditorWidget._getViewModel()!; + const lastLineNumber = viewModel.getLineCount(); + const lastLineCol = viewModel.getLineLength(lastLineNumber) + 1; + const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(position); + const firstLine = viewPosition.lineNumber === 1 && viewPosition.column === 1; + const lastLine = viewPosition.lineNumber === lastLineNumber && viewPosition.column === lastLineCol; + + if (firstLine) { + if (lastLine) { + cursorAtBoundaryContext.set('both'); + } else { + cursorAtBoundaryContext.set('top'); + } + } else { + if (lastLine) { + cursorAtBoundaryContext.set('bottom'); + } else { + cursorAtBoundaryContext.set('none'); + } + } + })); + + this._widgetDisposableStore.add(editorModel.onDidChangeContent(() => { + const value = editorModel.getValue(); + if (this.input?.resource && value !== '') { + const historyService = (this.input as ReplEditorInput).historyService; + if (!historyService.matchesCurrent(this.input.resource, value)) { + historyService.replaceLast(this.input.resource, value); + } + } + })); + + this._widgetDisposableStore.add(this._notebookWidget.value!.onDidScroll(() => this._onDidChangeScroll.fire())); + + this._updateInputHint(); + this._syncWithKernel(); + } + + override setOptions(options: INotebookEditorOptions | undefined): void { + this._notebookWidget.value?.setOptions(options); + super.setOptions(options); + } + + private _toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason { + switch (e.source) { + case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC; + case TextEditorSelectionSource.NAVIGATION: return EditorPaneSelectionChangeReason.NAVIGATION; + case TextEditorSelectionSource.JUMP: return EditorPaneSelectionChangeReason.JUMP; + default: return EditorPaneSelectionChangeReason.USER; + } + } + + private _cellAtBottom(cell: ICellViewModel): boolean { + const visibleRanges = this._notebookWidget.value?.visibleRanges || []; + const cellIndex = this._notebookWidget.value?.getCellIndex(cell); + if (cellIndex === Math.max(...visibleRanges.map(range => range.end - 1))) { + return true; + } + return false; + } + + private _scrollIfNecessary(cvm: ICellViewModel) { + const index = this._notebookWidget.value!.getCellIndex(cvm); + if (index === this._notebookWidget.value!.getLength() - 1) { + // If we're already at the bottom or auto scroll is enabled, scroll to the bottom + if (this._configurationService.getValue(InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell) || this._cellAtBottom(cvm)) { + this._notebookWidget.value!.scrollToBottom(); + } + } + } + + private _syncWithKernel() { + const notebook = this._notebookWidget.value?.textModel; + const textModel = this._codeEditorWidget.getModel(); + + if (notebook && textModel) { + const info = this._notebookKernelService.getMatchingKernel(notebook); + const selectedOrSuggested = info.selected + ?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined) + ?? (info.all.length === 1 ? info.all[0] : undefined); + + if (selectedOrSuggested) { + const language = selectedOrSuggested.supportedLanguages[0]; + // All kernels will initially list plaintext as the supported language before they properly initialized. + if (language && language !== 'plaintext') { + const newMode = this._languageService.createById(language).languageId; + textModel.setLanguage(newMode); + } + + NOTEBOOK_KERNEL.bindTo(this._contextKeyService).set(selectedOrSuggested.id); + } + } + } + + layout(dimension: DOM.Dimension, position: DOM.IDomPosition): void { + this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600); + this._rootElement.classList.toggle('narrow-width', dimension.width < 600); + const editorHeightChanged = dimension.height !== this._lastLayoutDimensions?.dimension.height; + this._lastLayoutDimensions = { dimension, position }; + + if (!this._notebookWidget.value) { + return; + } + + if (editorHeightChanged && this._codeEditorWidget) { + SuggestController.get(this._codeEditorWidget)?.cancelSuggestWidget(); + } + + this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`; + this._layoutWidgets(dimension, position); + } + + private _layoutWidgets(dimension: DOM.Dimension, position: DOM.IDomPosition) { + const contentHeight = this._codeEditorWidget.hasModel() ? this._codeEditorWidget.getContentHeight() : this.inputCellEditorHeight; + const maxHeight = Math.min(dimension.height / 2, contentHeight); + const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin(); + + const inputCellContainerHeight = maxHeight + INPUT_CELL_VERTICAL_PADDING * 2; + this._notebookEditorContainer.style.height = `${dimension.height - inputCellContainerHeight}px`; + + this._notebookWidget.value!.layout(dimension.with(dimension.width, dimension.height - inputCellContainerHeight), this._notebookEditorContainer, position); + this._codeEditorWidget.layout(this._validateDimension(dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight)); + this._inputFocusIndicator.style.height = `${contentHeight}px`; + this._inputCellContainer.style.top = `${dimension.height - inputCellContainerHeight}px`; + this._inputCellContainer.style.width = `${dimension.width}px`; + } + + private _validateDimension(width: number, height: number) { + return new DOM.Dimension(Math.max(0, width), Math.max(0, height)); + } + + private _updateInputHint(): void { + if (!this._codeEditorWidget) { + return; + } + + const shouldHide = + !this._codeEditorWidget.hasModel() || + this._configurationService.getValue(InteractiveWindowSetting.showExecutionHint) === false || + this._codeEditorWidget.getModel()!.getValueLength() !== 0; + + if (!this._hintElement && !shouldHide) { + this._hintElement = this._instantiationService.createInstance(ReplInputHintContentWidget, this._codeEditorWidget); + } else if (this._hintElement && shouldHide) { + this._hintElement.dispose(); + this._hintElement = undefined; + } + } + + getScrollPosition(): IEditorPaneScrollPosition { + return { + scrollTop: this._notebookWidget.value?.scrollTop ?? 0, + scrollLeft: 0 + }; + } + + setScrollPosition(position: IEditorPaneScrollPosition): void { + this._notebookWidget.value?.setScrollTop(position.scrollTop); + } + + override focus() { + super.focus(); + + this._notebookWidget.value?.onShow(); + this._codeEditorWidget.focus(); + } + + focusHistory() { + this._notebookWidget.value!.focus(); + } + + protected override setEditorVisible(visible: boolean): void { + super.setEditorVisible(visible); + this._groupListener.value = this.group.onWillCloseEditor(e => this._saveEditorViewState(e.editor)); + + if (!visible) { + this._saveEditorViewState(this.input); + if (this.input && this._notebookWidget.value) { + this._notebookWidget.value.onWillHide(); + } + } + + this._updateInputHint(); + } + + override clearInput() { + if (this._notebookWidget.value) { + this._saveEditorViewState(this.input); + this._notebookWidget.value.onWillHide(); + } + + this._codeEditorWidget?.dispose(); + + this._notebookWidget = { value: undefined }; + this._widgetDisposableStore.clear(); + + super.clearInput(); + } + + override getControl(): { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } { + return { + notebookEditor: this._notebookWidget.value, + codeEditor: this._codeEditorWidget + }; + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts new file mode 100644 index 00000000..1e61f74a --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/replNotebook/browser/replEditorInput.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IReference } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { EditorInputCapabilities } from 'vs/workbench/common/editor'; +import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CellEditType, CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ICustomEditorLabelService } from 'vs/workbench/services/editor/common/customEditorLabelService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; + +export class ReplEditorInput extends NotebookEditorInput implements ICompositeNotebookEditorInput { + static override ID: string = 'workbench.editorinputs.replEditorInput'; + + private inputModelRef: IReference | undefined; + private isScratchpad: boolean; + private label: string; + private isDisposing = false; + + constructor( + resource: URI, + label: string | undefined, + @INotebookService _notebookService: INotebookService, + @INotebookEditorModelResolverService _notebookModelResolverService: INotebookEditorModelResolverService, + @IFileDialogService _fileDialogService: IFileDialogService, + @ILabelService labelService: ILabelService, + @IFileService fileService: IFileService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, + @IExtensionService extensionService: IExtensionService, + @IEditorService editorService: IEditorService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService, + @IInteractiveHistoryService public readonly historyService: IInteractiveHistoryService, + @ITextModelService private readonly _textModelService: ITextModelService, + @IConfigurationService configurationService: IConfigurationService + ) { + super(resource, undefined, 'jupyter-notebook', {}, _notebookService, _notebookModelResolverService, _fileDialogService, labelService, fileService, filesConfigurationService, extensionService, editorService, textResourceConfigurationService, customEditorLabelService); + this.isScratchpad = resource.scheme === 'untitled' && configurationService.getValue(NotebookSetting.InteractiveWindowPromptToSave) !== true; + this.label = label ?? this.createEditorLabel(resource); + } + + private createEditorLabel(resource: URI | undefined): string { + if (!resource) { + return 'REPL'; + } + + if (resource.scheme === 'untitled') { + const match = new RegExp('Untitled-(\\d+)\.').exec(resource.path); + if (match?.length === 2) { + return `REPL - ${match[1]}`; + } + } + + const filename = resource.path.split('/').pop(); + return filename ? `REPL - ${filename}` : 'REPL'; + } + + override get typeId(): string { + return ReplEditorInput.ID; + } + + override get editorId(): string | undefined { + return 'repl'; + } + + override getName() { + return this.label; + } + + get editorInputs() { + return [this]; + } + + override get capabilities() { + const capabilities = super.capabilities; + const scratchPad = this.isScratchpad ? EditorInputCapabilities.Scratchpad : 0; + + return capabilities + | EditorInputCapabilities.Readonly + | scratchPad; + } + + override async resolve() { + const model = await super.resolve(); + if (model) { + await this.ensureInputBoxCell(model.notebook); + } + + return model; + } + + private async ensureInputBoxCell(notebook: NotebookTextModel) { + const lastCell = notebook.cells[notebook.cells.length - 1]; + + if (!lastCell || lastCell.cellKind === CellKind.Markup || lastCell.outputs.length > 0 || lastCell.internalMetadata.executionOrder !== undefined) { + notebook.applyEdits([ + { + editType: CellEditType.Replace, + index: notebook.cells.length, + count: 0, + cells: [ + { + cellKind: CellKind.Code, + language: 'python', + mime: undefined, + outputs: [], + source: '' + } + ] + } + ], true, undefined, () => undefined, undefined, false); + } + } + + async resolveInput(notebook: NotebookTextModel) { + if (this.inputModelRef) { + return this.inputModelRef.object.textEditorModel; + } + const lastCell = notebook.cells[notebook.cells.length - 1]; + if (!lastCell) { + throw new Error('The REPL editor requires at least one cell for the input box.'); + } + + this.inputModelRef = await this._textModelService.createModelReference(lastCell.uri); + return this.inputModelRef.object.textEditorModel; + } + + override dispose() { + if (!this.isDisposing) { + this.isDisposing = true; + this.editorModelReference?.object.revert({ soft: true }); + this.inputModelRef?.dispose(); + super.dispose(); + } + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/activity.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/activity.ts index 7ee1d516..332ac067 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/activity.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { basename } from 'vs/base/common/resources'; -import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { VIEW_PANE_ID, ISCMService, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -16,142 +16,153 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorResourceAccessor } from 'vs/workbench/common/editor'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { Schemas } from 'vs/base/common/network'; import { Iterable } from 'vs/base/common/iterator'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IEditorGroupContextKeyProvider, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { getRepositoryResourceCount } from 'vs/workbench/contrib/scm/browser/util'; +import { autorun, autorunWithStore, derived, IObservable, observableFromEvent } from 'vs/base/common/observable'; +import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils'; +import { derivedObservableWithCache, latestChangedValue, observableFromEventOpts } from 'vs/base/common/observableInternal/utils'; +import { Command } from 'vs/editor/common/languages'; -function getCount(repository: ISCMRepository): number { - if (typeof repository.provider.count === 'number') { - return repository.provider.count; - } else { - return repository.provider.groups.reduce((r, g) => r + g.resources.length, 0); - } -} +const ActiveRepositoryContextKeys = { + ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), + ActiveRepositoryBranchName: new RawContextKey('scmActiveRepositoryBranchName', ''), +}; -export class SCMStatusController implements IWorkbenchContribution { +export class SCMActiveRepositoryController extends Disposable implements IWorkbenchContribution { + private readonly _countBadgeConfig = observableConfigValue<'all' | 'focused' | 'off'>('scm.countBadge', 'all', this.configurationService); - private statusBarDisposable: IDisposable = Disposable.None; - private focusDisposable: IDisposable = Disposable.None; - private focusedRepository: ISCMRepository | undefined = undefined; - private readonly badgeDisposable = new MutableDisposable(); - private readonly disposables = new DisposableStore(); - private repositoryDisposables = new Set(); + private readonly _repositories = observableFromEvent(this, + Event.any(this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository), + () => this.scmService.repositories); - constructor( - @ISCMService private readonly scmService: ISCMService, - @ISCMViewService private readonly scmViewService: ISCMViewService, - @IStatusbarService private readonly statusbarService: IStatusbarService, - @IActivityService private readonly activityService: IActivityService, - @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService - ) { - this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); - this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); + private readonly _focusedRepository = observableFromEventOpts( + { owner: this, equalsFn: () => false }, + this.scmViewService.onDidFocusRepository, + () => this.scmViewService.focusedRepository); - const onDidChangeSCMCountBadge = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.countBadge')); - onDidChangeSCMCountBadge(this.renderActivityCount, this, this.disposables); + private readonly _activeEditor = observableFromEventOpts( + { owner: this, equalsFn: () => false }, + this.editorService.onDidActiveEditorChange, + () => this.editorService.activeEditor); - for (const repository of this.scmService.repositories) { - this.onDidAddRepository(repository); + private readonly _activeEditorRepository = derivedObservableWithCache(this, (reader, lastValue) => { + const activeResource = EditorResourceAccessor.getOriginalUri(this._activeEditor.read(reader)); + if (!activeResource) { + return lastValue; } - this.scmViewService.onDidFocusRepository(this.focusRepository, this, this.disposables); - this.focusRepository(this.scmViewService.focusedRepository); + const repository = this.scmService.getRepository(activeResource); + if (!repository) { + return lastValue; + } - editorService.onDidActiveEditorChange(() => this.tryFocusRepositoryBasedOnActiveEditor(), this, this.disposables); - this.renderActivityCount(); - } + return Object.create(repository); + }); - private tryFocusRepositoryBasedOnActiveEditor(repositories: Iterable = this.scmService.repositories): boolean { - const resource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor); + /** + * The focused repository takes precedence over the active editor repository when the observable + * values are updated in the same transaction (or during the initial read of the observable value). + */ + private readonly _activeRepository = latestChangedValue(this, [this._activeEditorRepository, this._focusedRepository]); - if (!resource) { - return false; + private readonly _countBadgeRepositories = derived(this, reader => { + switch (this._countBadgeConfig.read(reader)) { + case 'all': { + const repositories = this._repositories.read(reader); + return [...Iterable.map(repositories, r => ({ provider: r.provider, resourceCount: this._getRepositoryResourceCount(r) }))]; + } + case 'focused': { + const repository = this._activeRepository.read(reader); + return repository ? [{ provider: repository.provider, resourceCount: this._getRepositoryResourceCount(repository) }] : []; + } + case 'off': + return []; + default: + throw new Error('Invalid countBadge setting'); } + }); - let bestRepository: ISCMRepository | null = null; - let bestMatchLength = Number.POSITIVE_INFINITY; + private readonly _countBadge = derived(this, reader => { + let total = 0; - for (const repository of repositories) { - const root = repository.provider.rootUri; + for (const repository of this._countBadgeRepositories.read(reader)) { + const count = repository.provider.count?.read(reader); + const resourceCount = repository.resourceCount.read(reader); - if (!root) { - continue; - } + total = total + (count ?? resourceCount); + } - const path = this.uriIdentityService.extUri.relativePath(root, resource); + return total; + }); - if (path && !/^\.\./.test(path) && path.length < bestMatchLength) { - bestRepository = repository; - bestMatchLength = path.length; - } - } + private _activeRepositoryNameContextKey: IContextKey; + private _activeRepositoryBranchNameContextKey: IContextKey; - if (!bestRepository) { - return false; - } + constructor( + @IActivityService private readonly activityService: IActivityService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IEditorService private readonly editorService: IEditorService, + @ISCMService private readonly scmService: ISCMService, + @ISCMViewService private readonly scmViewService: ISCMViewService, + @IStatusbarService private readonly statusbarService: IStatusbarService, + @ITitleService private readonly titleService: ITitleService + ) { + super(); - this.focusRepository(bestRepository); - return true; - } + this._activeRepositoryNameContextKey = ActiveRepositoryContextKeys.ActiveRepositoryName.bindTo(this.contextKeyService); + this._activeRepositoryBranchNameContextKey = ActiveRepositoryContextKeys.ActiveRepositoryBranchName.bindTo(this.contextKeyService); - private onDidAddRepository(repository: ISCMRepository): void { - const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); - const changeDisposable = onDidChange(() => this.renderActivityCount()); + this.titleService.registerVariables([ + { name: 'activeRepositoryName', contextKey: ActiveRepositoryContextKeys.ActiveRepositoryName.key }, + { name: 'activeRepositoryBranchName', contextKey: ActiveRepositoryContextKeys.ActiveRepositoryBranchName.key, } + ]); - const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository); - const removeDisposable = onDidRemove(() => { - disposable.dispose(); - this.repositoryDisposables.delete(disposable); - this.renderActivityCount(); - }); + this._register(autorunWithStore((reader, store) => { + this._updateActivityCountBadge(this._countBadge.read(reader), store); + })); - const disposable = combinedDisposable(changeDisposable, removeDisposable); - this.repositoryDisposables.add(disposable); + this._register(autorunWithStore((reader, store) => { + const repository = this._activeRepository.read(reader); + const commands = repository?.provider.statusBarCommands.read(reader); - this.tryFocusRepositoryBasedOnActiveEditor(Iterable.single(repository)); - } + this._updateStatusBar(repository, commands ?? [], store); + })); - private onDidRemoveRepository(repository: ISCMRepository): void { - if (this.focusedRepository !== repository) { - return; - } + this._register(autorun(reader => { + const repository = this._activeRepository.read(reader); + const historyProvider = repository?.provider.historyProvider.read(reader); + const branchName = historyProvider?.currentHistoryItemGroupName.read(reader); - this.focusRepository(Iterable.first(this.scmService.repositories)); + this._updateActiveRepositoryContextKeys(repository?.provider.name, branchName); + })); } - private focusRepository(repository: ISCMRepository | undefined): void { - if (this.focusedRepository === repository) { - return; - } - - this.focusDisposable.dispose(); - this.focusedRepository = repository; + private _getRepositoryResourceCount(repository: ISCMRepository): IObservable { + return observableFromEvent(this, repository.provider.onDidChangeResources, () => /** @description repositoryResourceCount */ getRepositoryResourceCount(repository.provider)); + } - if (repository && repository.provider.onDidChangeStatusBarCommands) { - this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository)); + private _updateActivityCountBadge(count: number, store: DisposableStore): void { + if (count === 0) { + return; } - this.renderStatusBar(repository); - this.renderActivityCount(); + const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); + store.add(this.activityService.showViewActivity(VIEW_PANE_ID, { badge })); } - private renderStatusBar(repository: ISCMRepository | undefined): void { - this.statusBarDisposable.dispose(); - + private _updateStatusBar(repository: ISCMRepository | undefined, commands: readonly Command[], store: DisposableStore): void { if (!repository) { return; } - const commands = repository.provider.statusBarCommands || []; const label = repository.provider.rootUri ? `${basename(repository.provider.rootUri)} (${repository.provider.label})` : repository.provider.label; - const disposables = new DisposableStore(); for (let index = 0; index < commands.length; index++) { const command = commands[index]; const tooltip = `${label}${command.tooltip ? ` - ${command.tooltip}` : ''}`; @@ -178,213 +189,91 @@ export class SCMStatusController implements IWorkbenchContribution { command: command.id ? command : undefined }; - disposables.add(index === 0 ? + store.add(index === 0 ? this.statusbarService.addEntry(statusbarEntry, `status.scm.${index}`, MainThreadStatusBarAlignment.LEFT, 10000) : this.statusbarService.addEntry(statusbarEntry, `status.scm.${index}`, MainThreadStatusBarAlignment.LEFT, { id: `status.scm.${index - 1}`, alignment: MainThreadStatusBarAlignment.RIGHT, compact: true }) ); } - - this.statusBarDisposable = disposables; - } - - private renderActivityCount(): void { - const countBadgeType = this.configurationService.getValue<'all' | 'focused' | 'off'>('scm.countBadge'); - - let count = 0; - - if (countBadgeType === 'all') { - count = Iterable.reduce(this.scmService.repositories, (r, repository) => r + getCount(repository), 0); - } else if (countBadgeType === 'focused' && this.focusedRepository) { - count = getCount(this.focusedRepository); - } - - if (count > 0) { - const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); - this.badgeDisposable.value = this.activityService.showViewActivity(VIEW_PANE_ID, { badge }); - } else { - this.badgeDisposable.value = undefined; - } - } - - dispose(): void { - this.focusDisposable.dispose(); - this.statusBarDisposable.dispose(); - this.badgeDisposable.dispose(); - this.disposables.dispose(); - dispose(this.repositoryDisposables.values()); - this.repositoryDisposables.clear(); - } -} - -const ActiveRepositoryContextKeys = { - ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), - ActiveRepositoryBranchName: new RawContextKey('scmActiveRepositoryBranchName', ''), -}; - -export class SCMActiveRepositoryContextKeyController implements IWorkbenchContribution { - - private activeRepositoryNameContextKey: IContextKey; - private activeRepositoryBranchNameContextKey: IContextKey; - - private focusedRepository: ISCMRepository | undefined = undefined; - private focusDisposable: IDisposable = Disposable.None; - private readonly disposables = new DisposableStore(); - - constructor( - @IContextKeyService contextKeyService: IContextKeyService, - @IEditorService private readonly editorService: IEditorService, - @ISCMViewService private readonly scmViewService: ISCMViewService, - @ITitleService titleService: ITitleService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService - ) { - this.activeRepositoryNameContextKey = ActiveRepositoryContextKeys.ActiveRepositoryName.bindTo(contextKeyService); - this.activeRepositoryBranchNameContextKey = ActiveRepositoryContextKeys.ActiveRepositoryBranchName.bindTo(contextKeyService); - - titleService.registerVariables([ - { name: 'activeRepositoryName', contextKey: ActiveRepositoryContextKeys.ActiveRepositoryName.key }, - { name: 'activeRepositoryBranchName', contextKey: ActiveRepositoryContextKeys.ActiveRepositoryBranchName.key, } - ]); - - editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.disposables); - scmViewService.onDidFocusRepository(this.onDidFocusRepository, this, this.disposables); - this.onDidFocusRepository(scmViewService.focusedRepository); - } - - private onDidActiveEditorChange(): void { - const activeResource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor); - - if (activeResource?.scheme !== Schemas.file && activeResource?.scheme !== Schemas.vscodeRemote) { - return; - } - - const repository = Iterable.find( - this.scmViewService.repositories, - r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri)) - ); - - this.onDidFocusRepository(repository); } - private onDidFocusRepository(repository: ISCMRepository | undefined): void { - if (!repository || this.focusedRepository === repository) { - return; - } - - this.focusDisposable.dispose(); - this.focusedRepository = repository; - - if (repository && repository.provider.onDidChangeStatusBarCommands) { - this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.updateContextKeys(repository)); - } - - this.updateContextKeys(repository); - } - - private updateContextKeys(repository: ISCMRepository | undefined): void { - this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); - this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.name ?? ''); - } - - dispose(): void { - this.focusDisposable.dispose(); - this.disposables.dispose(); + private _updateActiveRepositoryContextKeys(repositoryName: string | undefined, branchName: string | undefined): void { + this._activeRepositoryNameContextKey.set(repositoryName ?? ''); + this._activeRepositoryBranchNameContextKey.set(branchName ?? ''); } } -export class SCMActiveResourceContextKeyController implements IWorkbenchContribution { +export class SCMActiveResourceContextKeyController extends Disposable implements IWorkbenchContribution { + private readonly _repositories = observableFromEvent(this, + Event.any(this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository), + () => this.scmService.repositories); - private readonly disposables = new DisposableStore(); - private repositoryDisposables = new Set(); - private onDidRepositoryChange = new Emitter(); + private readonly _onDidRepositoryChange = new Emitter(); constructor( @IEditorGroupsService editorGroupsService: IEditorGroupsService, @ISCMService private readonly scmService: ISCMService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { + super(); + const activeResourceHasChangesContextKey = new RawContextKey('scmActiveResourceHasChanges', false, localize('scmActiveResourceHasChanges', "Whether the active resource has changes")); const activeResourceRepositoryContextKey = new RawContextKey('scmActiveResourceRepository', undefined, localize('scmActiveResourceRepository', "The active resource's repository")); - this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); - - for (const repository of this.scmService.repositories) { - this.onDidAddRepository(repository); - } + this._store.add(autorunWithStore((reader, store) => { + for (const repository of this._repositories.read(reader)) { + store.add(Event.runAndSubscribe(repository.provider.onDidChangeResources, () => { + this._onDidRepositoryChange.fire(); + })); + } + })); // Create context key providers which will update the context keys based on each groups active editor const hasChangesContextKeyProvider: IEditorGroupContextKeyProvider = { contextKey: activeResourceHasChangesContextKey, - getGroupContextKeyValue: (group) => this.getEditorHasChanges(group.activeEditor), - onDidChange: this.onDidRepositoryChange.event + getGroupContextKeyValue: (group) => this._getEditorHasChanges(group.activeEditor), + onDidChange: this._onDidRepositoryChange.event }; const repositoryContextKeyProvider: IEditorGroupContextKeyProvider = { contextKey: activeResourceRepositoryContextKey, - getGroupContextKeyValue: (group) => this.getEditorRepositoryId(group.activeEditor), - onDidChange: this.onDidRepositoryChange.event + getGroupContextKeyValue: (group) => this._getEditorRepositoryId(group.activeEditor), + onDidChange: this._onDidRepositoryChange.event }; - this.disposables.add(editorGroupsService.registerContextKeyProvider(hasChangesContextKeyProvider)); - this.disposables.add(editorGroupsService.registerContextKeyProvider(repositoryContextKeyProvider)); + this._store.add(editorGroupsService.registerContextKeyProvider(hasChangesContextKeyProvider)); + this._store.add(editorGroupsService.registerContextKeyProvider(repositoryContextKeyProvider)); } - private onDidAddRepository(repository: ISCMRepository): void { - const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); - const changeDisposable = onDidChange(() => { - this.onDidRepositoryChange.fire(); - }); - - const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository); - const removeDisposable = onDidRemove(() => { - disposable.dispose(); - this.repositoryDisposables.delete(disposable); - this.onDidRepositoryChange.fire(); - }); - - const disposable = combinedDisposable(changeDisposable, removeDisposable); - this.repositoryDisposables.add(disposable); - } - - private getEditorRepositoryId(activeEditor: EditorInput | null): string | undefined { + private _getEditorHasChanges(activeEditor: EditorInput | null): boolean { const activeResource = EditorResourceAccessor.getOriginalUri(activeEditor); + if (!activeResource) { + return false; + } - if (activeResource?.scheme === Schemas.file || activeResource?.scheme === Schemas.vscodeRemote) { - const activeResourceRepository = Iterable.find( - this.scmService.repositories, - r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri)) - ); - - return activeResourceRepository?.id; + const activeResourceRepository = this.scmService.getRepository(activeResource); + for (const resourceGroup of activeResourceRepository?.provider.groups ?? []) { + if (resourceGroup.resources + .some(scmResource => + this.uriIdentityService.extUri.isEqual(activeResource, scmResource.sourceUri))) { + return true; + } } - return undefined; + return false; } - private getEditorHasChanges(activeEditor: EditorInput | null): boolean { + private _getEditorRepositoryId(activeEditor: EditorInput | null): string | undefined { const activeResource = EditorResourceAccessor.getOriginalUri(activeEditor); - - if (activeResource?.scheme === Schemas.file || activeResource?.scheme === Schemas.vscodeRemote) { - const activeResourceRepository = Iterable.find( - this.scmService.repositories, - r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri)) - ); - - for (const resourceGroup of activeResourceRepository?.provider.groups ?? []) { - if (resourceGroup.resources - .some(scmResource => - this.uriIdentityService.extUri.isEqual(activeResource, scmResource.sourceUri))) { - return true; - } - } + if (!activeResource) { + return undefined; } - return false; + const activeResourceRepository = this.scmService.getRepository(activeResource); + return activeResourceRepository?.id; } - dispose(): void { - this.disposables.dispose(); - dispose(this.repositoryDisposables.values()); - this.repositoryDisposables.clear(); - this.onDidRepositoryChange.dispose(); + override dispose(): void { + this._onDidRepositoryChange.dispose(); + super.dispose(); } } diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 7537f80f..c01be2b8 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -787,7 +787,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu if (this.editor.hasModel() && (typeof lineNumber === 'number' || !this.widget.provider)) { index = this.model.findNextClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber, true, this.widget.provider); } else { - const providerChanges: number[] = this.model.mapChanges.get(this.widget.provider) ?? this.model.mapChanges.values().next().value; + const providerChanges: number[] = this.model.mapChanges.get(this.widget.provider) ?? this.model.mapChanges.values().next().value!; const mapIndex = providerChanges.findIndex(value => value === this.widget!.index); index = providerChanges[rot(mapIndex + 1, providerChanges.length)]; } @@ -807,7 +807,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu if (this.editor.hasModel() && (typeof lineNumber === 'number')) { index = this.model.findPreviousClosestChange(typeof lineNumber === 'number' ? lineNumber : this.editor.getPosition().lineNumber, true, this.widget.provider); } else { - const providerChanges: number[] = this.model.mapChanges.get(this.widget.provider) ?? this.model.mapChanges.values().next().value; + const providerChanges: number[] = this.model.mapChanges.get(this.widget.provider) ?? this.model.mapChanges.values().next().value!; const mapIndex = providerChanges.findIndex(value => value === this.widget!.index); index = providerChanges[rot(mapIndex - 1, providerChanges.length)]; } @@ -1013,37 +1013,17 @@ const editorGutterAddedBackground = registerColor('editorGutter.addedBackground' hcLight: '#48985D' }, nls.localize('editorGutterAddedBackground', "Editor gutter background color for lines that are added.")); -const editorGutterDeletedBackground = registerColor('editorGutter.deletedBackground', { - dark: editorErrorForeground, - light: editorErrorForeground, - hcDark: editorErrorForeground, - hcLight: editorErrorForeground -}, nls.localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted.")); - -const minimapGutterModifiedBackground = registerColor('minimapGutter.modifiedBackground', { - dark: editorGutterModifiedBackground, - light: editorGutterModifiedBackground, - hcDark: editorGutterModifiedBackground, - hcLight: editorGutterModifiedBackground -}, nls.localize('minimapGutterModifiedBackground', "Minimap gutter background color for lines that are modified.")); - -const minimapGutterAddedBackground = registerColor('minimapGutter.addedBackground', { - dark: editorGutterAddedBackground, - light: editorGutterAddedBackground, - hcDark: editorGutterAddedBackground, - hcLight: editorGutterAddedBackground -}, nls.localize('minimapGutterAddedBackground', "Minimap gutter background color for lines that are added.")); - -const minimapGutterDeletedBackground = registerColor('minimapGutter.deletedBackground', { - dark: editorGutterDeletedBackground, - light: editorGutterDeletedBackground, - hcDark: editorGutterDeletedBackground, - hcLight: editorGutterDeletedBackground -}, nls.localize('minimapGutterDeletedBackground', "Minimap gutter background color for lines that are deleted.")); - -const overviewRulerModifiedForeground = registerColor('editorOverviewRuler.modifiedForeground', { dark: transparent(editorGutterModifiedBackground, 0.6), light: transparent(editorGutterModifiedBackground, 0.6), hcDark: transparent(editorGutterModifiedBackground, 0.6), hcLight: transparent(editorGutterModifiedBackground, 0.6) }, nls.localize('overviewRulerModifiedForeground', 'Overview ruler marker color for modified content.')); -const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: transparent(editorGutterAddedBackground, 0.6), light: transparent(editorGutterAddedBackground, 0.6), hcDark: transparent(editorGutterAddedBackground, 0.6), hcLight: transparent(editorGutterAddedBackground, 0.6) }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); -const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.deletedForeground', { dark: transparent(editorGutterDeletedBackground, 0.6), light: transparent(editorGutterDeletedBackground, 0.6), hcDark: transparent(editorGutterDeletedBackground, 0.6), hcLight: transparent(editorGutterDeletedBackground, 0.6) }, nls.localize('overviewRulerDeletedForeground', 'Overview ruler marker color for deleted content.')); +const editorGutterDeletedBackground = registerColor('editorGutter.deletedBackground', editorErrorForeground, nls.localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted.")); + +const minimapGutterModifiedBackground = registerColor('minimapGutter.modifiedBackground', editorGutterModifiedBackground, nls.localize('minimapGutterModifiedBackground', "Minimap gutter background color for lines that are modified.")); + +const minimapGutterAddedBackground = registerColor('minimapGutter.addedBackground', editorGutterAddedBackground, nls.localize('minimapGutterAddedBackground', "Minimap gutter background color for lines that are added.")); + +const minimapGutterDeletedBackground = registerColor('minimapGutter.deletedBackground', editorGutterDeletedBackground, nls.localize('minimapGutterDeletedBackground', "Minimap gutter background color for lines that are deleted.")); + +const overviewRulerModifiedForeground = registerColor('editorOverviewRuler.modifiedForeground', transparent(editorGutterModifiedBackground, 0.6), nls.localize('overviewRulerModifiedForeground', 'Overview ruler marker color for modified content.')); +const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', transparent(editorGutterAddedBackground, 0.6), nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); +const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.deletedForeground', transparent(editorGutterDeletedBackground, 0.6), nls.localize('overviewRulerDeletedForeground', 'Overview ruler marker color for deleted content.')); class DirtyDiffDecorator extends Disposable { diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/media/scm.css b/patched-vscode/src/vs/workbench/contrib/scm/browser/media/scm.css index c8939c63..9af24908 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -67,10 +67,6 @@ justify-content: flex-end; } -.scm-view .monaco-editor .selected-text { - border-radius: 0; -} - /** * The following rules are very specific because of inline drop down menus * https://github.com/microsoft/vscode/issues/101410 @@ -114,6 +110,10 @@ line-height: 22px; } +.scm-view .monaco-list-row .monaco-icon-label-container { + height: 22px; +} + .scm-view .monaco-list-row .history, .scm-view .monaco-list-row .history-item-group, .scm-view .monaco-list-row .resource-group { @@ -133,6 +133,31 @@ align-items: center; } +.scm-view .monaco-list-row .history-item > .graph-container { + display: flex; + flex-shrink: 0; + height: 22px; +} + +.scm-view .monaco-list-row .history-item > .graph-container > .graph > circle { + stroke: var(--vscode-sideBar-background); +} + +.scm-view .monaco-list-row .history-item > .label-container { + display: flex; + opacity: 0.75; + flex-shrink: 0; + gap: 4px; +} + +.scm-view .monaco-list-row .history-item > .label-container > .codicon { + font-size: 14px; + border: 1px solid var(--vscode-scm-historyItemStatisticsBorder); + border-radius: 2px; + margin: 1px 0; + padding: 2px +} + .scm-view .monaco-list-row .history-item .stats-container { display: flex; font-size: 11px; @@ -198,7 +223,9 @@ } .scm-view .monaco-list-row.focused .separator-container .label-name, -.scm-view .monaco-list-row.selected .separator-container .label-name { +.scm-view .monaco-list-row.selected .separator-container .label-name, +.scm-view .monaco-list-row.focused .separator-container .action-label::before, +.scm-view .monaco-list-row.selected .separator-container .action-label::before { color: var(--vscode-foreground); } @@ -332,10 +359,6 @@ border-radius: 2px; } -.scm-view .scm-editor-container .monaco-editor .focused .selected-text { - background-color: var(--vscode-editor-selectionBackground); -} - .scm-view .scm-editor { box-sizing: border-box; width: 100%; @@ -475,22 +498,6 @@ margin-top: 1px; } -.scm-view .scm-editor-placeholder { - position: absolute; - pointer-events: none; - z-index: 1; - padding: 2px 6px; - box-sizing: border-box; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - color: var(--vscode-input-placeholderForeground); -} - -.scm-view .scm-editor-placeholder.hidden { - display: none; -} - .scm-view .scm-editor-container .monaco-editor-background, .scm-view .scm-editor-container .monaco-editor, .scm-view .scm-editor-container .monaco-editor .margin, @@ -505,6 +512,10 @@ color: var(--vscode-input-foreground); } +.scm-view .scm-editor-container .placeholder-text.mtk1 { + color: var(--vscode-input-placeholderForeground); +} + /* Repositories */ .scm-repositories-view .scm-provider { @@ -515,3 +526,86 @@ .scm-repositories-view .scm-provider > .label > .name { font-weight: normal; } + +/* History item hover */ + +.monaco-hover.history-item-hover p:first-child { + margin-top: 4px; +} + +.monaco-hover.history-item-hover p:last-child { + margin-bottom: 4px; +} + +.monaco-hover.history-item-hover hr { + margin-top: 4px; + margin-bottom: 4px; +} + +.monaco-hover.history-item-hover hr + p { + margin: 4px 0; +} + +.monaco-hover.history-item-hover .markdown-hover .hover-contents:not(.code-hover-contents):not(.html-hover-contents) span:not(.codicon) { + margin-bottom: 0 !important; +} + +/* Graph */ + +.scm-history-view .scm-provider .label-name { + font-weight: bold; +} + +.scm-history-view .scm-provider .monaco-icon-label { + align-items: center; +} + +.scm-history-view .scm-provider .state-label.monaco-count-badge.long { + display: flex; + font-size: 0.8em; + margin: 0 10px; + color: var(--vscode-debugView-stateLabelForeground); + background: var(--vscode-debugView-stateLabelBackground); + border-radius: 2px; +} + +.scm-history-view .scm-provider .actions { + display: flex; + flex-grow: 1; +} + +.scm-history-view .history-item-load-more { + display: flex; + height: 22px; +} + +.scm-history-view .history-item-load-more .graph-placeholder { + mask-image: linear-gradient(black, transparent); +} + +.scm-history-view .history-item-load-more .history-item-placeholder { + flex-grow: 1; +} + +.scm-history-view .history-item-load-more .history-item-placeholder .monaco-highlighted-label { + display: flex; + align-items: center; + justify-content: center; + color: var(--vscode-textLink-foreground) +} + +.scm-history-view .history-item-load-more .history-item-placeholder .monaco-highlighted-label .codicon { + font-size: 12px; + color: var(--vscode-textLink-foreground) +} + +.scm-history-view .history-item-load-more .history-item-placeholder.shimmer { + padding: 2px 0; +} + +.scm-history-view .history-item-load-more .history-item-placeholder.shimmer .monaco-icon-label-container { + height: 18px; + background: var(--vscode-scm-historyItemStatisticsBorder); + border-radius: 2px; + opacity: 0.5; +} diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/menus.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/menus.ts index 915abcb3..b2516c76 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/menus.ts @@ -14,7 +14,7 @@ import { IMenu, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/c import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ISCMHistoryProviderMenus, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMHistoryProviderMenus, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMHistoryItemViewModelTreeElement } from 'vs/workbench/contrib/scm/common/history'; import { ISCMMenus, ISCMProvider, ISCMRepository, ISCMRepositoryMenus, ISCMResource, ISCMResourceGroup, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; function actionEquals(a: IAction, b: IAction): boolean { @@ -176,7 +176,7 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { private _historyProviderMenu: SCMHistoryProviderMenus | undefined; get historyProviderMenu(): SCMHistoryProviderMenus | undefined { - if (this.provider.historyProvider && !this._historyProviderMenu) { + if (this.provider.historyProvider.get() && !this._historyProviderMenu) { this._historyProviderMenu = new SCMHistoryProviderMenus(this.contextKeyService, this.menuService); this.disposables.add(this._historyProviderMenu); } @@ -256,6 +256,7 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDisposable { private readonly historyItemMenus = new Map(); + private readonly historyItemMenus2 = new Map(); private readonly disposables = new DisposableStore(); constructor( @@ -266,6 +267,10 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo return this.getOrCreateHistoryItemMenu(historyItem); } + getHistoryItemMenu2(historyItem: SCMHistoryItemViewModelTreeElement): IMenu { + return this.getOrCreateHistoryItemMenu2(historyItem); + } + getHistoryItemGroupMenu(historyItemGroup: SCMHistoryItemGroupTreeElement): IMenu { return historyItemGroup.direction === 'incoming' ? this.menuService.createMenu(MenuId.SCMIncomingChanges, this.contextKeyService) : @@ -304,9 +309,20 @@ export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDispo return result; } + private getOrCreateHistoryItemMenu2(historyItem: SCMHistoryItemViewModelTreeElement): IMenu { + let result = this.historyItemMenus2.get(historyItem); + + if (!result) { + result = this.menuService.createMenu(MenuId.SCMChangesContext, this.contextKeyService); + this.historyItemMenus2.set(historyItem, result); + } + + return result; + } + private getOutgoingHistoryItemGroupMenu(menuId: MenuId, historyItemGroup: SCMHistoryItemGroupTreeElement): IMenu { const contextKeyService = this.contextKeyService.createOverlay([ - ['scmHistoryItemGroupHasUpstream', !!historyItemGroup.repository.provider.historyProvider?.currentHistoryItemGroup?.base], + ['scmHistoryItemGroupHasRemote', !!historyItemGroup.repository.provider.historyProvider.get()?.currentHistoryItemGroup.get()?.remote], ]); return this.menuService.createMenu(menuId, contextKeyService); diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index c6090796..892fff0a 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -7,10 +7,10 @@ import { localize, localize2 } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, registerWorkbenchContribution2, Extensions as WorkbenchExtensions, WorkbenchPhase } from 'vs/workbench/common/contributions'; import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; -import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; +import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID, HISTORY_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { SCMActiveRepositoryContextKeyController, SCMActiveResourceContextKeyController, SCMStatusController } from './activity'; +import { SCMActiveResourceContextKeyController, SCMActiveRepositoryController } from './activity'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -32,9 +32,12 @@ import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/sug import { MANAGE_TRUST_COMMAND_ID, WorkspaceTrustContext } from 'vs/workbench/contrib/workspace/common/workspace'; import { IQuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiff'; import { QuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiffService'; -import { getActiveElement } from 'vs/base/browser/dom'; +import { getActiveElement, isActiveElement } from 'vs/base/browser/dom'; import { SCMWorkingSetController } from 'vs/workbench/contrib/scm/browser/workingSet'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; +import { isSCMRepository } from 'vs/workbench/contrib/scm/browser/util'; +import { SCMHistoryViewPane } from 'vs/workbench/contrib/scm/browser/scmHistoryViewPane'; ModesRegistry.registerLanguage({ id: 'scminput', @@ -82,7 +85,7 @@ viewsRegistry.registerViews([{ ctorDescriptor: new SyncDescriptor(SCMViewPane), canToggleVisibility: true, canMoveView: true, - weight: 80, + weight: 40, order: -999, containerIcon: sourceControlViewIcon, openCommandActionDescriptor: { @@ -112,14 +115,23 @@ viewsRegistry.registerViews([{ containerIcon: sourceControlViewIcon }], viewContainer); -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(SCMActiveResourceContextKeyController, LifecyclePhase.Restored); +viewsRegistry.registerViews([{ + id: HISTORY_VIEW_PANE_ID, + name: localize2('source control history', "Source Control Graph"), + ctorDescriptor: new SyncDescriptor(SCMHistoryViewPane), + canToggleVisibility: true, + canMoveView: true, + weight: 40, + order: 2, /* https://github.com/microsoft/vscode/issues/226447 */ + when: ContextKeyExpr.and(ContextKeyExpr.has('scm.providerCount'), ContextKeyExpr.notEquals('scm.providerCount', 0)), + containerIcon: sourceControlViewIcon +}], viewContainer); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(SCMActiveRepositoryContextKeyController, LifecyclePhase.Restored); + .registerWorkbenchContribution(SCMActiveRepositoryController, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); + .registerWorkbenchContribution(SCMActiveResourceContextKeyController, LifecyclePhase.Restored); registerWorkbenchContribution2( SCMWorkingSetController.ID, @@ -311,33 +323,6 @@ Registry.as(ConfigurationExtensions.Configuration).regis markdownDescription: localize('showInputActionButton', "Controls whether an action button can be shown in the Source Control input."), default: true }, - 'scm.showIncomingChanges': { - type: 'string', - enum: ['always', 'never', 'auto'], - enumDescriptions: [ - localize('scm.showIncomingChanges.always', "Always show incoming changes in the Source Control view."), - localize('scm.showIncomingChanges.never', "Never show incoming changes in the Source Control view."), - localize('scm.showIncomingChanges.auto', "Only show incoming changes in the Source Control view when any exist."), - ], - description: localize('scm.showIncomingChanges', "Controls whether incoming changes are shown in the Source Control view."), - default: 'auto' - }, - 'scm.showOutgoingChanges': { - type: 'string', - enum: ['always', 'never', 'auto'], - enumDescriptions: [ - localize('scm.showOutgoingChanges.always', "Always show outgoing changes in the Source Control view."), - localize('scm.showOutgoingChanges.never', "Never show outgoing changes in the Source Control view."), - localize('scm.showOutgoingChanges.auto', "Only show outgoing changes in the Source Control view when any exist."), - ], - description: localize('scm.showOutgoingChanges', "Controls whether outgoing changes are shown in the Source Control view."), - default: 'auto' - }, - 'scm.showChangesSummary': { - type: 'boolean', - description: localize('scm.showChangesSummary', "Controls whether the All Changes entry is shown for incoming/outgoing changes in the Source Control view."), - default: true - }, 'scm.workingSets.enabled': { type: 'boolean', description: localize('scm.workingSets.enabled', "Controls whether to store editor working sets when switching between source control history item groups."), @@ -352,6 +337,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis ], description: localize('scm.workingSets.default', "Controls the default working set to use when switching to a source control history item group that does not have a working set."), default: 'current' + }, + 'scm.compactFolders': { + type: 'boolean', + description: localize('scm.compactFolders', "Controls whether the Source Control view should render folders in a compact form. In such a form, single child folders will be compressed in a combined tree element."), + default: true } } }); @@ -386,6 +376,22 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'scm.clearInput', + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(ContextKeyExpr.has('scmRepository'), SuggestContext.Visible.toNegated()), + primary: KeyCode.Escape, + handler: async (accessor) => { + const scmService = accessor.get(ISCMService); + const contextKeyService = accessor.get(IContextKeyService); + + const context = contextKeyService.getContext(getActiveElement()); + const repositoryId = context.getValue('scmRepository'); + const repository = repositoryId ? scmService.getRepository(repositoryId) : undefined; + repository?.input.setValue('', true); + } +}); + const viewNextCommitCommand = { description: { description: localize('scm view next commit', "Source Control: View Next Commit"), args: [] }, weight: KeybindingWeight.WorkbenchContrib, @@ -440,12 +446,35 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Alt | KeyCode.UpArrow }); -CommandsRegistry.registerCommand('scm.openInIntegratedTerminal', async (accessor, provider: ISCMProvider) => { - if (!provider || !provider.rootUri) { +CommandsRegistry.registerCommand('scm.openInIntegratedTerminal', async (accessor, ...providers: ISCMProvider[]) => { + if (!providers || providers.length === 0) { return; } const commandService = accessor.get(ICommandService); + const listService = accessor.get(IListService); + + let provider = providers.length === 1 ? providers[0] : undefined; + + if (!provider) { + const list = listService.lastFocusedList; + const element = list?.getHTMLElement(); + + if (list instanceof WorkbenchList && element && isActiveElement(element)) { + const [index] = list.getFocus(); + const focusedElement = list.element(index); + + // Source Control Repositories + if (isSCMRepository(focusedElement)) { + provider = focusedElement.provider; + } + } + } + + if (!provider?.rootUri) { + return; + } + await commandService.executeCommand('openInIntegratedTerminal', provider.rootUri); }); diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/scmHistory.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmHistory.ts new file mode 100644 index 00000000..733c95f5 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmHistory.ts @@ -0,0 +1,310 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { lastOrDefault } from 'vs/base/common/arrays'; +import { deepClone } from 'vs/base/common/objects'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { buttonForeground } from 'vs/platform/theme/common/colorRegistry'; +import { chartsBlue, chartsGreen, chartsOrange, chartsPurple, chartsRed, chartsYellow } from 'vs/platform/theme/common/colors/chartsColors'; +import { asCssVariable, ColorIdentifier, registerColor } from 'vs/platform/theme/common/colorUtils'; +import { ISCMHistoryItem, ISCMHistoryItemGraphNode, ISCMHistoryItemViewModel } from 'vs/workbench/contrib/scm/common/history'; +import { rot } from 'vs/base/common/numbers'; +import { svgElem } from 'vs/base/browser/dom'; + +export const SWIMLANE_HEIGHT = 22; +export const SWIMLANE_WIDTH = 11; +const CIRCLE_RADIUS = 4; +const SWIMLANE_CURVE_RADIUS = 5; + +/** + * History graph colors (local, remote, base) + */ +export const historyItemGroupLocal = registerColor('scmGraph.historyItemGroupLocal', chartsBlue, localize('scmGraphHistoryItemGroupLocal', "Local history item group color.")); +export const historyItemGroupRemote = registerColor('scmGraph.historyItemGroupRemote', chartsPurple, localize('scmGraphHistoryItemGroupRemote', "Remote history item group color.")); +export const historyItemGroupBase = registerColor('scmGraph.historyItemGroupBase', chartsOrange, localize('scmGraphHistoryItemGroupBase', "Base history item group color.")); + +/** + * History item hover color + */ +export const historyItemGroupHoverLabelForeground = registerColor('scmGraph.historyItemHoverLabelForeground', buttonForeground, localize('scmGraphHistoryItemHoverLabelForeground', "History item hover label foreground color.")); + +/** + * History graph color registry + */ +export const colorRegistry: ColorIdentifier[] = [ + registerColor('scmGraph.foreground1', chartsGreen, localize('scmGraphForeground1', "Source control graph foreground color (1).")), + registerColor('scmGraph.foreground2', chartsRed, localize('scmGraphForeground2', "Source control graph foreground color (2).")), + registerColor('scmGraph.foreground3', chartsYellow, localize('scmGraphForeground3', "Source control graph foreground color (3).")), +]; + +function getLabelColorIdentifier(historyItem: ISCMHistoryItem, colorMap: Map): ColorIdentifier | undefined { + for (const label of historyItem.labels ?? []) { + const colorIndex = colorMap.get(label.title); + if (colorIndex !== undefined) { + return colorIndex; + } + } + + return undefined; +} + +function createPath(colorIdentifier: string): SVGPathElement { + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('fill', 'none'); + path.setAttribute('stroke-width', '1px'); + path.setAttribute('stroke-linecap', 'round'); + path.style.stroke = asCssVariable(colorIdentifier); + + return path; +} + +function drawCircle(index: number, radius: number, colorIdentifier: string): SVGCircleElement { + const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + circle.setAttribute('cx', `${SWIMLANE_WIDTH * (index + 1)}`); + circle.setAttribute('cy', `${SWIMLANE_WIDTH}`); + circle.setAttribute('r', `${radius}`); + circle.style.fill = asCssVariable(colorIdentifier); + + return circle; +} + +function drawVerticalLine(x1: number, y1: number, y2: number, color: string): SVGPathElement { + const path = createPath(color); + path.setAttribute('d', `M ${x1} ${y1} V ${y2}`); + + return path; +} + +function findLastIndex(nodes: ISCMHistoryItemGraphNode[], id: string): number { + for (let i = nodes.length - 1; i >= 0; i--) { + if (nodes[i].id === id) { + return i; + } + } + + return -1; +} + +export function renderSCMHistoryItemGraph(historyItemViewModel: ISCMHistoryItemViewModel): SVGElement { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.classList.add('graph'); + + const historyItem = historyItemViewModel.historyItem; + const inputSwimlanes = historyItemViewModel.inputSwimlanes; + const outputSwimlanes = historyItemViewModel.outputSwimlanes; + + // Find the history item in the input swimlanes + const inputIndex = inputSwimlanes.findIndex(node => node.id === historyItem.id); + + // Circle index - use the input swimlane index if present, otherwise add it to the end + const circleIndex = inputIndex !== -1 ? inputIndex : inputSwimlanes.length; + + // Circle color - use the output swimlane color if present, otherwise the input swimlane color + const circleColor = circleIndex < outputSwimlanes.length ? outputSwimlanes[circleIndex].color : + circleIndex < inputSwimlanes.length ? inputSwimlanes[circleIndex].color : historyItemGroupLocal; + + let outputSwimlaneIndex = 0; + for (let index = 0; index < inputSwimlanes.length; index++) { + const color = inputSwimlanes[index].color; + + // Current commit + if (inputSwimlanes[index].id === historyItem.id) { + // Base commit + if (index !== circleIndex) { + const d: string[] = []; + const path = createPath(color); + + // Draw / + d.push(`M ${SWIMLANE_WIDTH * (index + 1)} 0`); + d.push(`A ${SWIMLANE_WIDTH} ${SWIMLANE_WIDTH} 0 0 1 ${SWIMLANE_WIDTH * (index)} ${SWIMLANE_WIDTH}`); + + // Draw - + d.push(`H ${SWIMLANE_WIDTH * (circleIndex + 1)}`); + + path.setAttribute('d', d.join(' ')); + svg.append(path); + } else { + outputSwimlaneIndex++; + } + } else { + // Not the current commit + if (outputSwimlaneIndex < outputSwimlanes.length && + inputSwimlanes[index].id === outputSwimlanes[outputSwimlaneIndex].id) { + if (index === outputSwimlaneIndex) { + // Draw | + const path = drawVerticalLine(SWIMLANE_WIDTH * (index + 1), 0, SWIMLANE_HEIGHT, color); + svg.append(path); + } else { + const d: string[] = []; + const path = createPath(color); + + // Draw | + d.push(`M ${SWIMLANE_WIDTH * (index + 1)} 0`); + d.push(`V 6`); + + // Draw / + d.push(`A ${SWIMLANE_CURVE_RADIUS} ${SWIMLANE_CURVE_RADIUS} 0 0 1 ${(SWIMLANE_WIDTH * (index + 1)) - SWIMLANE_CURVE_RADIUS} ${SWIMLANE_HEIGHT / 2}`); + + // Draw - + d.push(`H ${(SWIMLANE_WIDTH * (outputSwimlaneIndex + 1)) + SWIMLANE_CURVE_RADIUS}`); + + // Draw / + d.push(`A ${SWIMLANE_CURVE_RADIUS} ${SWIMLANE_CURVE_RADIUS} 0 0 0 ${SWIMLANE_WIDTH * (outputSwimlaneIndex + 1)} ${(SWIMLANE_HEIGHT / 2) + SWIMLANE_CURVE_RADIUS}`); + + // Draw | + d.push(`V ${SWIMLANE_HEIGHT}`); + + path.setAttribute('d', d.join(' ')); + svg.append(path); + } + + outputSwimlaneIndex++; + } + } + } + + // Add remaining parent(s) + for (let i = 1; i < historyItem.parentIds.length; i++) { + const parentOutputIndex = findLastIndex(outputSwimlanes, historyItem.parentIds[i]); + if (parentOutputIndex === -1) { + continue; + } + + // Draw -\ + const d: string[] = []; + const path = createPath(outputSwimlanes[parentOutputIndex].color); + + // Draw \ + d.push(`M ${SWIMLANE_WIDTH * parentOutputIndex} ${SWIMLANE_HEIGHT / 2}`); + d.push(`A ${SWIMLANE_WIDTH} ${SWIMLANE_WIDTH} 0 0 1 ${SWIMLANE_WIDTH * (parentOutputIndex + 1)} ${SWIMLANE_HEIGHT}`); + + // Draw - + d.push(`M ${SWIMLANE_WIDTH * parentOutputIndex} ${SWIMLANE_HEIGHT / 2}`); + d.push(`H ${SWIMLANE_WIDTH * (circleIndex + 1)} `); + + path.setAttribute('d', d.join(' ')); + svg.append(path); + } + + // Draw | to * + if (inputIndex !== -1) { + const path = drawVerticalLine(SWIMLANE_WIDTH * (circleIndex + 1), 0, SWIMLANE_HEIGHT / 2, inputSwimlanes[inputIndex].color); + svg.append(path); + } + + // Draw | from * + if (historyItem.parentIds.length > 0) { + const path = drawVerticalLine(SWIMLANE_WIDTH * (circleIndex + 1), SWIMLANE_HEIGHT / 2, SWIMLANE_HEIGHT, circleColor); + svg.append(path); + } + + // Draw * + if (historyItem.parentIds.length > 1) { + // Multi-parent node + const circleOuter = drawCircle(circleIndex, CIRCLE_RADIUS + 1, circleColor); + svg.append(circleOuter); + + const circleInner = drawCircle(circleIndex, CIRCLE_RADIUS - 1, circleColor); + svg.append(circleInner); + } else { + // HEAD + // TODO@lszomoru - implement a better way to determine if the commit is HEAD + if (historyItem.labels?.some(l => ThemeIcon.isThemeIcon(l.icon) && l.icon.id === 'target')) { + const outerCircle = drawCircle(circleIndex, CIRCLE_RADIUS + 2, circleColor); + svg.append(outerCircle); + } + + // Node + const circle = drawCircle(circleIndex, CIRCLE_RADIUS, circleColor); + svg.append(circle); + } + + // Set dimensions + svg.style.height = `${SWIMLANE_HEIGHT}px`; + svg.style.width = `${SWIMLANE_WIDTH * (Math.max(inputSwimlanes.length, outputSwimlanes.length, 1) + 1)}px`; + + return svg; +} + +export function renderSCMHistoryGraphPlaceholder(columns: ISCMHistoryItemGraphNode[]): HTMLElement { + const elements = svgElem('svg', { + style: { height: `${SWIMLANE_HEIGHT}px`, width: `${SWIMLANE_WIDTH * (columns.length + 1)}px`, } + }); + + // Draw | + for (let index = 0; index < columns.length; index++) { + const path = drawVerticalLine(SWIMLANE_WIDTH * (index + 1), 0, SWIMLANE_HEIGHT, columns[index].color); + elements.root.append(path); + } + + return elements.root; +} + +export function toISCMHistoryItemViewModelArray(historyItems: ISCMHistoryItem[], colorMap = new Map()): ISCMHistoryItemViewModel[] { + let colorIndex = -1; + const viewModels: ISCMHistoryItemViewModel[] = []; + + for (let index = 0; index < historyItems.length; index++) { + const historyItem = historyItems[index]; + + const outputSwimlanesFromPreviousItem = lastOrDefault(viewModels)?.outputSwimlanes ?? []; + const inputSwimlanes = outputSwimlanesFromPreviousItem.map(i => deepClone(i)); + const outputSwimlanes: ISCMHistoryItemGraphNode[] = []; + + let firstParentAdded = false; + + // Add first parent to the output + if (historyItem.parentIds.length > 0) { + for (const node of inputSwimlanes) { + if (node.id === historyItem.id) { + if (!firstParentAdded) { + outputSwimlanes.push({ + id: historyItem.parentIds[0], + color: getLabelColorIdentifier(historyItem, colorMap) ?? node.color + }); + firstParentAdded = true; + } + + continue; + } + + outputSwimlanes.push(deepClone(node)); + } + } + + // Add unprocessed parent(s) to the output + for (let i = firstParentAdded ? 1 : 0; i < historyItem.parentIds.length; i++) { + // Color index (label -> next color) + let colorIdentifier: string | undefined; + + if (!firstParentAdded) { + colorIdentifier = getLabelColorIdentifier(historyItem, colorMap); + } else { + const historyItemParent = historyItems + .find(h => h.id === historyItem.parentIds[i]); + colorIdentifier = historyItemParent ? getLabelColorIdentifier(historyItemParent, colorMap) : undefined; + } + + if (!colorIdentifier) { + colorIndex = rot(colorIndex + 1, colorRegistry.length); + colorIdentifier = colorRegistry[colorIndex]; + } + + outputSwimlanes.push({ + id: historyItem.parentIds[i], + color: colorIdentifier + }); + } + + viewModels.push({ + historyItem, + inputSwimlanes, + outputSwimlanes, + }); + } + + return viewModels; +} diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts new file mode 100644 index 00000000..4805c6e2 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -0,0 +1,1037 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/scm'; +import { Event } from 'vs/base/common/event'; +import * as platform from 'vs/base/common/platform'; +import { $, append } from 'vs/base/browser/dom'; +import { IHoverOptions, IManagedHover, IManagedHoverTooltipMarkdownString } from 'vs/base/browser/ui/hover/hover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { LabelFuzzyScore } from 'vs/base/browser/ui/tree/abstractTree'; +import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { fromNow } from 'vs/base/common/date'; +import { createMatches, FuzzyScore, IMatch } from 'vs/base/common/filters'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { autorun, autorunWithStore, IObservable, ISettableObservable, observableValue } from 'vs/base/common/observable'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IHoverService, WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IOpenEvent, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ColorIdentifier, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IViewPaneOptions, ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { renderSCMHistoryItemGraph, historyItemGroupLocal, historyItemGroupRemote, historyItemGroupBase, historyItemGroupHoverLabelForeground, toISCMHistoryItemViewModelArray, SWIMLANE_WIDTH, renderSCMHistoryGraphPlaceholder } from 'vs/workbench/contrib/scm/browser/scmHistory'; +import { RepositoryActionRunner } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; +import { collectContextMenuActions, connectPrimaryMenu, getActionViewItemProvider, isSCMHistoryItemLoadMoreTreeElement, isSCMHistoryItemViewModelTreeElement, isSCMRepository, isSCMViewService } from 'vs/workbench/contrib/scm/browser/util'; +import { ISCMHistoryItem, ISCMHistoryItemGroup, ISCMHistoryItemViewModel, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from 'vs/workbench/contrib/scm/common/history'; +import { HISTORY_VIEW_PANE_ID, ISCMProvider, ISCMRepository, ISCMService, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent } from 'vs/workbench/contrib/scm/common/scm'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { stripIcons } from 'vs/base/common/iconLabels'; +import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IMenuService, MenuId, MenuItemAction, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Iterable } from 'vs/base/common/iterator'; +import { Sequencer, Throttler } from 'vs/base/common/async'; +import { URI } from 'vs/base/common/uri'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; +import { tail } from 'vs/base/common/arrays'; +import { Codicon } from 'vs/base/common/codicons'; +import { ContextKeys } from 'vs/workbench/contrib/scm/browser/scmViewPane'; +import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { IProgressService } from 'vs/platform/progress/common/progress'; + +const historyItemAdditionsForeground = registerColor('scmGraph.historyItemHoverAdditionsForeground', 'gitDecoration.addedResourceForeground', localize('scmGraph.HistoryItemHoverAdditionsForeground', "History item hover additions foreground color.")); +const historyItemDeletionsForeground = registerColor('scmGraph.historyItemHoverDeletionsForeground', 'gitDecoration.deletedResourceForeground', localize('scmGraph.HistoryItemHoverDeletionsForeground', "History item hover deletions foreground color.")); + +type TreeElement = ISCMRepository | SCMHistoryItemViewModelTreeElement | SCMHistoryItemLoadMoreTreeElement; + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'workbench.scm.action.refreshGraph', + title: localize('refreshGraph', "Refresh"), + viewId: HISTORY_VIEW_PANE_ID, + f1: false, + icon: Codicon.refresh, + menu: { + id: MenuId.SCMHistoryTitle, + when: ContextKeyExpr.or( + ContextKeyExpr.has('scmRepository'), + ContextKeyExpr.and(ContextKeys.RepositoryVisibilityCount.isEqualTo(1), ContextKeyExpr.equals('config.scm.alwaysShowRepositories', false)) + ), + group: 'navigation', + order: 1000 + } + }); + } + + async runInView(accessor: ServicesAccessor, view: SCMHistoryViewPane, provider?: ISCMProvider): Promise { + const scmService = accessor.get(ISCMService); + const repository = provider ? scmService.getRepository(provider.id) : undefined; + + view.refresh(repository); + } +}); + +class ListDelegate implements IListVirtualDelegate { + + getHeight(): number { + return 22; + } + + getTemplateId(element: TreeElement): string { + if (isSCMRepository(element)) { + return RepositoryRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + return HistoryItemRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemLoadMoreTreeElement(element)) { + return HistoryItemLoadMoreRenderer.TEMPLATE_ID; + } else { + throw new Error('Unknown element'); + } + } +} + +interface RepositoryTemplate { + readonly label: IconLabel; + readonly labelCustomHover: IManagedHover; + readonly stateLabel: HTMLElement; + readonly toolBar: WorkbenchToolBar; + readonly elementDisposables: DisposableStore; + readonly templateDisposable: IDisposable; +} + +class RepositoryRenderer implements ITreeRenderer { + + static readonly TEMPLATE_ID = 'repository'; + get templateId(): string { return RepositoryRenderer.TEMPLATE_ID; } + + constructor( + private readonly description: (repository: ISCMRepository) => IObservable, + private readonly actionRunner: IActionRunner, + private readonly actionViewItemProvider: IActionViewItemProvider, + @ICommandService private commandService: ICommandService, + @IContextKeyService private contextKeyService: IContextKeyService, + @IContextMenuService private contextMenuService: IContextMenuService, + @IHoverService private hoverService: IHoverService, + @IKeybindingService private keybindingService: IKeybindingService, + @IMenuService private menuService: IMenuService, + @ITelemetryService private telemetryService: ITelemetryService + ) { } + + renderTemplate(container: HTMLElement): RepositoryTemplate { + // hack + if (container.classList.contains('monaco-tl-contents')) { + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-twistie'); + } + + const element = append(container, $('.scm-provider')); + const label = new IconLabel(element); + const labelCustomHover = this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), label.element, '', {}); + const stateLabel = append(element, $('div.state-label.monaco-count-badge.long')); + const toolBar = new WorkbenchToolBar(append(element, $('.actions')), { actionRunner: this.actionRunner, actionViewItemProvider: this.actionViewItemProvider, resetMenu: MenuId.SCMHistoryTitle }, this.menuService, this.contextKeyService, this.contextMenuService, this.keybindingService, this.commandService, this.telemetryService); + + return { label, labelCustomHover, stateLabel, toolBar, elementDisposables: new DisposableStore(), templateDisposable: combinedDisposable(labelCustomHover, toolBar) }; + } + + renderElement(arg: ISCMRepository | ITreeNode, index: number, templateData: RepositoryTemplate, height: number | undefined): void { + const repository = isSCMRepository(arg) ? arg : arg.element; + + templateData.elementDisposables.add(autorun(reader => { + const description = this.description(repository).read(reader); + templateData.stateLabel.style.display = description !== '' ? '' : 'none'; + templateData.stateLabel.textContent = description; + })); + + templateData.label.setLabel(repository.provider.name); + templateData.labelCustomHover.update(repository.provider.rootUri ? `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` : repository.provider.label); + + templateData.elementDisposables.add(autorunWithStore((reader, store) => { + const currentHistoryItemGroup = repository.provider.historyProvider.read(reader)?.currentHistoryItemGroup.read(reader); + if (!currentHistoryItemGroup) { + templateData.toolBar.setActions([], []); + return; + } + + const contextKeyService = this.contextKeyService.createOverlay([ + ['scmRepository', repository.id], + ['scmProvider', repository.provider.contextValue], + ['scmHistoryItemGroupHasRemote', !!currentHistoryItemGroup.remote], + ]); + const menu = this.menuService.createMenu(MenuId.SCMHistoryTitle, contextKeyService); + store.add(connectPrimaryMenu(menu, (primary, secondary) => { + templateData.toolBar.setActions(primary, secondary); + })); + })); + + templateData.toolBar.context = repository.provider; + } + + disposeElement(group: ISCMRepository | ITreeNode, index: number, template: RepositoryTemplate): void { + template.elementDisposables.clear(); + } + + disposeTemplate(templateData: RepositoryTemplate): void { + templateData.elementDisposables.dispose(); + templateData.templateDisposable.dispose(); + } +} + +interface HistoryItemTemplate { + readonly element: HTMLElement; + readonly label: IconLabel; + readonly graphContainer: HTMLElement; + readonly labelContainer: HTMLElement; + readonly elementDisposables: DisposableStore; + readonly disposables: IDisposable; +} + +class HistoryItemRenderer implements ITreeRenderer { + + static readonly TEMPLATE_ID = 'history-item'; + get templateId(): string { return HistoryItemRenderer.TEMPLATE_ID; } + + constructor( + private readonly hoverDelegate: IHoverDelegate, + @IHoverService private readonly hoverService: IHoverService, + @IThemeService private readonly themeService: IThemeService + ) { } + + renderTemplate(container: HTMLElement): HistoryItemTemplate { + // hack + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-no-twistie'); + + const element = append(container, $('.history-item')); + const graphContainer = append(element, $('.graph-container')); + const iconLabel = new IconLabel(element, { supportIcons: true, supportHighlights: true, supportDescriptionHighlights: true }); + + const labelContainer = append(element, $('.label-container')); + element.appendChild(labelContainer); + + return { element, graphContainer, label: iconLabel, labelContainer, elementDisposables: new DisposableStore(), disposables: new DisposableStore() }; + } + + renderElement(node: ITreeNode, index: number, templateData: HistoryItemTemplate, height: number | undefined): void { + const historyItemViewModel = node.element.historyItemViewModel; + const historyItem = historyItemViewModel.historyItem; + + const historyItemHover = this.hoverService.setupManagedHover(this.hoverDelegate, templateData.element, this.getTooltip(node.element)); + templateData.elementDisposables.add(historyItemHover); + + templateData.graphContainer.textContent = ''; + templateData.graphContainer.appendChild(renderSCMHistoryItemGraph(historyItemViewModel)); + + const [matches, descriptionMatches] = this.processMatches(historyItemViewModel, node.filterData); + templateData.label.setLabel(historyItem.message, historyItem.author, { matches, descriptionMatches }); + + templateData.labelContainer.textContent = ''; + if (historyItem.labels) { + // Get lits of labels to render (current, remote, base) + const labels = this.getLabels(node.element.repository); + + for (const label of historyItem.labels) { + if (label.icon && ThemeIcon.isThemeIcon(label.icon) && labels.includes(label.title)) { + const icon = append(templateData.labelContainer, $('div.label')); + icon.classList.add(...ThemeIcon.asClassNameArray(label.icon)); + } + } + } + } + + private getLabels(repository: ISCMRepository): string[] { + const currentHistoryItemGroup = repository.provider.historyProvider.get()?.currentHistoryItemGroup.get(); + if (!currentHistoryItemGroup) { + return []; + } + + return [ + currentHistoryItemGroup.name, + currentHistoryItemGroup.remote?.name, + currentHistoryItemGroup.base?.name] + .filter(l => l !== undefined); + } + + private getTooltip(element: SCMHistoryItemViewModelTreeElement): IManagedHoverTooltipMarkdownString { + const colorTheme = this.themeService.getColorTheme(); + const historyItem = element.historyItemViewModel.historyItem; + const currentHistoryItemGroup = element.repository.provider.historyProvider.get()?.currentHistoryItemGroup?.get(); + + const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); + + if (historyItem.author) { + markdown.appendMarkdown(`$(account) **${historyItem.author}**`); + + if (historyItem.timestamp) { + const dateFormatter = new Intl.DateTimeFormat(platform.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); + markdown.appendMarkdown(`, $(history) ${fromNow(historyItem.timestamp, true, true)} (${dateFormatter.format(historyItem.timestamp)})`); + } + + markdown.appendMarkdown('\n\n'); + } + + markdown.appendMarkdown(`${historyItem.message}\n\n`); + + if (historyItem.statistics) { + markdown.appendMarkdown(`---\n\n`); + + markdown.appendMarkdown(`${historyItem.statistics.files === 1 ? + localize('fileChanged', "{0} file changed", historyItem.statistics.files) : + localize('filesChanged', "{0} files changed", historyItem.statistics.files)}`); + + if (historyItem.statistics.insertions) { + const historyItemAdditionsForegroundColor = colorTheme.getColor(historyItemAdditionsForeground); + markdown.appendMarkdown(`, ${historyItem.statistics.insertions === 1 ? + localize('insertion', "{0} insertion{1}", historyItem.statistics.insertions, '(+)') : + localize('insertions', "{0} insertions{1}", historyItem.statistics.insertions, '(+)')}`); + } + + if (historyItem.statistics.deletions) { + const historyItemDeletionsForegroundColor = colorTheme.getColor(historyItemDeletionsForeground); + markdown.appendMarkdown(`, ${historyItem.statistics.deletions === 1 ? + localize('deletion', "{0} deletion{1}", historyItem.statistics.deletions, '(-)') : + localize('deletions', "{0} deletions{1}", historyItem.statistics.deletions, '(-)')}`); + } + } + + const labels = this.getLabels(element.repository); + const historyItemLabels = (historyItem.labels ?? []) + .filter(l => labels.includes(l.title)); + + if (historyItemLabels.length > 0) { + const historyItemGroupLocalColor = colorTheme.getColor(historyItemGroupLocal); + const historyItemGroupRemoteColor = colorTheme.getColor(historyItemGroupRemote); + const historyItemGroupBaseColor = colorTheme.getColor(historyItemGroupBase); + + const historyItemGroupHoverLabelForegroundColor = colorTheme.getColor(historyItemGroupHoverLabelForeground); + + markdown.appendMarkdown(`\n\n---\n\n`); + markdown.appendMarkdown(historyItemLabels.map(label => { + const historyItemGroupHoverLabelBackgroundColor = + label.title === currentHistoryItemGroup?.name ? historyItemGroupLocalColor : + label.title === currentHistoryItemGroup?.remote?.name ? historyItemGroupRemoteColor : + label.title === currentHistoryItemGroup?.base?.name ? historyItemGroupBaseColor : + undefined; + + const historyItemGroupHoverLabelIconId = ThemeIcon.isThemeIcon(label.icon) ? label.icon.id : ''; + + return ` $(${historyItemGroupHoverLabelIconId}) ${label.title} `; + }).join('  ')); + } + + return { markdown, markdownNotSupportedFallback: historyItem.message }; + } + + private processMatches(historyItemViewModel: ISCMHistoryItemViewModel, filterData: LabelFuzzyScore | undefined): [IMatch[] | undefined, IMatch[] | undefined] { + if (!filterData) { + return [undefined, undefined]; + } + + return [ + historyItemViewModel.historyItem.message === filterData.label ? createMatches(filterData.score) : undefined, + historyItemViewModel.historyItem.author === filterData.label ? createMatches(filterData.score) : undefined + ]; + } + + disposeElement(element: ITreeNode, index: number, templateData: HistoryItemTemplate, height: number | undefined): void { + templateData.elementDisposables.clear(); + } + + disposeTemplate(templateData: HistoryItemTemplate): void { + templateData.disposables.dispose(); + } +} + +interface LoadMoreTemplate { + readonly element: HTMLElement; + readonly graphPlaceholder: HTMLElement; + readonly historyItemPlaceholderContainer: HTMLElement; + readonly historyItemPlaceholderLabel: IconLabel; + readonly elementDisposables: DisposableStore; + readonly disposables: IDisposable; +} + +class HistoryItemLoadMoreRenderer implements ITreeRenderer { + + static readonly TEMPLATE_ID = 'historyItemLoadMore'; + get templateId(): string { return HistoryItemLoadMoreRenderer.TEMPLATE_ID; } + + constructor( + private readonly _loadingMore: (repository: ISCMRepository) => IObservable, + private readonly _loadMoreCallback: (repository: ISCMRepository) => void, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ISCMViewService private readonly _scmViewService: ISCMViewService) { } + + renderTemplate(container: HTMLElement): LoadMoreTemplate { + // hack + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-no-twistie'); + + const element = append(container, $('.history-item-load-more')); + const graphPlaceholder = append(element, $('.graph-placeholder')); + const historyItemPlaceholderContainer = append(element, $('.history-item-placeholder')); + const historyItemPlaceholderLabel = new IconLabel(historyItemPlaceholderContainer, { supportIcons: true }); + + return { element, graphPlaceholder, historyItemPlaceholderContainer, historyItemPlaceholderLabel, elementDisposables: new DisposableStore(), disposables: new DisposableStore() }; + } + + renderElement(element: ITreeNode, index: number, templateData: LoadMoreTemplate, height: number | undefined): void { + const repositoryCount = this._scmViewService.visibleRepositories.length; + const alwaysShowRepositories = this._configurationService.getValue('scm.alwaysShowRepositories') === true; + + templateData.graphPlaceholder.textContent = ''; + templateData.graphPlaceholder.style.width = `${SWIMLANE_WIDTH * (element.element.graphColumns.length + 1)}px`; + templateData.graphPlaceholder.appendChild(renderSCMHistoryGraphPlaceholder(element.element.graphColumns)); + + templateData.historyItemPlaceholderContainer.classList.toggle('shimmer', repositoryCount === 1 && !alwaysShowRepositories); + + if (repositoryCount > 1 || alwaysShowRepositories) { + templateData.elementDisposables.add(autorun(reader => { + const loadingMore = this._loadingMore(element.element.repository).read(reader); + const icon = `$(${loadingMore ? 'loading~spin' : 'fold-down'})`; + + templateData.historyItemPlaceholderLabel.setLabel(localize('loadMore', "{0} Load More...", icon)); + })); + } else { + templateData.historyItemPlaceholderLabel.setLabel(''); + this._loadMoreCallback(element.element.repository); + } + } + + disposeTemplate(templateData: LoadMoreTemplate): void { + templateData.disposables.dispose(); + } +} + +class HistoryItemActionRunner extends ActionRunner { + constructor(private readonly getSelectedHistoryItems: () => SCMHistoryItemViewModelTreeElement[]) { + super(); + } + + protected override async runAction(action: IAction, context: SCMHistoryItemViewModelTreeElement): Promise { + if (!(action instanceof MenuItemAction)) { + return super.runAction(action, context); + } + + const args: (ISCMProvider | ISCMHistoryItem)[] = []; + args.push(context.repository.provider); + + const selection = this.getSelectedHistoryItems(); + const contextIsSelected = selection.some(s => s === context); + if (contextIsSelected && selection.length > 1) { + args.push(...selection.map(h => ( + { + id: h.historyItemViewModel.historyItem.id, + parentIds: h.historyItemViewModel.historyItem.parentIds, + message: h.historyItemViewModel.historyItem.message, + author: h.historyItemViewModel.historyItem.author, + icon: h.historyItemViewModel.historyItem.icon, + timestamp: h.historyItemViewModel.historyItem.timestamp, + statistics: h.historyItemViewModel.historyItem.statistics, + } satisfies ISCMHistoryItem))); + } else { + args.push({ + id: context.historyItemViewModel.historyItem.id, + parentIds: context.historyItemViewModel.historyItem.parentIds, + message: context.historyItemViewModel.historyItem.message, + author: context.historyItemViewModel.historyItem.author, + icon: context.historyItemViewModel.historyItem.icon, + timestamp: context.historyItemViewModel.historyItem.timestamp, + statistics: context.historyItemViewModel.historyItem.statistics, + } satisfies ISCMHistoryItem); + } + + await action.run(...args); + } +} + +class HistoryItemHoverDelegate extends WorkbenchHoverDelegate { + constructor( + private readonly _viewContainerLocation: ViewContainerLocation | null, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IConfigurationService configurationService: IConfigurationService, + @IHoverService hoverService: IHoverService, + + ) { + super('element', true, () => this.getHoverOptions(), configurationService, hoverService); + } + + private getHoverOptions(): Partial { + const sideBarPosition = this.layoutService.getSideBarPosition(); + + let hoverPosition: HoverPosition; + if (this._viewContainerLocation === ViewContainerLocation.Sidebar) { + hoverPosition = sideBarPosition === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT; + } else if (this._viewContainerLocation === ViewContainerLocation.AuxiliaryBar) { + hoverPosition = sideBarPosition === Position.LEFT ? HoverPosition.LEFT : HoverPosition.RIGHT; + } else { + hoverPosition = HoverPosition.RIGHT; + } + + return { additionalClasses: ['history-item-hover'], position: { hoverPosition, forcePosition: true } }; + } +} + +class SCMHistoryViewPaneActionRunner extends ActionRunner { + constructor(@IProgressService private readonly _progressService: IProgressService) { + super(); + } + + protected override runAction(action: IAction, context?: unknown): Promise { + return this._progressService.withProgress({ location: HISTORY_VIEW_PANE_ID }, + async () => await super.runAction(action, context)); + } +} + +class SCMHistoryTreeAccessibilityProvider implements IListAccessibilityProvider { + + getWidgetAriaLabel(): string { + return localize('scm history', "Source Control History"); + } + + getAriaLabel(element: TreeElement): string { + if (isSCMRepository(element)) { + return `${element.provider.name} ${element.provider.label}`; + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + const historyItem = element.historyItemViewModel.historyItem; + return `${stripIcons(historyItem.message).trim()}${historyItem.author ? `, ${historyItem.author}` : ''}`; + } else { + return ''; + } + } +} + +class SCMHistoryTreeIdentityProvider implements IIdentityProvider { + + getId(element: TreeElement): string { + if (isSCMRepository(element)) { + const provider = element.provider; + return `repo:${provider.id}`; + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + const provider = element.repository.provider; + const historyItem = element.historyItemViewModel.historyItem; + return `historyItem:${provider.id}/${historyItem.id}/${historyItem.parentIds.join(',')}`; + } else if (isSCMHistoryItemLoadMoreTreeElement(element)) { + const provider = element.repository.provider; + return `historyItemLoadMore:${provider.id}}`; + } else { + throw new Error('Invalid tree element'); + } + } +} + +class SCMHistoryTreeKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + getKeyboardNavigationLabel(element: TreeElement): { toString(): string } | { toString(): string }[] | undefined { + if (isSCMRepository(element)) { + return undefined; + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + // For a history item we want to match both the message and + // the author. A match in the message takes precedence over + // a match in the author. + return [element.historyItemViewModel.historyItem.message, element.historyItemViewModel.historyItem.author]; + } else if (isSCMHistoryItemLoadMoreTreeElement(element)) { + // We don't want to match the load more element + return ''; + } else { + throw new Error('Invalid tree element'); + } + } +} + +type HistoryItemState = { currentHistoryItemGroup: ISCMHistoryItemGroup; items: ISCMHistoryItem[]; loadMore: boolean }; + +class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource { + private readonly _state = new Map(); + + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ISCMViewService private readonly _scmViewService: ISCMViewService + ) { + super(); + } + + async getChildren(inputOrElement: ISCMViewService | TreeElement): Promise> { + const repositoryCount = this._scmViewService.visibleRepositories.length; + const alwaysShowRepositories = this._configurationService.getValue('scm.alwaysShowRepositories') === true; + + if (isSCMViewService(inputOrElement) && (repositoryCount > 1 || alwaysShowRepositories)) { + return this._scmViewService.visibleRepositories; + } else if ((isSCMViewService(inputOrElement) && repositoryCount === 1 && !alwaysShowRepositories) || isSCMRepository(inputOrElement)) { + const children: TreeElement[] = []; + inputOrElement = isSCMRepository(inputOrElement) ? inputOrElement : this._scmViewService.visibleRepositories[0]; + + const historyItems = await this._getHistoryItems(inputOrElement); + children.push(...historyItems); + + const lastHistoryItem = tail(historyItems); + if (lastHistoryItem && lastHistoryItem.historyItemViewModel.outputSwimlanes.length > 0) { + children.push({ + repository: inputOrElement, + graphColumns: lastHistoryItem.historyItemViewModel.outputSwimlanes, + type: 'historyItemLoadMore' + } satisfies SCMHistoryItemLoadMoreTreeElement); + } + + return children; + } + return []; + } + + hasChildren(inputOrElement: ISCMViewService | TreeElement): boolean { + if (isSCMViewService(inputOrElement)) { + return this._scmViewService.visibleRepositories.length !== 0; + } else if (isSCMRepository(inputOrElement)) { + return true; + } else if (isSCMHistoryItemViewModelTreeElement(inputOrElement)) { + return false; + } else if (isSCMHistoryItemLoadMoreTreeElement(inputOrElement)) { + return false; + } else { + throw new Error('hasChildren not implemented.'); + } + } + + clearState(repository?: ISCMRepository): void { + if (!repository) { + this._state.clear(); + return; + } + + this._state.delete(repository); + } + + loadMore(repository: ISCMRepository): void { + const state = this._state.get(repository); + if (!state) { + return; + } + + this._state.set(repository, { ...state, loadMore: true }); + } + + private async _getHistoryItems(element: ISCMRepository): Promise { + let state = this._state.get(element); + const historyProvider = element.provider.historyProvider.get(); + const currentHistoryItemGroup = state?.currentHistoryItemGroup ?? historyProvider?.currentHistoryItemGroup.get(); + + if (!historyProvider || !currentHistoryItemGroup) { + return []; + } + + if (!state || state.loadMore) { + const historyItemGroupIds = [ + currentHistoryItemGroup.revision ?? currentHistoryItemGroup.id, + ...currentHistoryItemGroup.remote ? [currentHistoryItemGroup.remote.revision ?? currentHistoryItemGroup.remote.id] : [], + ...currentHistoryItemGroup.base ? [currentHistoryItemGroup.base.revision ?? currentHistoryItemGroup.base.id] : [], + ]; + + const existingHistoryItems = state?.items ?? []; + const historyItems = await historyProvider.provideHistoryItems2({ + historyItemGroupIds, limit: 50, skip: existingHistoryItems.length + }) ?? []; + + state = { + currentHistoryItemGroup, + items: [...existingHistoryItems, ...historyItems], + loadMore: false + }; + + this._state.set(element, state); + } + + // Create the color map + const colorMap = new Map([ + [currentHistoryItemGroup.name, historyItemGroupLocal] + ]); + if (currentHistoryItemGroup.remote) { + colorMap.set(currentHistoryItemGroup.remote.name, historyItemGroupRemote); + } + if (currentHistoryItemGroup.base) { + colorMap.set(currentHistoryItemGroup.base.name, historyItemGroupBase); + } + + return toISCMHistoryItemViewModelArray(state.items, colorMap) + .map(historyItemViewModel => ({ + repository: element, + historyItemViewModel, + type: 'historyItem2' + }) satisfies SCMHistoryItemViewModelTreeElement); + } + + override dispose(): void { + this._state.clear(); + super.dispose(); + } +} + +export class SCMHistoryViewPane extends ViewPane { + + private _treeContainer!: HTMLElement; + private _tree!: WorkbenchAsyncDataTree; + private _treeDataSource!: SCMHistoryTreeDataSource; + private _treeIdentityProvider!: SCMHistoryTreeIdentityProvider; + private _repositoryDescription = new Map>(); + private _repositoryLoadMore = new Map>(); + + private readonly _actionRunner: IActionRunner; + private readonly _repositories = new DisposableMap(); + private readonly _visibilityDisposables = new DisposableStore(); + + private readonly _treeOperationSequencer = new Sequencer(); + private readonly _updateChildrenThrottler = new Throttler(); + + private readonly _scmHistoryItemGroupHasRemoteContextKey: IContextKey; + + private readonly _providerCountBadgeConfig = observableConfigValue<'hidden' | 'auto' | 'visible'>('scm.providerCountBadge', 'hidden', this.configurationService); + + constructor( + options: IViewPaneOptions, + @ICommandService private readonly _commandService: ICommandService, + @ISCMViewService private readonly _scmViewService: ISCMViewService, + @IProgressService private readonly _progressService: IProgressService, + @IConfigurationService configurationService: IConfigurationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @IHoverService hoverService: IHoverService + ) { + super({ ...options, titleMenuId: MenuId.SCMHistoryTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); + + this._scmHistoryItemGroupHasRemoteContextKey = this.scopedContextKeyService.createKey('scmHistoryItemGroupHasRemote', undefined); + + this._actionRunner = this.instantiationService.createInstance(SCMHistoryViewPaneActionRunner); + this._register(this._actionRunner); + + this._register(this._updateChildrenThrottler); + } + + protected override layoutBody(height: number, width: number): void { + super.layoutBody(height, width); + this._tree.layout(height, width); + } + + protected override renderBody(container: HTMLElement): void { + super.renderBody(container); + + this._treeContainer = append(container, $('.scm-view.scm-history-view')); + this._treeContainer.classList.add('file-icon-themable-tree'); + + this._register(autorun(reader => { + const providerCountBadgeConfig = this._providerCountBadgeConfig.read(reader); + + this._treeContainer.classList.toggle('hide-provider-counts', providerCountBadgeConfig === 'hidden'); + this._treeContainer.classList.toggle('auto-provider-counts', providerCountBadgeConfig === 'auto'); + })); + + this._createTree(this._treeContainer); + + this.onDidChangeBodyVisibility(visible => { + if (visible) { + this._treeOperationSequencer.queue(async () => { + await this._tree.setInput(this._scmViewService); + + Event.filter(this.configurationService.onDidChangeConfiguration, + e => + e.affectsConfiguration('scm.alwaysShowRepositories'), + this._visibilityDisposables) + (() => { + this.updateActions(); + this.refresh(); + }, this, this._visibilityDisposables); + + // Add visible repositories + this._scmViewService.onDidChangeVisibleRepositories(this._onDidChangeVisibleRepositories, this, this._visibilityDisposables); + this._onDidChangeVisibleRepositories({ added: this._scmViewService.visibleRepositories, removed: Iterable.empty() }); + + this._tree.scrollTop = 0; + }); + } else { + this._treeDataSource.clearState(); + this._visibilityDisposables.clear(); + this._repositories.clearAndDisposeAll(); + } + }); + } + + override getActionRunner(): IActionRunner | undefined { + return this._actionRunner; + } + + async refresh(repository?: ISCMRepository): Promise { + this._treeDataSource.clearState(repository); + await this._updateChildren(repository); + + this._setRepositoryDescription(repository, ''); + this._tree.scrollTop = 0; + } + + private _createTree(container: HTMLElement): void { + this._treeIdentityProvider = new SCMHistoryTreeIdentityProvider(); + + const historyItemHoverDelegate = this.instantiationService.createInstance(HistoryItemHoverDelegate, this.viewDescriptorService.getViewLocationById(this.id)); + this._register(historyItemHoverDelegate); + + this._treeDataSource = this.instantiationService.createInstance(SCMHistoryTreeDataSource); + this._register(this._treeDataSource); + + this._tree = this.instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'SCM History Tree', + container, + new ListDelegate(), + [ + this.instantiationService.createInstance(RepositoryRenderer, repository => this._getRepositoryDescription(repository), this._actionRunner, getActionViewItemProvider(this.instantiationService)), + this.instantiationService.createInstance(HistoryItemRenderer, historyItemHoverDelegate), + this.instantiationService.createInstance(HistoryItemLoadMoreRenderer, repository => this._getLoadMore(repository), repository => this._loadMoreCallback(repository)), + ], + this._treeDataSource, + { + accessibilityProvider: new SCMHistoryTreeAccessibilityProvider(), + identityProvider: this._treeIdentityProvider, + collapseByDefault: (e: unknown) => !isSCMRepository(e), + keyboardNavigationLabelProvider: new SCMHistoryTreeKeyboardNavigationLabelProvider(), + horizontalScrolling: false, + multipleSelectionSupport: false, + } + ) as WorkbenchAsyncDataTree; + this._register(this._tree); + + this._tree.onDidOpen(this._onDidOpen, this, this._store); + this._tree.onContextMenu(this._onContextMenu, this, this._store); + } + + private async _onDidOpen(e: IOpenEvent): Promise { + if (!e.element) { + return; + } else if (isSCMRepository(e.element)) { + this._scmViewService.focus(e.element); + } else if (isSCMHistoryItemViewModelTreeElement(e.element)) { + const historyItem = e.element.historyItemViewModel.historyItem; + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + + const historyProvider = e.element.repository.provider.historyProvider.get(); + const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItem.id, historyItemParentId); + if (historyItemChanges) { + const title = `${historyItem.id.substring(0, 8)} - ${historyItem.message}`; + + const rootUri = e.element.repository.provider.rootUri; + const path = rootUri ? rootUri.path : e.element.repository.provider.label; + const multiDiffSourceUri = URI.from({ scheme: 'scm-history-item', path: `${path}/${historyItemParentId}..${historyItem.id}` }, true); + + await this._commandService.executeCommand('_workbench.openMultiDiffEditor', { title, multiDiffSourceUri, resources: historyItemChanges }); + } + + this._scmViewService.focus(e.element.repository); + } else if (isSCMHistoryItemLoadMoreTreeElement(e.element)) { + const repositoryCount = this._scmViewService.visibleRepositories.length; + const alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories') === true; + + if (repositoryCount > 1 || alwaysShowRepositories) { + this._loadMoreCallback(e.element.repository); + this._tree.setSelection([]); + } + } + } + + private _onContextMenu(e: ITreeContextMenuEvent): void { + const element = e.element; + + if (!element) { + return; + } + + let actions: IAction[] = []; + let context: TreeElement | ISCMProvider = element; + let actionRunner: IActionRunner = new HistoryItemActionRunner(() => this._getSelectedHistoryItems()); + + if (isSCMRepository(element)) { + const menus = this._scmViewService.menus.getRepositoryMenus(element.provider); + const menu = menus.repositoryContextMenu; + + actions = collectContextMenuActions(menu); + actionRunner = new RepositoryActionRunner(() => this._getSelectedRepositories()); + context = element.provider; + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + const menus = this._scmViewService.menus.getRepositoryMenus(element.repository.provider); + const menu = menus.historyProviderMenu?.getHistoryItemMenu2(element); + + actions = menu ? collectContextMenuActions(menu) : []; + } + + actionRunner.onWillRun(() => this._tree.domFocus()); + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions, + getActionsContext: () => context, + actionRunner + }); + } + + private _onDidChangeVisibleRepositories({ added, removed }: ISCMViewVisibleRepositoryChangeEvent): void { + // Added repositories + for (const repository of added) { + const repositoryDisposables = new DisposableStore(); + + repositoryDisposables.add(autorun(reader => { + const historyProvider = repository.provider.historyProvider.read(reader); + const currentHistoryItemGroupId = historyProvider?.currentHistoryItemGroupId.read(reader); + const currentHistoryItemGroupRevision = historyProvider?.currentHistoryItemGroupRevision.read(reader); + const currentHistoryItemGroupRemoteId = historyProvider?.currentHistoryItemGroupRemoteId.read(reader); + + // Update scmHistoryItemGroupHasRemote context key + if (this._scmViewService.visibleRepositories.length === 1) { + this._scmHistoryItemGroupHasRemoteContextKey.set(!!currentHistoryItemGroupRemoteId); + } else { + this._scmHistoryItemGroupHasRemoteContextKey.reset(); + } + + if (!currentHistoryItemGroupId && !currentHistoryItemGroupRevision && !currentHistoryItemGroupRemoteId) { + return; + } + + this.refresh(repository); + })); + + repositoryDisposables.add(autorun(reader => { + const historyProvider = repository.provider.historyProvider.read(reader); + const currentHistoryItemGroupRemoteRevision = historyProvider?.currentHistoryItemGroupRemoteRevision.read(reader); + + if (!currentHistoryItemGroupRemoteRevision) { + return; + } + + // Remote revision changes can occur as a result of a user action (Fetch, Push) but + // it can also occur as a result of background action (Auto Fetch). If the tree is + // scrolled to the top, we can safely refresh the tree. + if (this._tree.scrollTop === 0) { + this.refresh(repository); + return; + } + + // Set the "OUTDATED" description + const description = localize('outdated', "OUTDATED"); + this._setRepositoryDescription(this._isRepositoryNodeVisible() ? repository : undefined, description); + })); + + this._repositories.set(repository, repositoryDisposables); + } + + // Removed repositories + for (const repository of removed) { + this._treeDataSource.clearState(repository); + this._repositoryDescription.delete(repository); + this._repositoryLoadMore.delete(repository); + this._repositories.deleteAndDispose(repository); + } + + this._updateChildren(); + } + + private _getSelectedRepositories(): ISCMRepository[] { + const focusedRepositories = this._tree.getFocus().filter(r => !!r && isSCMRepository(r))! as ISCMRepository[]; + const selectedRepositories = this._tree.getSelection().filter(r => !!r && isSCMRepository(r))! as ISCMRepository[]; + + return Array.from(new Set([...focusedRepositories, ...selectedRepositories])); + } + + private _getSelectedHistoryItems(): SCMHistoryItemViewModelTreeElement[] { + return this._tree.getSelection() + .filter(r => !!r && isSCMHistoryItemViewModelTreeElement(r))!; + } + + private _getLoadMore(repository: ISCMRepository): ISettableObservable { + let loadMore = this._repositoryLoadMore.get(repository); + if (!loadMore) { + loadMore = observableValue(this, false); + this._repositoryLoadMore.set(repository, loadMore); + } + + return loadMore; + } + + private async _loadMoreCallback(repository: ISCMRepository): Promise { + const loadMore = this._getLoadMore(repository); + if (loadMore.get()) { + return; + } + + loadMore.set(true, undefined); + this._treeDataSource.loadMore(repository); + + await this._updateChildren(repository); + loadMore.set(false, undefined); + } + + private _getRepositoryDescription(repository: ISCMRepository): ISettableObservable { + let description = this._repositoryDescription.get(repository); + if (!description) { + description = observableValue(this, ''); + this._repositoryDescription.set(repository, description); + } + + return description; + } + + private _setRepositoryDescription(repository: ISCMRepository | undefined, description: string): void { + if (!repository) { + this.updateTitleDescription(description); + } else { + this._getRepositoryDescription(repository).set(description, undefined); + } + } + + private _isRepositoryNodeVisible(): boolean { + const repositoryCount = this._scmViewService.visibleRepositories.length; + const alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories') === true; + + return alwaysShowRepositories || repositoryCount > 1; + } + + private _updateChildren(element?: ISCMRepository): Promise { + return this._updateChildrenThrottler.queue( + () => this._treeOperationSequencer.queue( + async () => { + await this._progressService.withProgress({ location: this.id }, + async () => { + if (element && this._tree.hasNode(element)) { + // Refresh specific repository + await this._tree.updateChildren(element, undefined, undefined, { + // diffIdentityProvider: this._treeIdentityProvider + }); + } else { + // Refresh the entire tree + await this._tree.updateChildren(undefined, undefined, undefined, { + // diffIdentityProvider: this._treeIdentityProvider + }); + } + }); + })); + } + + override dispose(): void { + this._visibilityDisposables.dispose(); + this._repositories.dispose(); + super.dispose(); + } +} diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 890e39b4..99e9f61f 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -18,7 +18,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RepositoryActionRunner, RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; @@ -82,9 +81,7 @@ export class SCMRepositoriesViewPane extends ViewPane { this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, listContainer, delegate, [renderer], { identityProvider, horizontalScrolling: false, - overrideStyles: { - listBackground: SIDE_BAR_BACKGROUND - }, + overrideStyles: this.getLocationBasedColors().listOverrideStyles, accessibilityProvider: { getAriaLabel(r: ISCMRepository) { return r.provider.label; diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index e33a86c8..2c4970da 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/scm'; -import { IDisposable, DisposableStore, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; +import { autorun } from 'vs/base/common/observable'; import { append, $ } from 'vs/base/browser/dom'; import { ISCMProvider, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ActionRunner, IAction } from 'vs/base/common/actions'; -import { connectPrimaryMenu, isSCMRepository, StatusBarAction } from './util'; +import { connectPrimaryMenu, getRepositoryResourceCount, isSCMRepository, StatusBarAction } from './util'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { FuzzyScore } from 'vs/base/common/filters'; @@ -23,6 +24,9 @@ import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IManagedHover } from 'vs/base/browser/ui/hover/hover'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export class RepositoryActionRunner extends ActionRunner { constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) { @@ -43,6 +47,7 @@ export class RepositoryActionRunner extends ActionRunner { interface RepositoryTemplate { readonly label: HTMLElement; + readonly labelCustomHover: IManagedHover; readonly name: HTMLElement; readonly description: HTMLElement; readonly countContainer: HTMLElement; @@ -60,12 +65,13 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer provider.classList.toggle('active', e)); - const templateDisposable = combinedDisposable(visibilityDisposable, toolBar); + const templateDisposable = combinedDisposable(labelCustomHover, visibilityDisposable, toolBar); - return { label, name, description, countContainer, count, toolBar, elementDisposables: new DisposableStore(), templateDisposable }; + return { label, labelCustomHover, name, description, countContainer, count, toolBar, elementDisposables: new DisposableStore(), templateDisposable }; } renderElement(arg: ISCMRepository | ITreeNode, index: number, templateData: RepositoryTemplate, height: number | undefined): void { @@ -95,10 +102,10 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer { - const commands = repository.provider.statusBarCommands || []; + templateData.elementDisposables.add(autorun(reader => { + const commands = repository.provider.statusBarCommands.read(reader) ?? []; statusPrimaryActions = commands.map(c => new StatusBarAction(c, this.commandService)); updateToolbar(); + })); - const count = repository.provider.count || 0; + templateData.elementDisposables.add(autorun(reader => { + const count = repository.provider.count.read(reader) ?? getRepositoryResourceCount(repository.provider); templateData.countContainer.setAttribute('data-count', String(count)); templateData.count.setCount(count); - }; - - // TODO@joao TODO@lszomoru - let disposed = false; - templateData.elementDisposables.add(toDisposable(() => disposed = true)); - templateData.elementDisposables.add(repository.provider.onDidChange(() => { - if (disposed) { - return; - } - - onDidChangeProvider(); })); - onDidChangeProvider(); - const repositoryMenus = this.scmViewService.menus.getRepositoryMenus(repository.provider); const menu = this.toolbarMenuId === MenuId.SCMTitle ? repositoryMenus.titleMenu.menu : repositoryMenus.repositoryMenu; templateData.elementDisposables.add(connectPrimaryMenu(menu, (primary, secondary) => { @@ -139,6 +135,7 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer; // type SCMHistoryItemChangeResourceTreeNode = IResourceNode; @@ -121,39 +128,18 @@ type TreeElement = IResourceNode | SCMHistoryItemGroupTreeElement | SCMHistoryItemTreeElement | + SCMHistoryItemViewModelTreeElement | SCMHistoryItemChangeTreeElement | IResourceNode | SCMViewSeparatorElement; -type ShowChangesSetting = 'always' | 'never' | 'auto'; - -registerColor('scm.historyItemAdditionsForeground', { - dark: 'gitDecoration.addedResourceForeground', - light: 'gitDecoration.addedResourceForeground', - hcDark: 'gitDecoration.addedResourceForeground', - hcLight: 'gitDecoration.addedResourceForeground' -}, localize('scm.historyItemAdditionsForeground', "History item additions foreground color.")); - -registerColor('scm.historyItemDeletionsForeground', { - dark: 'gitDecoration.deletedResourceForeground', - light: 'gitDecoration.deletedResourceForeground', - hcDark: 'gitDecoration.deletedResourceForeground', - hcLight: 'gitDecoration.deletedResourceForeground' -}, localize('scm.historyItemDeletionsForeground', "History item deletions foreground color.")); - -registerColor('scm.historyItemStatisticsBorder', { - dark: transparent(foreground, 0.2), - light: transparent(foreground, 0.2), - hcDark: transparent(foreground, 0.2), - hcLight: transparent(foreground, 0.2) -}, localize('scm.historyItemStatisticsBorder', "History item statistics border color.")); - -registerColor('scm.historyItemSelectedStatisticsBorder', { - dark: transparent(listActiveSelectionForeground, 0.2), - light: transparent(listActiveSelectionForeground, 0.2), - hcDark: transparent(listActiveSelectionForeground, 0.2), - hcLight: transparent(listActiveSelectionForeground, 0.2) -}, localize('scm.historyItemSelectedStatisticsBorder', "History item selected statistics border color.")); +const historyItemAdditionsForeground = registerColor('scm.historyItemAdditionsForeground', 'gitDecoration.addedResourceForeground', localize('scm.historyItemAdditionsForeground', "History item additions foreground color.")); + +const historyItemDeletionsForeground = registerColor('scm.historyItemDeletionsForeground', 'gitDecoration.deletedResourceForeground', localize('scm.historyItemDeletionsForeground', "History item deletions foreground color.")); + +registerColor('scm.historyItemStatisticsBorder', transparent(foreground, 0.2), localize('scm.historyItemStatisticsBorder', "History item statistics border color.")); + +registerColor('scm.historyItemSelectedStatisticsBorder', transparent(listActiveSelectionForeground, 0.2), localize('scm.historyItemSelectedStatisticsBorder', "History item selected statistics border color.")); function processResourceFilterData(uri: URI, filterData: FuzzyScore | LabelFuzzyScore | undefined): [IMatch[] | undefined, IMatch[] | undefined] { if (!filterData) { @@ -843,6 +829,7 @@ class HistoryItemActionRunner extends ActionRunner { const args: (ISCMProvider | ISCMHistoryItem)[] = []; args.push(context.historyItemGroup.repository.provider); + args.push({ id: context.id, parentIds: context.parentIds, @@ -857,11 +844,78 @@ class HistoryItemActionRunner extends ActionRunner { } } +class HistoryItemActionRunner2 extends ActionRunner { + constructor(private readonly getSelectedHistoryItems: () => SCMHistoryItemViewModelTreeElement[]) { + super(); + } + + protected override async runAction(action: IAction, context: SCMHistoryItemViewModelTreeElement): Promise { + if (!(action instanceof MenuItemAction)) { + return super.runAction(action, context); + } + + const args: (ISCMProvider | ISCMHistoryItem)[] = []; + args.push(context.repository.provider); + + const selection = this.getSelectedHistoryItems(); + const contextIsSelected = selection.some(s => s === context); + if (contextIsSelected && selection.length > 1) { + args.push(...selection.map(h => ( + { + id: h.historyItemViewModel.historyItem.id, + parentIds: h.historyItemViewModel.historyItem.parentIds, + message: h.historyItemViewModel.historyItem.message, + author: h.historyItemViewModel.historyItem.author, + icon: h.historyItemViewModel.historyItem.icon, + timestamp: h.historyItemViewModel.historyItem.timestamp, + statistics: h.historyItemViewModel.historyItem.statistics, + } satisfies ISCMHistoryItem))); + } else { + args.push({ + id: context.historyItemViewModel.historyItem.id, + parentIds: context.historyItemViewModel.historyItem.parentIds, + message: context.historyItemViewModel.historyItem.message, + author: context.historyItemViewModel.historyItem.author, + icon: context.historyItemViewModel.historyItem.icon, + timestamp: context.historyItemViewModel.historyItem.timestamp, + statistics: context.historyItemViewModel.historyItem.statistics, + } satisfies ISCMHistoryItem); + } + + await action.run(...args); + } +} + +class HistoryItemHoverDelegate extends WorkbenchHoverDelegate { + constructor( + private readonly viewContainerLocation: ViewContainerLocation | null, + private readonly sideBarPosition: Position, + @IConfigurationService configurationService: IConfigurationService, + @IHoverService hoverService: IHoverService + + ) { + super('element', true, () => this.getHoverOptions(), configurationService, hoverService); + } + + private getHoverOptions(): Partial { + let hoverPosition: HoverPosition; + if (this.viewContainerLocation === ViewContainerLocation.Sidebar) { + hoverPosition = this.sideBarPosition === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT; + } else if (this.viewContainerLocation === ViewContainerLocation.AuxiliaryBar) { + hoverPosition = this.sideBarPosition === Position.LEFT ? HoverPosition.LEFT : HoverPosition.RIGHT; + } else { + hoverPosition = HoverPosition.RIGHT; + } + + return { additionalClasses: ['history-item-hover'], position: { hoverPosition, forcePosition: true } }; + } +} + interface HistoryItemTemplate { readonly iconContainer: HTMLElement; readonly label: IconLabel; readonly statsContainer: HTMLElement; - readonly statsCustomHover: IUpdatableHover; + readonly statsCustomHover: IManagedHover; readonly filesLabel: HTMLElement; readonly insertionsLabel: HTMLElement; readonly deletionsLabel: HTMLElement; @@ -901,7 +955,7 @@ class HistoryItemRenderer implements ICompressibleTreeRenderer { + + static readonly TEMPLATE_ID = 'history-item-2'; + get templateId(): string { return HistoryItem2Renderer.TEMPLATE_ID; } + + constructor( + private readonly hoverDelegate: IHoverDelegate, + @IHoverService private readonly hoverService: IHoverService, + @IThemeService private readonly themeService: IThemeService + ) { } + + renderTemplate(container: HTMLElement): HistoryItem2Template { + // hack + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-no-twistie'); + + const element = append(container, $('.history-item')); + const graphContainer = append(element, $('.graph-container')); + const iconLabel = new IconLabel(element, { supportIcons: true, supportHighlights: true, supportDescriptionHighlights: true }); + + const labelContainer = append(element, $('.label-container')); + element.appendChild(labelContainer); + + return { element, graphContainer, label: iconLabel, labelContainer, elementDisposables: new DisposableStore(), disposables: new DisposableStore() }; + } + + renderElement(node: ITreeNode, index: number, templateData: HistoryItem2Template, height: number | undefined): void { + const historyItemViewModel = node.element.historyItemViewModel; + const historyItem = historyItemViewModel.historyItem; + + const historyItemHover = this.hoverService.setupManagedHover(this.hoverDelegate, templateData.element, this.getTooltip(node.element)); + templateData.elementDisposables.add(historyItemHover); + + templateData.graphContainer.textContent = ''; + templateData.graphContainer.appendChild(renderSCMHistoryItemGraph(historyItemViewModel)); + + const [matches, descriptionMatches] = this.processMatches(historyItemViewModel, node.filterData); + templateData.label.setLabel(historyItem.message, historyItem.author, { matches, descriptionMatches }); + + templateData.labelContainer.textContent = ''; + if (historyItem.labels) { + const instantHoverDelegate = createInstantHoverDelegate(); + templateData.elementDisposables.add(instantHoverDelegate); + + for (const label of historyItem.labels) { + if (label.icon && ThemeIcon.isThemeIcon(label.icon)) { + const icon = append(templateData.labelContainer, $('div.label')); + icon.classList.add(...ThemeIcon.asClassNameArray(label.icon)); + + const hover = this.hoverService.setupManagedHover(instantHoverDelegate, icon, label.title); + templateData.elementDisposables.add(hover); + } + } + } + } + + renderCompressedElements(node: ITreeNode, LabelFuzzyScore>, index: number, templateData: HistoryItem2Template, height: number | undefined): void { + throw new Error('Should never happen since node is incompressible'); + } + + private getTooltip(element: SCMHistoryItemViewModelTreeElement): IManagedHoverTooltipMarkdownString { + const colorTheme = this.themeService.getColorTheme(); + const historyItem = element.historyItemViewModel.historyItem; + const currentHistoryItemGroup = element.repository.provider.historyProvider.get()?.currentHistoryItemGroup?.get(); + + const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); + + if (historyItem.author) { + markdown.appendMarkdown(`$(account) **${historyItem.author}**`); + + if (historyItem.timestamp) { + const dateFormatter = new Intl.DateTimeFormat(platform.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); + markdown.appendMarkdown(`, $(history) ${fromNow(historyItem.timestamp, true, true)} (${dateFormatter.format(historyItem.timestamp)})`); + } + + markdown.appendMarkdown('\n\n'); + } + + markdown.appendMarkdown(`${historyItem.message}\n\n`); + + if (historyItem.statistics) { + markdown.appendMarkdown(`---\n\n`); + + markdown.appendMarkdown(`${historyItem.statistics.files === 1 ? + localize('fileChanged', "{0} file changed", historyItem.statistics.files) : + localize('filesChanged', "{0} files changed", historyItem.statistics.files)}`); + + if (historyItem.statistics.insertions) { + const historyItemAdditionsForegroundColor = colorTheme.getColor(historyItemAdditionsForeground); + markdown.appendMarkdown(`, ${historyItem.statistics.insertions === 1 ? + localize('insertion', "{0} insertion{1}", historyItem.statistics.insertions, '(+)') : + localize('insertions', "{0} insertions{1}", historyItem.statistics.insertions, '(+)')}`); + } + + if (historyItem.statistics.deletions) { + const historyItemDeletionsForegroundColor = colorTheme.getColor(historyItemDeletionsForeground); + markdown.appendMarkdown(`, ${historyItem.statistics.deletions === 1 ? + localize('deletion', "{0} deletion{1}", historyItem.statistics.deletions, '(-)') : + localize('deletions', "{0} deletions{1}", historyItem.statistics.deletions, '(-)')}`); + } + } + + if (historyItem.labels) { + const historyItemGroupLocalColor = colorTheme.getColor(historyItemGroupLocal); + const historyItemGroupRemoteColor = colorTheme.getColor(historyItemGroupRemote); + const historyItemGroupBaseColor = colorTheme.getColor(historyItemGroupBase); + + const historyItemGroupHoverLabelForegroundColor = colorTheme.getColor(historyItemGroupHoverLabelForeground); + + markdown.appendMarkdown(`\n\n---\n\n`); + markdown.appendMarkdown(historyItem.labels.map(label => { + const historyItemGroupHoverLabelBackgroundColor = + label.title === currentHistoryItemGroup?.name ? historyItemGroupLocalColor : + label.title === currentHistoryItemGroup?.remote?.name ? historyItemGroupRemoteColor : + label.title === currentHistoryItemGroup?.base?.name ? historyItemGroupBaseColor : + undefined; + + const historyItemGroupHoverLabelIconId = ThemeIcon.isThemeIcon(label.icon) ? label.icon.id : ''; + + return ` $(${historyItemGroupHoverLabelIconId}) ${label.title} `; + }).join('  ')); + } + + return { markdown, markdownNotSupportedFallback: historyItem.message }; + } + + private processMatches(historyItemViewModel: ISCMHistoryItemViewModel, filterData: LabelFuzzyScore | undefined): [IMatch[] | undefined, IMatch[] | undefined] { + if (!filterData) { + return [undefined, undefined]; + } + + return [ + historyItemViewModel.historyItem.message === filterData.label ? createMatches(filterData.score) : undefined, + historyItemViewModel.historyItem.author === filterData.label ? createMatches(filterData.score) : undefined + ]; + } + + disposeElement(element: ITreeNode, index: number, templateData: HistoryItem2Template, height: number | undefined): void { + templateData.elementDisposables.clear(); + } + + disposeTemplate(templateData: HistoryItem2Template): void { + templateData.disposables.dispose(); + } +} + interface HistoryItemChangeTemplate { readonly element: HTMLElement; readonly name: HTMLElement; @@ -1071,7 +1279,9 @@ class HistoryItemChangeRenderer implements ICompressibleTreeRenderer { @@ -1080,6 +1290,7 @@ class SeparatorRenderer implements ICompressibleTreeRenderer IAction[], @IContextKeyService private readonly contextKeyService: IContextKeyService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IKeybindingService private readonly keybindingService: IKeybindingService, @@ -1095,29 +1306,49 @@ class SeparatorRenderer implements ICompressibleTreeRenderer, index: number, templateData: SeparatorTemplate, height: number | undefined): void { + const provider = element.element.repository.provider; + const historyProvider = provider.historyProvider.get(); + const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup.get(); + + // Label templateData.label.setLabel(element.element.label, undefined, { title: element.element.ariaLabel }); + + // Toolbar + const contextKeyService = this.contextKeyService.createOverlay([ + ['scmHistoryItemGroupHasRemote', !!currentHistoryItemGroup?.remote], + ]); + const menu = this.menuService.createMenu(MenuId.SCMChangesSeparator, contextKeyService); + templateData.elementDisposables.add(connectPrimaryMenu(menu, (primary, secondary) => { + secondary.splice(0, 0, ...this.getFilterActions(element.element.repository), new Separator()); + templateData.toolBar.setActions(primary, secondary, [MenuId.SCMChangesSeparator]); + })); + templateData.toolBar.context = provider; } renderCompressedElements(node: ITreeNode, void>, index: number, templateData: SeparatorTemplate, height: number | undefined): void { throw new Error('Should never happen since node is incompressible'); } - disposeTemplate(templateData: SeparatorTemplate): void { - templateData.disposables.dispose(); + disposeElement(node: ITreeNode, index: number, templateData: SeparatorTemplate, height: number | undefined): void { + templateData.elementDisposables.clear(); } + disposeTemplate(templateData: SeparatorTemplate): void { + templateData.elementDisposables.dispose(); + templateData.templateDisposables.dispose(); + } } class ListDelegate implements IListVirtualDelegate { @@ -1149,6 +1380,8 @@ class ListDelegate implements IListVirtualDelegate { return HistoryItemGroupRenderer.TEMPLATE_ID; } else if (isSCMHistoryItemTreeElement(element)) { return HistoryItemRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + return HistoryItem2Renderer.TEMPLATE_ID; } else if (isSCMHistoryItemChangeTreeElement(element) || isSCMHistoryItemChangeNode(element)) { return HistoryItemChangeRenderer.TEMPLATE_ID; } else if (isSCMViewSeparator(element)) { @@ -1229,6 +1462,10 @@ export class SCMTreeSorter implements ITreeSorter { return 0; } + if (isSCMHistoryItemViewModelTreeElement(one)) { + return isSCMHistoryItemViewModelTreeElement(other) ? 0 : 1; + } + if (isSCMHistoryItemChangeTreeElement(one) || isSCMHistoryItemChangeNode(one)) { // List if (this.viewMode() === ViewMode.List) { @@ -1313,6 +1550,11 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb // the author. A match in the message takes precedence over // a match in the author. return [element.message, element.author]; + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + // For a history item we want to match both the message and + // the author. A match in the message takes precedence over + // a match in the author. + return [element.historyItemViewModel.historyItem.message, element.historyItemViewModel.historyItem.author]; } else if (isSCMViewSeparator(element)) { return element.label; } else { @@ -1363,6 +1605,10 @@ function getSCMResourceId(element: TreeElement): string { const historyItemGroup = element.historyItemGroup; const provider = historyItemGroup.repository.provider; return `historyItem:${provider.id}/${historyItemGroup.id}/${element.id}/${element.parentIds.join(',')}`; + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + const provider = element.repository.provider; + const historyItem = element.historyItemViewModel.historyItem; + return `historyItem2:${provider.id}/${historyItem.id}/${historyItem.parentIds.join(',')}`; } else if (isSCMHistoryItemChangeTreeElement(element)) { const historyItem = element.historyItem; const historyItemGroup = historyItem.historyItemGroup; @@ -1413,6 +1659,9 @@ export class SCMAccessibilityProvider implements IListAccessibilityProvider) { +registerAction2(class extends Action2 { + constructor() { super({ - ...desc, + id: 'workbench.scm.action.scm.viewChanges', + title: localize('viewChanges', "View Changes"), f1: false, - toggled: ContextKeyExpr.equals(`config.${settingKey}`, settingValue), + menu: [ + { + id: MenuId.SCMChangesContext, + group: '0_view', + when: ContextKeyExpr.equals('config.multiDiffEditor.experimental.enabled', true) + } + ] }); } - override run(accessor: ServicesAccessor): void { - const configurationService = accessor.get(IConfigurationService); - configurationService.updateValue(this.settingKey, this.settingValue); - } -} - -MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { - title: localize('incomingChanges', "Show Incoming Changes"), - submenu: MenuId.SCMIncomingChangesSetting, - group: '1_incoming&outgoing', - order: 1 -}); - -MenuRegistry.appendMenuItem(Menus.ChangesSettings, { - title: localize('incomingChanges', "Show Incoming Changes"), - submenu: MenuId.SCMIncomingChangesSetting, - group: '1_incoming&outgoing', - order: 1 -}); - -registerAction2(class extends SCMChangesSettingAction { - constructor() { - super('scm.showIncomingChanges', 'always', - { - id: 'workbench.scm.action.showIncomingChanges.always', - title: localize('always', "Always"), - menu: { id: MenuId.SCMIncomingChangesSetting }, - }); - } -}); + override async run(accessor: ServicesAccessor, provider: ISCMProvider, ...historyItems: ISCMHistoryItem[]) { + const commandService = accessor.get(ICommandService); -registerAction2(class extends SCMChangesSettingAction { - constructor() { - super('scm.showIncomingChanges', 'auto', - { - id: 'workbench.scm.action.showIncomingChanges.auto', - title: localize('auto', "Auto"), - menu: { - id: MenuId.SCMIncomingChangesSetting, - } - }); - } -}); + if (!provider || historyItems.length === 0) { + return; + } -registerAction2(class extends SCMChangesSettingAction { - constructor() { - super('scm.showIncomingChanges', 'never', - { - id: 'workbench.scm.action.showIncomingChanges.never', - title: localize('never', "Never"), - menu: { - id: MenuId.SCMIncomingChangesSetting, - } - }); - } -}); + const historyItem = historyItems[0]; + const historyItemLast = historyItems[historyItems.length - 1]; + const historyProvider = provider.historyProvider.get(); -MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { - title: localize('outgoingChanges', "Show Outgoing Changes"), - submenu: MenuId.SCMOutgoingChangesSetting, - group: '1_incoming&outgoing', - order: 2 -}); + if (historyItems.length > 1) { + const ancestor = await historyProvider?.resolveHistoryItemGroupCommonAncestor2([historyItem.id, historyItemLast.id]); + if (!ancestor || (ancestor !== historyItem.id && ancestor !== historyItemLast.id)) { + return; + } + } -MenuRegistry.appendMenuItem(Menus.ChangesSettings, { - title: localize('outgoingChanges', "Show Outgoing Changes"), - submenu: MenuId.SCMOutgoingChangesSetting, - group: '1_incoming&outgoing', - order: 2 -}); + const historyItemParentId = historyItemLast.parentIds.length > 0 ? historyItemLast.parentIds[0] : undefined; + const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItem.id, historyItemParentId); -registerAction2(class extends SCMChangesSettingAction { - constructor() { - super('scm.showOutgoingChanges', 'always', - { - id: 'workbench.scm.action.showOutgoingChanges.always', - title: localize('always', "Always"), - menu: { - id: MenuId.SCMOutgoingChangesSetting, + if (!historyItemChanges?.length) { + return; + } - } - }); - } -}); + const title = historyItems.length === 1 ? + `${historyItems[0].id.substring(0, 8)} - ${historyItems[0].message}` : + localize('historyItemChangesEditorTitle', "All Changes ({0} ↔ {1})", historyItemLast.id.substring(0, 8), historyItem.id.substring(0, 8)); -registerAction2(class extends SCMChangesSettingAction { - constructor() { - super('scm.showOutgoingChanges', 'auto', - { - id: 'workbench.scm.action.showOutgoingChanges.auto', - title: localize('auto', "Auto"), - menu: { - id: MenuId.SCMOutgoingChangesSetting, - } - }); - } -}); + const rootUri = provider.rootUri; + const path = rootUri ? rootUri.path : provider.label; + const multiDiffSourceUri = URI.from({ scheme: 'scm-history-item', path: `${path}/${historyItemParentId}..${historyItem.id}` }, true); -registerAction2(class extends SCMChangesSettingAction { - constructor() { - super('scm.showOutgoingChanges', 'never', - { - id: 'workbench.scm.action.showOutgoingChanges.never', - title: localize('never', "Never"), - menu: { - id: MenuId.SCMOutgoingChangesSetting, - } - }); - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'workbench.scm.action.scm.showChangesSummary', - title: localize('showChangesSummary', "Show Changes Summary"), - f1: false, - toggled: ContextKeyExpr.equals('config.scm.showChangesSummary', true), - menu: [ - { id: MenuId.SCMChangesSeparator, order: 3 }, - { id: Menus.ChangesSettings, order: 3 }, - ] - }); - } - - override run(accessor: ServicesAccessor) { - const configurationService = accessor.get(IConfigurationService); - const configValue = configurationService.getValue('scm.showChangesSummary') === true; - configurationService.updateValue('scm.showChangesSummary', !configValue); + commandService.executeCommand('_workbench.openMultiDiffEditor', { title, multiDiffSourceUri, resources: historyItemChanges }); } }); @@ -2028,7 +2178,7 @@ class SCMInputWidgetToolbar extends WorkbenchToolBar { private _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; - private readonly repositoryDisposables = new DisposableStore(); + private readonly _disposables = this._register(new MutableDisposable()); constructor( container: HTMLElement, @@ -2056,7 +2206,7 @@ class SCMInputWidgetToolbar extends WorkbenchToolBar { } public setInput(input: ISCMInput): void { - this.repositoryDisposables.clear(); + this._disposables.value = new DisposableStore(); const contextKeyService = this.contextKeyService.createOverlay([ ['scmProvider', input.repository.provider.contextValue], @@ -2064,7 +2214,7 @@ class SCMInputWidgetToolbar extends WorkbenchToolBar { ['scmProviderHasRootUri', !!input.repository.provider.rootUri] ]); - const menu = this.repositoryDisposables.add(this.menuService.createMenu(MenuId.SCMInputBox, contextKeyService, { emitEventsForSubmenuChanges: true })); + const menu = this._disposables.value.add(this.menuService.createMenu(MenuId.SCMInputBox, contextKeyService, { emitEventsForSubmenuChanges: true })); const isEnabled = (): boolean => { return input.repository.provider.groups.some(g => g.resources.length > 0); @@ -2094,18 +2244,18 @@ class SCMInputWidgetToolbar extends WorkbenchToolBar { this._onDidChange.fire(); }; - this.repositoryDisposables.add(menu.onDidChange(() => updateToolbar())); - this.repositoryDisposables.add(input.repository.provider.onDidChangeResources(() => updateToolbar())); - this.repositoryDisposables.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, SCMInputWidgetStorageKey.LastActionId, this.repositoryDisposables)(() => updateToolbar())); + this._disposables.value.add(menu.onDidChange(() => updateToolbar())); + this._disposables.value.add(input.repository.provider.onDidChangeResources(() => updateToolbar())); + this._disposables.value.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, SCMInputWidgetStorageKey.LastActionId, this._disposables.value)(() => updateToolbar())); this.actionRunner = new SCMInputWidgetActionRunner(input, this.storageService); - this.repositoryDisposables.add(this.actionRunner.onWillRun(e => { + this._disposables.value.add(this.actionRunner.onWillRun(e => { if ((this.actionRunner as SCMInputWidgetActionRunner).runningActions.size === 0) { super.setActions([this._cancelAction], []); this._onDidChange.fire(); } })); - this.repositoryDisposables.add(this.actionRunner.onDidRun(e => { + this._disposables.value.add(this.actionRunner.onDidRun(e => { if ((this.actionRunner as SCMInputWidgetActionRunner).runningActions.size === 0) { updateToolbar(); } @@ -2113,7 +2263,6 @@ class SCMInputWidgetToolbar extends WorkbenchToolBar { updateToolbar(); } - } class SCMInputWidgetEditorOptions { @@ -2237,7 +2386,6 @@ class SCMInputWidget { private element: HTMLElement; private editorContainer: HTMLElement; - private placeholderTextContainer: HTMLElement; private readonly inputEditor: CodeEditorWidget; private readonly inputEditorOptions: SCMInputWidgetEditorOptions; private toolbarContainer: HTMLElement; @@ -2328,14 +2476,11 @@ class SCMInputWidget { this.repositoryDisposables.add(input.onDidChangeValidationMessage((e) => this.setValidation(e, { focus: true, timeout: true }))); this.repositoryDisposables.add(input.onDidChangeValidateInput((e) => triggerValidation())); - // Keep API in sync with model, update placeholder visibility and validate - const updatePlaceholderVisibility = () => this.placeholderTextContainer.classList.toggle('hidden', textModel.getValueLength() > 0); + // Keep API in sync with model and validate this.repositoryDisposables.add(textModel.onDidChangeContent(() => { input.setValue(textModel.getValue(), true); - updatePlaceholderVisibility(); triggerValidation(); })); - updatePlaceholderVisibility(); // Update placeholder text const updatePlaceholderText = () => { @@ -2343,8 +2488,7 @@ class SCMInputWidget { const label = binding ? binding.getLabel() : (platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'); const placeholderText = format(input.placeholder, label); - this.inputEditor.updateOptions({ ariaLabel: placeholderText }); - this.placeholderTextContainer.textContent = placeholderText; + this.inputEditor.updateOptions({ placeholder: placeholderText }); }; this.repositoryDisposables.add(input.onDidChangePlaceholder(updatePlaceholderText)); this.repositoryDisposables.add(this.keybindingService.onDidUpdateKeybindings(updatePlaceholderText)); @@ -2352,24 +2496,21 @@ class SCMInputWidget { // Update input template let commitTemplate = ''; - const updateTemplate = () => { - if (typeof input.repository.provider.commitTemplate === 'undefined' || !input.visible) { + this.repositoryDisposables.add(autorun(reader => { + if (!input.visible) { return; } const oldCommitTemplate = commitTemplate; - commitTemplate = input.repository.provider.commitTemplate; + commitTemplate = input.repository.provider.commitTemplate.read(reader); const value = textModel.getValue(); - if (value && value !== oldCommitTemplate) { return; } textModel.setValue(commitTemplate); - }; - this.repositoryDisposables.add(input.repository.provider.onDidChangeCommitTemplate(updateTemplate, this)); - updateTemplate(); + })); // Update input enablement const updateEnablement = (enabled: boolean) => { @@ -2428,7 +2569,6 @@ 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')); this.contextKeyService = contextKeyService.createScoped(this.element); @@ -2438,33 +2578,33 @@ class SCMInputWidget { this.disposables.add(this.inputEditorOptions.onDidChange(this.onDidChangeEditorOptions, this)); this.disposables.add(this.inputEditorOptions); - const editorConstructionOptions = this.inputEditorOptions.getEditorConstructionOptions(); - this.setPlaceholderFontStyles(editorConstructionOptions.fontFamily!, editorConstructionOptions.fontSize!, editorConstructionOptions.lineHeight!); - const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - isSimpleWidget: true, contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + CodeActionController.ID, ColorDetector.ID, ContextMenuController.ID, - DragAndDropController.ID, CopyPasteController.ID, + DragAndDropController.ID, DropIntoEditorController.ID, + EditorDictation.ID, + FormatOnType.ID, + ContentHoverController.ID, + MarginHoverController.ID, + InlineCompletionsController.ID, LinkDetector.ID, MenuPreventer.ID, MessageController.ID, - HoverController.ID, + PlaceholderTextContribution.ID, SelectionClipboardContributionID, SnippetController2.ID, - SuggestController.ID, - InlineCompletionsController.ID, - CodeActionController.ID, - FormatOnType.ID, - EditorDictation.ID, - ]) + SuggestController.ID + ]), + isSimpleWidget: true }; const services = new ServiceCollection([IContextKeyService, this.contextKeyService]); const instantiationService2 = instantiationService.createChild(services, this.disposables); + const editorConstructionOptions = this.inputEditorOptions.getEditorConstructionOptions(); this.inputEditor = instantiationService2.createInstance(CodeEditorWidget, this.editorContainer, editorConstructionOptions, codeEditorWidgetOptions); this.disposables.add(this.inputEditor); @@ -2554,7 +2694,6 @@ class SCMInputWidget { this.lastLayoutWasTrash = false; this.inputEditor.layout(dimension); - this.placeholderTextContainer.style.width = `${dimension.width}px`; this.renderValidation(); const showInputActionButton = this.configurationService.getValue('scm.showInputActionButton') === true; @@ -2582,10 +2721,7 @@ class SCMInputWidget { } private onDidChangeEditorOptions(): void { - const editorOptions = this.inputEditorOptions.getEditorOptions(); - - this.inputEditor.updateOptions(editorOptions); - this.setPlaceholderFontStyles(editorOptions.fontFamily!, editorOptions.fontSize!, editorOptions.lineHeight!); + this.inputEditor.updateOptions(this.inputEditorOptions.getEditorOptions()); } private renderValidation(): void { @@ -2675,11 +2811,6 @@ class SCMInputWidget { 26 /* 22px action + 4px margin */ : 39 /* 35px action + 4px margin */; } - private setPlaceholderFontStyles(fontFamily: string, fontSize: number, lineHeight: number): void { - this.placeholderTextContainer.style.fontFamily = fontFamily; - this.placeholderTextContainer.style.fontSize = `${fontSize}px`; - this.placeholderTextContainer.style.lineHeight = `${lineHeight}px`; - } clearValidation(): void { this.validationContextView?.close(); @@ -2775,12 +2906,12 @@ export class SCMViewPane extends ViewPane { options: IViewPaneOptions, @ICommandService private readonly commandService: ICommandService, @IEditorService private readonly editorService: IEditorService, - @ILogService private readonly logService: ILogService, @IMenuService private readonly menuService: IMenuService, @ISCMService private readonly scmService: ISCMService, @ISCMViewService private readonly scmViewService: ISCMViewService, @IStorageService private readonly storageService: IStorageService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService keybindingService: IKeybindingService, @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @@ -2894,10 +3025,7 @@ export class SCMViewPane extends ViewPane { e => e.affectsConfiguration('scm.inputMinLineCount') || e.affectsConfiguration('scm.inputMaxLineCount') || - e.affectsConfiguration('scm.showActionButton') || - e.affectsConfiguration('scm.showChangesSummary') || - e.affectsConfiguration('scm.showIncomingChanges') || - e.affectsConfiguration('scm.showOutgoingChanges'), + e.affectsConfiguration('scm.showActionButton'), this.visibilityDisposables) (() => this.updateChildren(), this, this.visibilityDisposables); @@ -2958,9 +3086,14 @@ export class SCMViewPane extends ViewPane { historyItemActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); this.disposables.add(historyItemActionRunner); + const historyItemHoverDelegate = this.instantiationService.createInstance(HistoryItemHoverDelegate, this.viewDescriptorService.getViewLocationById(this.id), this.layoutService.getSideBarPosition()); + this.disposables.add(historyItemHoverDelegate); + const treeDataSource = this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode); this.disposables.add(treeDataSource); + const compressionEnabled = observableConfigValue('scm.compactFolders', true, this.configurationService); + this.tree = this.instantiationService.createInstance( WorkbenchCompressibleAsyncDataTree, 'SCM Tree Repo', @@ -2972,11 +3105,7 @@ export class SCMViewPane extends ViewPane { this.actionButtonRenderer, this.instantiationService.createInstance(RepositoryRenderer, MenuId.SCMTitle, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), - this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), resourceActionRunner), - this.instantiationService.createInstance(HistoryItemGroupRenderer, historyItemGroupActionRunner), - this.instantiationService.createInstance(HistoryItemRenderer, historyItemActionRunner, getActionViewItemProvider(this.instantiationService)), - this.instantiationService.createInstance(HistoryItemChangeRenderer, () => this.viewMode, this.listLabels), - this.instantiationService.createInstance(SeparatorRenderer) + this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), resourceActionRunner) ], treeDataSource, { @@ -2988,9 +3117,8 @@ export class SCMViewPane extends ViewPane { identityProvider: new SCMResourceIdentityProvider(), sorter: new SCMTreeSorter(() => this.viewMode, () => this.viewSortKey), keyboardNavigationLabelProvider: this.instantiationService.createInstance(SCMTreeKeyboardNavigationLabelProvider, () => this.viewMode), - overrideStyles: { - listBackground: this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND - }, + overrideStyles: this.getLocationBasedColors().listOverrideStyles, + compressionEnabled: compressionEnabled.get(), collapseByDefault: (e: unknown) => { // Repository, Resource Group, Resource Folder (Tree), History Item Change Folder (Tree) if (isSCMRepository(e) || isSCMResourceGroup(e) || isSCMResourceNode(e) || isSCMHistoryItemChangeNode(e)) { @@ -3010,6 +3138,12 @@ export class SCMViewPane extends ViewPane { this.tree.onDidScroll(this.inputRenderer.clearValidation, this.inputRenderer, this.disposables); Event.filter(this.tree.onDidChangeCollapseState, e => isSCMRepository(e.node.element?.element), this.disposables)(this.updateRepositoryCollapseAllContextKeys, this, this.disposables); + this.disposables.add(autorun(reader => { + this.tree.updateOptions({ + compressionEnabled: compressionEnabled.read(reader) + }); + })); + append(container, overflowWidgetsDomNode); } @@ -3100,6 +3234,24 @@ export class SCMViewPane extends ViewPane { } else if (isSCMHistoryItemTreeElement(e.element)) { this.scmViewService.focus(e.element.historyItemGroup.repository); return; + } else if (isSCMHistoryItemViewModelTreeElement(e.element)) { + const historyItem = e.element.historyItemViewModel.historyItem; + const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined; + + const historyProvider = e.element.repository.provider.historyProvider.get(); + const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItem.id, historyItemParentId); + if (historyItemChanges) { + const title = `${historyItem.id.substring(0, 8)} - ${historyItem.message}`; + + const rootUri = e.element.repository.provider.rootUri; + const path = rootUri ? rootUri.path : e.element.repository.provider.label; + const multiDiffSourceUri = URI.from({ scheme: 'scm-history-item', path: `${path}/${historyItemParentId}..${historyItem.id}` }, true); + + await this.commandService.executeCommand('_workbench.openMultiDiffEditor', { title, multiDiffSourceUri, resources: historyItemChanges }); + } + + this.scmViewService.focus(e.element.repository); + return; } else if (isSCMHistoryItemChangeTreeElement(e.element)) { if (e.element.originalUri && e.element.modifiedUri) { await this.commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(e.element.uri, e.element.originalUri, e.element.modifiedUri), e); @@ -3169,20 +3321,6 @@ export class SCMViewPane extends ViewPane { repositoryDisposables.add(repository.input.onDidChangeVisibility(() => this.updateChildren(repository))); repositoryDisposables.add(repository.provider.onDidChangeResourceGroups(() => this.updateChildren(repository))); - repositoryDisposables.add(Event.runAndSubscribe(repository.provider.onDidChangeHistoryProvider, () => { - if (!repository.provider.historyProvider) { - this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - no history provider present'); - return; - } - - repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => { - this.updateChildren(repository); - this.logService.debug('SCMViewPane:onDidChangeCurrentHistoryItemGroup - update children'); - })); - - this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - onDidChangeCurrentHistoryItemGroup listener added'); - })); - const resourceGroupDisposables = repositoryDisposables.add(new DisposableMap()); const onDidChangeResourceGroups = () => { @@ -3220,16 +3358,14 @@ export class SCMViewPane extends ViewPane { private onListContextMenu(e: ITreeContextMenuEvent): void { if (!e.element) { - const menu = this.menuService.createMenu(Menus.ViewSort, this.contextKeyService); + const menu = this.menuService.getMenuActions(Menus.ViewSort, this.contextKeyService); const actions: IAction[] = []; - createAndFillInContextMenuActions(menu, undefined, actions); + createAndFillInContextMenuActions(menu, actions); return this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - onHide: () => { - menu.dispose(); - } + onHide: () => { } }); } @@ -3279,6 +3415,13 @@ export class SCMViewPane extends ViewPane { actionRunner = new HistoryItemActionRunner(); actions = collectContextMenuActions(menu); } + } else if (isSCMHistoryItemViewModelTreeElement(element)) { + const menus = this.scmViewService.menus.getRepositoryMenus(element.repository.provider); + const menu = menus.historyProviderMenu?.getHistoryItemMenu2(element); + if (menu) { + actionRunner = new HistoryItemActionRunner2(() => this.getSelectedHistoryItems()); + actions = collectContextMenuActions(menu); + } } actionRunner.onWillRun(() => this.tree.domFocus()); @@ -3303,6 +3446,11 @@ export class SCMViewPane extends ViewPane { .filter(r => !!r && !isSCMResourceGroup(r))! as any; } + private getSelectedHistoryItems(): SCMHistoryItemViewModelTreeElement[] { + return this.tree.getSelection() + .filter(r => !!r && isSCMHistoryItemViewModelTreeElement(r))!; + } + private getViewMode(): ViewMode { let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewMode.List : ViewMode.Tree; const storageMode = this.storageService.get(`scm.viewMode`, StorageScope.WORKSPACE) as ViewMode; @@ -3414,8 +3562,8 @@ export class SCMViewPane extends ViewPane { return; } - this.isAnyRepositoryCollapsibleContextKey.set(this.scmViewService.visibleRepositories.some(r => this.tree.hasElement(r) && this.tree.isCollapsible(r))); - this.areAllRepositoriesCollapsedContextKey.set(this.scmViewService.visibleRepositories.every(r => this.tree.hasElement(r) && (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r)))); + this.isAnyRepositoryCollapsibleContextKey.set(this.scmViewService.visibleRepositories.some(r => this.tree.hasNode(r) && this.tree.isCollapsible(r))); + this.areAllRepositoriesCollapsedContextKey.set(this.scmViewService.visibleRepositories.every(r => this.tree.hasNode(r) && (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r)))); } collapseAllRepositories(): void { @@ -3561,61 +3709,21 @@ export class SCMViewPane extends ViewPane { } } -class SCMTreeDataSource implements IAsyncDataSource { - - private readonly historyProviderCache = new Map(); - private readonly repositoryDisposables = new DisposableMap(); - private readonly disposables = new DisposableStore(); - +class SCMTreeDataSource extends Disposable implements IAsyncDataSource { constructor( private readonly viewMode: () => ViewMode, @IConfigurationService private readonly configurationService: IConfigurationService, - @ILogService private readonly logService: ILogService, - @ISCMViewService private readonly scmViewService: ISCMViewService, - @IUriIdentityService private uriIdentityService: IUriIdentityService, + @ISCMViewService private readonly scmViewService: ISCMViewService ) { - const onDidChangeConfiguration = Event.filter( - this.configurationService.onDidChangeConfiguration, - e => e.affectsConfiguration('scm.showChangesSummary'), - this.disposables); - this.disposables.add(onDidChangeConfiguration(() => this.historyProviderCache.clear())); - - this.scmViewService.onDidChangeVisibleRepositories(this.onDidChangeVisibleRepositories, this, this.disposables); - this.onDidChangeVisibleRepositories({ added: this.scmViewService.visibleRepositories, removed: Iterable.empty() }); - } - - hasChildren(inputOrElement: ISCMViewService | TreeElement): boolean { - if (isSCMViewService(inputOrElement)) { - return this.scmViewService.visibleRepositories.length !== 0; - } else if (isSCMRepository(inputOrElement)) { - return true; - } else if (isSCMInput(inputOrElement)) { - return false; - } else if (isSCMActionButton(inputOrElement)) { - return false; - } else if (isSCMResourceGroup(inputOrElement)) { - return true; - } else if (isSCMResource(inputOrElement)) { - return false; - } else if (ResourceTree.isResourceNode(inputOrElement)) { - return inputOrElement.childrenCount > 0; - } else if (isSCMHistoryItemGroupTreeElement(inputOrElement)) { - return true; - } else if (isSCMHistoryItemTreeElement(inputOrElement)) { - return true; - } else if (isSCMHistoryItemChangeTreeElement(inputOrElement)) { - return false; - } else if (isSCMViewSeparator(inputOrElement)) { - return false; - } else { - throw new Error('hasChildren not implemented.'); - } + super(); } async getChildren(inputOrElement: ISCMViewService | TreeElement): Promise> { - const { alwaysShowRepositories, showActionButton } = this.getConfiguration(); const repositoryCount = this.scmViewService.visibleRepositories.length; + const showActionButton = this.configurationService.getValue('scm.showActionButton') === true; + const alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories') === true; + if (isSCMViewService(inputOrElement) && (repositoryCount > 1 || alwaysShowRepositories)) { return this.scmViewService.visibleRepositories; } else if ((isSCMViewService(inputOrElement) && repositoryCount === 1 && !alwaysShowRepositories) || isSCMRepository(inputOrElement)) { @@ -3645,30 +3753,6 @@ class SCMTreeDataSource implements IAsyncDataSource 0) { - let label = localize('syncSeparatorHeader', "Incoming/Outgoing"); - let ariaLabel = localize('syncSeparatorHeaderAriaLabel', "Incoming and outgoing changes"); - - const incomingHistoryItems = historyItemGroups.find(g => g.direction === 'incoming'); - const outgoingHistoryItems = historyItemGroups.find(g => g.direction === 'outgoing'); - - if (incomingHistoryItems && !outgoingHistoryItems) { - label = localize('syncIncomingSeparatorHeader', "Incoming"); - ariaLabel = localize('syncIncomingSeparatorHeaderAriaLabel', "Incoming changes"); - } else if (!incomingHistoryItems && outgoingHistoryItems) { - label = localize('syncOutgoingSeparatorHeader', "Outgoing"); - ariaLabel = localize('syncOutgoingSeparatorHeaderAriaLabel', "Outgoing changes"); - } - - children.push({ label, ariaLabel, repository: inputOrElement, type: 'separator' } satisfies SCMViewSeparatorElement); - } - - children.push(...historyItemGroups); - return children; } else if (isSCMResourceGroup(inputOrElement)) { if (this.viewMode() === ViewMode.List) { @@ -3691,190 +3775,11 @@ class SCMTreeDataSource implements IAsyncDataSource { - const { showIncomingChanges, showOutgoingChanges } = this.getConfiguration(); - - const scmProvider = element.provider; - const historyProvider = scmProvider.historyProvider; - const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup; - - if (!historyProvider || !currentHistoryItemGroup || (showIncomingChanges === 'never' && showOutgoingChanges === 'never')) { - return []; - } - - const children: SCMHistoryItemGroupTreeElement[] = []; - const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(element); - - let incomingHistoryItemGroup = historyProviderCacheEntry?.incomingHistoryItemGroup; - let outgoingHistoryItemGroup = historyProviderCacheEntry?.outgoingHistoryItemGroup; - - if (!incomingHistoryItemGroup && !outgoingHistoryItemGroup) { - // Common ancestor, ahead, behind - const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base?.id); - if (!ancestor) { - return []; - } - - // Only show "Incoming" node if there is a base branch - incomingHistoryItemGroup = currentHistoryItemGroup.base ? { - id: currentHistoryItemGroup.base.id, - label: currentHistoryItemGroup.base.name, - ariaLabel: localize('incomingChangesAriaLabel', "Incoming changes from {0}", currentHistoryItemGroup.base.name), - icon: Codicon.arrowCircleDown, - direction: 'incoming', - ancestor: ancestor.id, - count: ancestor.behind, - repository: element, - type: 'historyItemGroup' - } : undefined; - - outgoingHistoryItemGroup = { - id: currentHistoryItemGroup.id, - label: currentHistoryItemGroup.name, - ariaLabel: localize('outgoingChangesAriaLabel', "Outgoing changes to {0}", currentHistoryItemGroup.name), - icon: Codicon.arrowCircleUp, - direction: 'outgoing', - ancestor: ancestor.id, - count: ancestor.ahead, - repository: element, - type: 'historyItemGroup' - }; - - this.historyProviderCache.set(element, { - ...historyProviderCacheEntry, - incomingHistoryItemGroup, - outgoingHistoryItemGroup - }); - } - - // Incoming - if (incomingHistoryItemGroup && - (showIncomingChanges === 'always' || - (showIncomingChanges === 'auto' && (incomingHistoryItemGroup.count ?? 0) > 0))) { - children.push(incomingHistoryItemGroup); - } - - // Outgoing - if (outgoingHistoryItemGroup && - (showOutgoingChanges === 'always' || - (showOutgoingChanges === 'auto' && (outgoingHistoryItemGroup.count ?? 0) > 0))) { - children.push(outgoingHistoryItemGroup); - } - - return children; - } - - private async getHistoryItems(element: SCMHistoryItemGroupTreeElement): Promise { - const repository = element.repository; - const historyProvider = repository.provider.historyProvider; - - if (!historyProvider) { - return []; - } - - const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(repository); - const historyItemsMap = historyProviderCacheEntry.historyItems; - let historyItemsElement = historyProviderCacheEntry.historyItems.get(element.id); - - if (!historyItemsElement) { - const historyItems = await historyProvider.provideHistoryItems(element.id, { limit: { id: element.ancestor } }) ?? []; - - // All Changes - const { showChangesSummary } = this.getConfiguration(); - const allChanges = showChangesSummary && historyItems.length >= 2 ? - await historyProvider.provideHistoryItemSummary(historyItems[0].id, element.ancestor) : undefined; - - historyItemsElement = [allChanges, historyItems]; - - this.historyProviderCache.set(repository, { - ...historyProviderCacheEntry, - historyItems: historyItemsMap.set(element.id, historyItemsElement) - }); - } - - const children: SCMHistoryItemTreeElement[] = []; - if (historyItemsElement[0]) { - children.push({ - ...historyItemsElement[0], - icon: historyItemsElement[0].icon ?? Codicon.files, - message: localize('allChanges', "All Changes"), - historyItemGroup: element, - type: 'allChanges' - } satisfies SCMHistoryItemTreeElement); - } - - children.push(...historyItemsElement[1] - .map(historyItem => ({ - ...historyItem, - historyItemGroup: element, - type: 'historyItem' - } satisfies SCMHistoryItemTreeElement))); - - return children; - } - - private async getHistoryItemChanges(element: SCMHistoryItemTreeElement): Promise<(SCMHistoryItemChangeTreeElement | IResourceNode)[]> { - const repository = element.historyItemGroup.repository; - const historyProvider = repository.provider.historyProvider; - - if (!historyProvider) { - return []; - } - - const historyProviderCacheEntry = this.getHistoryProviderCacheEntry(repository); - const historyItemChangesMap = historyProviderCacheEntry.historyItemChanges; - - const historyItemParentId = element.parentIds.length > 0 ? element.parentIds[0] : undefined; - let historyItemChanges = historyItemChangesMap.get(`${element.id}/${historyItemParentId}`); - - if (!historyItemChanges) { - const historyItemParentId = element.parentIds.length > 0 ? element.parentIds[0] : undefined; - historyItemChanges = await historyProvider.provideHistoryItemChanges(element.id, historyItemParentId) ?? []; - - this.historyProviderCache.set(repository, { - ...historyProviderCacheEntry, - historyItemChanges: historyItemChangesMap.set(`${element.id}/${historyItemParentId}`, historyItemChanges) - }); - } - - if (this.viewMode() === ViewMode.List) { - // List - return historyItemChanges.map(change => ({ - ...change, - historyItem: element, - type: 'historyItemChange' - })); - } - - // Tree - const tree = new ResourceTree(element, repository.provider.rootUri ?? URI.file('/'), this.uriIdentityService.extUri); - for (const change of historyItemChanges) { - tree.add(change.uri, { - ...change, - historyItem: element, - type: 'historyItemChange' - }); - } - - const children: (SCMHistoryItemChangeTreeElement | IResourceNode)[] = []; - for (const node of tree.root.children) { - children.push(node.element ?? node); - } - - return children; - } - getParent(element: TreeElement): ISCMViewService | TreeElement { if (isSCMResourceNode(element)) { if (element.parent === element.context.resourceTree.root) { @@ -3915,64 +3820,35 @@ class SCMTreeDataSource implements IAsyncDataSource('scm.alwaysShowRepositories'), - showActionButton: this.configurationService.getValue('scm.showActionButton'), - showChangesSummary: this.configurationService.getValue('scm.showChangesSummary'), - showIncomingChanges: this.configurationService.getValue('scm.showIncomingChanges'), - showOutgoingChanges: this.configurationService.getValue('scm.showOutgoingChanges') - }; - } - - private onDidChangeVisibleRepositories({ added, removed }: ISCMViewVisibleRepositoryChangeEvent): void { - // Added repositories - for (const repository of added) { - const repositoryDisposables = new DisposableStore(); - - repositoryDisposables.add(Event.runAndSubscribe(repository.provider.onDidChangeHistoryProvider, () => { - if (!repository.provider.historyProvider) { - this.logService.debug('SCMTreeDataSource:onDidChangeVisibleRepositories - no history provider present'); - return; - } - - repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => { - this.historyProviderCache.delete(repository); - this.logService.debug('SCMTreeDataSource:onDidChangeCurrentHistoryItemGroup - cache cleared'); - })); - - this.logService.debug('SCMTreeDataSource:onDidChangeVisibleRepositories - onDidChangeCurrentHistoryItemGroup listener added'); - })); - - this.repositoryDisposables.set(repository, repositoryDisposables); - } - - // Removed repositories - for (const repository of removed) { - this.repositoryDisposables.deleteAndDispose(repository); - this.historyProviderCache.delete(repository); + hasChildren(inputOrElement: ISCMViewService | TreeElement): boolean { + if (isSCMViewService(inputOrElement)) { + return this.scmViewService.visibleRepositories.length !== 0; + } else if (isSCMRepository(inputOrElement)) { + return true; + } else if (isSCMInput(inputOrElement)) { + return false; + } else if (isSCMActionButton(inputOrElement)) { + return false; + } else if (isSCMResourceGroup(inputOrElement)) { + return true; + } else if (isSCMResource(inputOrElement)) { + return false; + } else if (ResourceTree.isResourceNode(inputOrElement)) { + return inputOrElement.childrenCount > 0; + } else if (isSCMHistoryItemGroupTreeElement(inputOrElement)) { + return true; + } else if (isSCMHistoryItemTreeElement(inputOrElement)) { + return true; + } else if (isSCMHistoryItemViewModelTreeElement(inputOrElement)) { + return false; + } else if (isSCMHistoryItemChangeTreeElement(inputOrElement)) { + return false; + } else if (isSCMViewSeparator(inputOrElement)) { + return false; + } else { + throw new Error('hasChildren not implemented.'); } } - - private getHistoryProviderCacheEntry(repository: ISCMRepository): ISCMHistoryProviderCacheEntry { - return this.historyProviderCache.get(repository) ?? { - incomingHistoryItemGroup: undefined, - outgoingHistoryItemGroup: undefined, - historyItems: new Map(), - historyItemChanges: new Map() - }; - } - - dispose(): void { - this.repositoryDisposables.dispose(); - this.disposables.dispose(); - } } export class SCMActionButton implements IDisposable { @@ -4054,3 +3930,5 @@ export class SCMActionButton implements IDisposable { } } } + +setupSimpleEditorSelectionStyling('.scm-view .scm-editor-container'); diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts index c603d810..1128546b 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts +++ b/patched-vscode/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 { ISCMViewService, VIEWLET_ID } from 'vs/workbench/contrib/scm/common/scm'; +import { 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,7 +21,6 @@ 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, @@ -49,8 +48,4 @@ export class SCMViewPaneContainer extends ViewPaneContainer { return localize('source control', "Source Control"); } - override getActionsContext(): unknown { - return this.scmViewService.visibleRepositories.length === 1 ? this.scmViewService.visibleRepositories[0].provider : undefined; - } - } diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/util.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/util.ts index 00c33388..6d036fa7 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/util.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/util.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'vs/base/common/path'; -import { SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history'; +import { SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemTreeElement, SCMHistoryItemViewModelTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history'; import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput, ISCMActionButton, ISCMViewService, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -62,6 +62,14 @@ export function isSCMHistoryItemTreeElement(element: any): element is SCMHistory (element as SCMHistoryItemTreeElement).type === 'historyItem'; } +export function isSCMHistoryItemViewModelTreeElement(element: any): element is SCMHistoryItemViewModelTreeElement { + return (element as SCMHistoryItemViewModelTreeElement).type === 'historyItem2'; +} + +export function isSCMHistoryItemLoadMoreTreeElement(element: any): element is SCMHistoryItemLoadMoreTreeElement { + return (element as SCMHistoryItemLoadMoreTreeElement).type === 'historyItemLoadMore'; +} + export function isSCMHistoryItemChangeTreeElement(element: any): element is SCMHistoryItemChangeTreeElement { return (element as SCMHistoryItemChangeTreeElement).type === 'historyItemChange'; } @@ -173,3 +181,7 @@ export function getActionViewItemProvider(instaService: IInstantiationService): export function getProviderKey(provider: ISCMProvider): string { return `${provider.contextValue}:${provider.label}${provider.rootUri ? `:${provider.rootUri.toString()}` : ''}`; } + +export function getRepositoryResourceCount(provider: ISCMProvider): number { + return provider.groups.reduce((r, g) => r + g.resources.length, 0); +} diff --git a/patched-vscode/src/vs/workbench/contrib/scm/browser/workingSet.ts b/patched-vscode/src/vs/workbench/contrib/scm/browser/workingSet.ts index 3a023470..3befe2a6 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/browser/workingSet.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/browser/workingSet.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { autorun, autorunWithStore } from 'vs/base/common/observable'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { observableConfigValue } from 'vs/platform/observable/common/platformObservableUtils'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { getProviderKey } from 'vs/workbench/contrib/scm/browser/util'; @@ -24,13 +25,13 @@ interface ISCMRepositoryWorkingSet { readonly editorWorkingSets: Map; } -export class SCMWorkingSetController implements IWorkbenchContribution { +export class SCMWorkingSetController extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.scmWorkingSets'; private _workingSets!: Map; + private _enabledConfig = observableConfigValue('scm.workingSets.enabled', false, this.configurationService); + private readonly _repositoryDisposables = new DisposableMap(); - private readonly _scmServiceDisposables = new DisposableStore(); - private readonly _disposables = new DisposableStore(); constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -39,69 +40,61 @@ export class SCMWorkingSetController implements IWorkbenchContribution { @IStorageService private readonly storageService: IStorageService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { - const onDidChangeConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.workingSets.enabled'), this._disposables); - this._disposables.add(Event.runAndSubscribe(onDidChangeConfiguration, () => this._onDidChangeConfiguration())); - } - - private _onDidChangeConfiguration(): void { - if (!this.configurationService.getValue('scm.workingSets.enabled')) { - this.storageService.remove('scm.workingSets', StorageScope.WORKSPACE); + super(); - this._scmServiceDisposables.clear(); - this._repositoryDisposables.clearAndDisposeAll(); - - return; - } + this._store.add(autorunWithStore((reader, store) => { + if (!this._enabledConfig.read(reader)) { + this.storageService.remove('scm.workingSets', StorageScope.WORKSPACE); + this._repositoryDisposables.clearAndDisposeAll(); + return; + } - this._workingSets = this._loadWorkingSets(); + this._workingSets = this._loadWorkingSets(); - this.scmService.onDidAddRepository(this._onDidAddRepository, this, this._scmServiceDisposables); - this.scmService.onDidRemoveRepository(this._onDidRemoveRepository, this, this._scmServiceDisposables); + this.scmService.onDidAddRepository(this._onDidAddRepository, this, store); + this.scmService.onDidRemoveRepository(this._onDidRemoveRepository, this, store); - for (const repository of this.scmService.repositories) { - this._onDidAddRepository(repository); - } + for (const repository of this.scmService.repositories) { + this._onDidAddRepository(repository); + } + })); } private _onDidAddRepository(repository: ISCMRepository): void { const disposables = new DisposableStore(); - disposables.add(Event.runAndSubscribe(repository.provider.onDidChangeHistoryProvider, () => { - if (!repository.provider.historyProvider) { + disposables.add(autorun(async reader => { + const historyProvider = repository.provider.historyProvider.read(reader); + const currentHistoryItemGroupId = historyProvider?.currentHistoryItemGroupId.read(reader); + + if (!currentHistoryItemGroupId) { return; } - disposables.add(Event.runAndSubscribe(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup, async () => { - if (!repository.provider.historyProvider?.currentHistoryItemGroup?.id) { - return; - } - - const providerKey = getProviderKey(repository.provider); - const currentHistoryItemGroupId = repository.provider.historyProvider.currentHistoryItemGroup.id; - const repositoryWorkingSets = this._workingSets.get(providerKey); + const providerKey = getProviderKey(repository.provider); + const repositoryWorkingSets = this._workingSets.get(providerKey); - if (!repositoryWorkingSets) { - this._workingSets.set(providerKey, { currentHistoryItemGroupId, editorWorkingSets: new Map() }); - return; - } + if (!repositoryWorkingSets) { + this._workingSets.set(providerKey, { currentHistoryItemGroupId, editorWorkingSets: new Map() }); + return; + } - if (repositoryWorkingSets.currentHistoryItemGroupId === currentHistoryItemGroupId) { - return; - } + // Editors for the current working set are automatically restored + if (repositoryWorkingSets.currentHistoryItemGroupId === currentHistoryItemGroupId) { + return; + } - // Save the working set - this._saveWorkingSet(providerKey, currentHistoryItemGroupId, repositoryWorkingSets); + // Save the working set + this._saveWorkingSet(providerKey, currentHistoryItemGroupId, repositoryWorkingSets); - // Restore the working set - await this._restoreWorkingSet(providerKey, currentHistoryItemGroupId); - })); + // Restore the working set + await this._restoreWorkingSet(providerKey, currentHistoryItemGroupId); })); this._repositoryDisposables.set(repository, disposables); } private _onDidRemoveRepository(repository: ISCMRepository): void { - this._workingSets.delete(getProviderKey(repository.provider)); this._repositoryDisposables.deleteAndDispose(repository); } @@ -159,9 +152,8 @@ export class SCMWorkingSetController implements IWorkbenchContribution { } } - dispose(): void { + override dispose(): void { this._repositoryDisposables.dispose(); - this._scmServiceDisposables.dispose(); - this._disposables.dispose(); + super.dispose(); } } diff --git a/patched-vscode/src/vs/workbench/contrib/scm/common/history.ts b/patched-vscode/src/vs/workbench/contrib/scm/common/history.ts index 2cb81eff..ea8911ac 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/common/history.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/common/history.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; +import { IObservable } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IMenu } from 'vs/platform/actions/common/actions'; +import { ColorIdentifier } from 'vs/platform/theme/common/colorUtils'; import { ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; export interface ISCMHistoryProviderMenus { @@ -14,37 +15,38 @@ export interface ISCMHistoryProviderMenus { getHistoryItemGroupContextMenu(historyItemGroup: SCMHistoryItemGroupTreeElement): IMenu; getHistoryItemMenu(historyItem: SCMHistoryItemTreeElement): IMenu; + getHistoryItemMenu2(historyItem: SCMHistoryItemViewModelTreeElement): IMenu; } export interface ISCMHistoryProvider { - - readonly onDidChangeCurrentHistoryItemGroup: Event; - - get currentHistoryItemGroup(): ISCMHistoryItemGroup | undefined; - set currentHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup | undefined); + readonly currentHistoryItemGroupId: IObservable; + readonly currentHistoryItemGroupName: IObservable; + readonly currentHistoryItemGroupRevision: IObservable; + readonly currentHistoryItemGroup: IObservable; + readonly currentHistoryItemGroupRemoteId: IObservable; + readonly currentHistoryItemGroupRemoteRevision: IObservable; provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; + provideHistoryItems2(options: ISCMHistoryOptions): Promise; provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise; resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined>; -} - -export interface ISCMHistoryProviderCacheEntry { - readonly incomingHistoryItemGroup: SCMHistoryItemGroupTreeElement | undefined; - readonly outgoingHistoryItemGroup: SCMHistoryItemGroupTreeElement | undefined; - readonly historyItems: Map; - readonly historyItemChanges: Map; + resolveHistoryItemGroupCommonAncestor2(historyItemGroupIds: string[]): Promise; } export interface ISCMHistoryOptions { readonly cursor?: string; + readonly skip?: number; readonly limit?: number | { id?: string }; + readonly historyItemGroupIds?: readonly string[]; } export interface ISCMHistoryItemGroup { readonly id: string; readonly name: string; - readonly base?: Omit; + readonly revision?: string; + readonly base?: Omit, 'remote'>; + readonly remote?: Omit, 'remote'>; } export interface SCMHistoryItemGroupTreeElement { @@ -66,6 +68,11 @@ export interface ISCMHistoryItemStatistics { readonly deletions: number; } +export interface ISCMHistoryItemLabel { + readonly title: string; + readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; +} + export interface ISCMHistoryItem { readonly id: string; readonly parentIds: string[]; @@ -74,6 +81,30 @@ export interface ISCMHistoryItem { readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; readonly timestamp?: number; readonly statistics?: ISCMHistoryItemStatistics; + readonly labels?: ISCMHistoryItemLabel[]; +} + +export interface ISCMHistoryItemGraphNode { + readonly id: string; + readonly color: ColorIdentifier; +} + +export interface ISCMHistoryItemViewModel { + readonly historyItem: ISCMHistoryItem; + readonly inputSwimlanes: ISCMHistoryItemGraphNode[]; + readonly outputSwimlanes: ISCMHistoryItemGraphNode[]; +} + +export interface SCMHistoryItemViewModelTreeElement { + readonly repository: ISCMRepository; + readonly historyItemViewModel: ISCMHistoryItemViewModel; + readonly type: 'historyItem2'; +} + +export interface SCMHistoryItemLoadMoreTreeElement { + readonly repository: ISCMRepository; + readonly graphColumns: ISCMHistoryItemGraphNode[]; + readonly type: 'historyItemLoadMore'; } export interface SCMHistoryItemTreeElement extends ISCMHistoryItem { diff --git a/patched-vscode/src/vs/workbench/contrib/scm/common/scm.ts b/patched-vscode/src/vs/workbench/contrib/scm/common/scm.ts index 3fe568b7..52f5563b 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/common/scm.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/common/scm.ts @@ -15,10 +15,12 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ResourceTree } from 'vs/base/common/resourceTree'; import { ISCMHistoryProvider, ISCMHistoryProviderMenus } from 'vs/workbench/contrib/scm/common/history'; import { ITextModel } from 'vs/editor/common/model'; +import { IObservable } from 'vs/base/common/observable'; export const VIEWLET_ID = 'workbench.view.scm'; export const VIEW_PANE_ID = 'workbench.scm'; export const REPOSITORIES_VIEW_PANE_ID = 'workbench.scm.repositories'; +export const HISTORY_VIEW_PANE_ID = 'workbench.scm.history'; export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; @@ -72,15 +74,12 @@ export interface ISCMProvider extends IDisposable { readonly rootUri?: URI; readonly inputBoxTextModel: ITextModel; - readonly count?: number; - readonly commitTemplate: string; - readonly historyProvider?: ISCMHistoryProvider; - readonly onDidChangeCommitTemplate: Event; - readonly onDidChangeHistoryProvider: Event; - readonly onDidChangeStatusBarCommands?: Event; + readonly count: IObservable; + readonly commitTemplate: IObservable; + readonly historyProvider: IObservable; readonly acceptInputCommand?: Command; readonly actionButton?: ISCMActionButtonDescriptor; - readonly statusBarCommands?: readonly Command[]; + readonly statusBarCommands: IObservable; readonly onDidChange: Event; getOriginalResource(uri: URI): Promise; @@ -173,7 +172,9 @@ export interface ISCMService { readonly repositoryCount: number; registerSCMProvider(provider: ISCMProvider): ISCMRepository; + getRepository(id: string): ISCMRepository | undefined; + getRepository(resource: URI): ISCMRepository | undefined; } export interface ISCMTitleMenu { diff --git a/patched-vscode/src/vs/workbench/contrib/scm/common/scmService.ts b/patched-vscode/src/vs/workbench/contrib/scm/common/scmService.ts index 762dc9ed..b341a202 100644 --- a/patched-vscode/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/patched-vscode/src/vs/workbench/contrib/scm/common/scmService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator, ISCMInputChangeEvent, SCMInputChangeReason, InputValidationType, IInputValidation } from './scm'; import { ILogService } from 'vs/platform/log/common/log'; @@ -15,8 +15,10 @@ import { ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { Iterable } from 'vs/base/common/iterator'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { Schemas } from 'vs/base/common/network'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -class SCMInput implements ISCMInput { +class SCMInput extends Disposable implements ISCMInput { private _value = ''; @@ -104,9 +106,11 @@ class SCMInput implements ISCMInput { readonly repository: ISCMRepository, private readonly history: SCMInputHistory ) { + super(); + if (this.repository.provider.rootUri) { this.historyNavigator = history.getHistory(this.repository.provider.label, this.repository.provider.rootUri); - this.history.onWillSaveHistory(event => { + this._register(this.history.onWillSaveHistory(event => { if (this.historyNavigator.isAtEnd()) { this.saveValue(); } @@ -116,7 +120,7 @@ class SCMInput implements ISCMInput { } this.didChangeHistory = false; - }); + })); } else { // in memory only this.historyNavigator = new HistoryNavigator2([''], 100); } @@ -130,7 +134,7 @@ class SCMInput implements ISCMInput { } if (!transient) { - this.historyNavigator.add(this._value); + this.historyNavigator.replaceLast(this._value); this.historyNavigator.add(value); this.didChangeHistory = true; } @@ -362,7 +366,8 @@ export class SCMService implements ISCMService { @ILogService private readonly logService: ILogService, @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { this.inputHistory = new SCMInputHistory(storageService, workspaceContextService); this.providerCount = contextKeyService.createKey('scm.providerCount', 0); @@ -389,8 +394,36 @@ export class SCMService implements ISCMService { return repository; } - getRepository(id: string): ISCMRepository | undefined { - return this._repositories.get(id); - } + getRepository(id: string): ISCMRepository | undefined; + getRepository(resource: URI): ISCMRepository | undefined; + getRepository(idOrResource: string | URI): ISCMRepository | undefined { + if (typeof idOrResource === 'string') { + return this._repositories.get(idOrResource); + } + + if (idOrResource.scheme !== Schemas.file && + idOrResource.scheme !== Schemas.vscodeRemote) { + return undefined; + } + + let bestRepository: ISCMRepository | undefined = undefined; + let bestMatchLength = Number.POSITIVE_INFINITY; + + for (const repository of this.repositories) { + const root = repository.provider.rootUri; + if (!root) { + continue; + } + + const path = this.uriIdentityService.extUri.relativePath(root, idOrResource); + + if (path && !/^\.\./.test(path) && path.length < bestMatchLength) { + bestRepository = repository; + bestMatchLength = path.length; + } + } + + return bestRepository; + } } diff --git a/patched-vscode/src/vs/workbench/contrib/scm/test/browser/scmHistory.test.ts b/patched-vscode/src/vs/workbench/contrib/scm/test/browser/scmHistory.test.ts new file mode 100644 index 00000000..bb326f51 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/scm/test/browser/scmHistory.test.ts @@ -0,0 +1,592 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ColorIdentifier } from 'vs/platform/theme/common/colorUtils'; +import { colorRegistry, historyItemGroupBase, historyItemGroupLocal, historyItemGroupRemote, toISCMHistoryItemViewModelArray } from 'vs/workbench/contrib/scm/browser/scmHistory'; +import { ISCMHistoryItem } from 'vs/workbench/contrib/scm/common/history'; + +suite('toISCMHistoryItemViewModelArray', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('empty graph', () => { + const viewModels = toISCMHistoryItemViewModelArray([]); + + assert.strictEqual(viewModels.length, 0); + }); + + + /** + * * a + */ + + test('single commit', () => { + const models = [ + { id: 'a', parentIds: [], message: '' }, + ] as ISCMHistoryItem[]; + + const viewModels = toISCMHistoryItemViewModelArray(models); + + assert.strictEqual(viewModels.length, 1); + + assert.strictEqual(viewModels[0].inputSwimlanes.length, 0); + assert.strictEqual(viewModels[0].outputSwimlanes.length, 0); + }); + + /** + * * a(b) + * * b(c) + * * c(d) + * * d(e) + * * e + */ + test('linear graph', () => { + const models = [ + { id: 'a', parentIds: ['b'] }, + { id: 'b', parentIds: ['c'] }, + { id: 'c', parentIds: ['d'] }, + { id: 'd', parentIds: ['e'] }, + { id: 'e', parentIds: [] }, + ] as ISCMHistoryItem[]; + + const viewModels = toISCMHistoryItemViewModelArray(models); + + assert.strictEqual(viewModels.length, 5); + + // node a + assert.strictEqual(viewModels[0].inputSwimlanes.length, 0); + + assert.strictEqual(viewModels[0].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[0].outputSwimlanes[0].color, colorRegistry[0]); + + // node b + assert.strictEqual(viewModels[1].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].inputSwimlanes[0].color, colorRegistry[0]); + + assert.strictEqual(viewModels[1].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'c'); + assert.strictEqual(viewModels[1].outputSwimlanes[0].color, colorRegistry[0]); + + // node c + assert.strictEqual(viewModels[2].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'c'); + assert.strictEqual(viewModels[2].inputSwimlanes[0].color, colorRegistry[0]); + + assert.strictEqual(viewModels[2].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[2].outputSwimlanes[0].color, colorRegistry[0]); + + // node d + assert.strictEqual(viewModels[3].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[3].inputSwimlanes[0].color, colorRegistry[0]); + + assert.strictEqual(viewModels[3].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[3].outputSwimlanes[0].color, colorRegistry[0]); + + // node e + assert.strictEqual(viewModels[4].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[4].inputSwimlanes[0].color, colorRegistry[0]); + + assert.strictEqual(viewModels[4].outputSwimlanes.length, 0); + }); + + /** + * * a(b) + * * b(c,d) + * |\ + * | * d(c) + * |/ + * * c(e) + * * e(f) + */ + test('merge commit (single commit in topic branch)', () => { + const models = [ + { id: 'a', parentIds: ['b'] }, + { id: 'b', parentIds: ['c', 'd'] }, + { id: 'd', parentIds: ['c'] }, + { id: 'c', parentIds: ['e'] }, + { id: 'e', parentIds: ['f'] }, + ] as ISCMHistoryItem[]; + + const viewModels = toISCMHistoryItemViewModelArray(models); + + assert.strictEqual(viewModels.length, 5); + + // node a + assert.strictEqual(viewModels[0].inputSwimlanes.length, 0); + + assert.strictEqual(viewModels[0].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[0].outputSwimlanes[0].color, colorRegistry[0]); + + // node b + assert.strictEqual(viewModels[1].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].inputSwimlanes[0].color, colorRegistry[0]); + + assert.strictEqual(viewModels[1].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'c'); + assert.strictEqual(viewModels[1].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[1].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[1].outputSwimlanes[1].color, colorRegistry[1]); + + // node d + assert.strictEqual(viewModels[2].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'c'); + assert.strictEqual(viewModels[2].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[2].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[2].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[2].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'c'); + assert.strictEqual(viewModels[2].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[2].outputSwimlanes[1].id, 'c'); + assert.strictEqual(viewModels[2].outputSwimlanes[1].color, colorRegistry[1]); + + // node c + assert.strictEqual(viewModels[3].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'c'); + assert.strictEqual(viewModels[3].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[3].inputSwimlanes[1].id, 'c'); + assert.strictEqual(viewModels[3].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[3].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[3].outputSwimlanes[0].color, colorRegistry[0]); + + // node e + assert.strictEqual(viewModels[4].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[4].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[4].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'f'); + assert.strictEqual(viewModels[4].outputSwimlanes[0].color, colorRegistry[0]); + }); + + /** + * * a(b,c) + * |\ + * | * c(d) + * * | b(e) + * * | e(f) + * * | f(d) + * |/ + * * d(g) + */ + test('merge commit (multiple commits in topic branch)', () => { + const models = [ + { id: 'a', parentIds: ['b', 'c'] }, + { id: 'c', parentIds: ['d'] }, + { id: 'b', parentIds: ['e'] }, + { id: 'e', parentIds: ['f'] }, + { id: 'f', parentIds: ['d'] }, + { id: 'd', parentIds: ['g'] }, + ] as ISCMHistoryItem[]; + + const viewModels = toISCMHistoryItemViewModelArray(models); + + assert.strictEqual(viewModels.length, 6); + + // node a + assert.strictEqual(viewModels[0].inputSwimlanes.length, 0); + + assert.strictEqual(viewModels[0].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[0].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[0].outputSwimlanes[1].id, 'c'); + assert.strictEqual(viewModels[0].outputSwimlanes[1].color, colorRegistry[1]); + + // node c + assert.strictEqual(viewModels[1].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[1].inputSwimlanes[1].id, 'c'); + assert.strictEqual(viewModels[1].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[1].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[1].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[1].outputSwimlanes[1].color, colorRegistry[1]); + + // node b + assert.strictEqual(viewModels[2].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[2].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[2].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[2].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[2].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[2].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[2].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[2].outputSwimlanes[1].color, colorRegistry[1]); + + // node e + assert.strictEqual(viewModels[3].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[3].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[3].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[3].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[3].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'f'); + assert.strictEqual(viewModels[3].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[3].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[3].outputSwimlanes[1].color, colorRegistry[1]); + + // node f + assert.strictEqual(viewModels[4].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'f'); + assert.strictEqual(viewModels[4].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[4].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[4].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[4].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[4].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[4].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[4].outputSwimlanes[1].color, colorRegistry[1]); + + // node d + assert.strictEqual(viewModels[5].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[5].inputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[5].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[5].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[5].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[5].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[5].outputSwimlanes[0].id, 'g'); + assert.strictEqual(viewModels[5].outputSwimlanes[0].color, colorRegistry[0]); + }); + + /** + * * a(b,c) + * |\ + * | * c(b) + * |/ + * * b(d,e) + * |\ + * | * e(f) + * | * f(g) + * * | d(h) + */ + test('create brach from merge commit', () => { + const models = [ + { id: 'a', parentIds: ['b', 'c'] }, + { id: 'c', parentIds: ['b'] }, + { id: 'b', parentIds: ['d', 'e'] }, + { id: 'e', parentIds: ['f'] }, + { id: 'f', parentIds: ['g'] }, + { id: 'd', parentIds: ['h'] }, + ] as ISCMHistoryItem[]; + + const viewModels = toISCMHistoryItemViewModelArray(models); + + assert.strictEqual(viewModels.length, 6); + + // node a + assert.strictEqual(viewModels[0].inputSwimlanes.length, 0); + + assert.strictEqual(viewModels[0].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[0].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[0].outputSwimlanes[1].id, 'c'); + assert.strictEqual(viewModels[0].outputSwimlanes[1].color, colorRegistry[1]); + + // node c + assert.strictEqual(viewModels[1].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[1].inputSwimlanes[1].id, 'c'); + assert.strictEqual(viewModels[1].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[1].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[1].outputSwimlanes[1].id, 'b'); + assert.strictEqual(viewModels[1].outputSwimlanes[1].color, colorRegistry[1]); + + // node b + assert.strictEqual(viewModels[2].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[2].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[2].inputSwimlanes[1].id, 'b'); + assert.strictEqual(viewModels[2].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[2].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[2].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[2].outputSwimlanes[1].id, 'e'); + assert.strictEqual(viewModels[2].outputSwimlanes[1].color, colorRegistry[2]); + + // node e + assert.strictEqual(viewModels[3].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[3].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[3].inputSwimlanes[1].id, 'e'); + assert.strictEqual(viewModels[3].inputSwimlanes[1].color, colorRegistry[2]); + + assert.strictEqual(viewModels[3].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[3].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[3].outputSwimlanes[1].id, 'f'); + assert.strictEqual(viewModels[3].outputSwimlanes[1].color, colorRegistry[2]); + + // node f + assert.strictEqual(viewModels[4].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[4].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[4].inputSwimlanes[1].id, 'f'); + assert.strictEqual(viewModels[4].inputSwimlanes[1].color, colorRegistry[2]); + + assert.strictEqual(viewModels[4].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[4].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[4].outputSwimlanes[1].id, 'g'); + assert.strictEqual(viewModels[4].outputSwimlanes[1].color, colorRegistry[2]); + + // node d + assert.strictEqual(viewModels[5].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[5].inputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[5].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[5].inputSwimlanes[1].id, 'g'); + assert.strictEqual(viewModels[5].inputSwimlanes[1].color, colorRegistry[2]); + + assert.strictEqual(viewModels[5].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[5].outputSwimlanes[0].id, 'h'); + assert.strictEqual(viewModels[5].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[5].outputSwimlanes[1].id, 'g'); + assert.strictEqual(viewModels[5].outputSwimlanes[1].color, colorRegistry[2]); + }); + + + /** + * * a(b,c) + * |\ + * | * c(d) + * * | b(e,f) + * |\| + * | |\ + * | | * f(g) + * * | | e(g) + * | * | d(g) + * |/ / + * | / + * |/ + * * g(h) + */ + test('create multiple branches from a commit', () => { + const models = [ + { id: 'a', parentIds: ['b', 'c'] }, + { id: 'c', parentIds: ['d'] }, + { id: 'b', parentIds: ['e', 'f'] }, + { id: 'f', parentIds: ['g'] }, + { id: 'e', parentIds: ['g'] }, + { id: 'd', parentIds: ['g'] }, + { id: 'g', parentIds: ['h'] }, + ] as ISCMHistoryItem[]; + + const viewModels = toISCMHistoryItemViewModelArray(models); + + assert.strictEqual(viewModels.length, 7); + + // node a + assert.strictEqual(viewModels[0].inputSwimlanes.length, 0); + + assert.strictEqual(viewModels[0].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[0].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[0].outputSwimlanes[1].id, 'c'); + assert.strictEqual(viewModels[0].outputSwimlanes[1].color, colorRegistry[1]); + + // node c + assert.strictEqual(viewModels[1].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[1].inputSwimlanes[1].id, 'c'); + assert.strictEqual(viewModels[1].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[1].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[1].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[1].outputSwimlanes[1].color, colorRegistry[1]); + + // node b + assert.strictEqual(viewModels[2].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[2].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[2].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[2].inputSwimlanes[1].color, colorRegistry[1]); + + assert.strictEqual(viewModels[2].outputSwimlanes.length, 3); + assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[2].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[2].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[2].outputSwimlanes[1].color, colorRegistry[1]); + assert.strictEqual(viewModels[2].outputSwimlanes[2].id, 'f'); + assert.strictEqual(viewModels[2].outputSwimlanes[2].color, colorRegistry[2]); + + // node f + assert.strictEqual(viewModels[3].inputSwimlanes.length, 3); + assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[3].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[3].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[3].inputSwimlanes[1].color, colorRegistry[1]); + assert.strictEqual(viewModels[3].inputSwimlanes[2].id, 'f'); + assert.strictEqual(viewModels[3].inputSwimlanes[2].color, colorRegistry[2]); + + assert.strictEqual(viewModels[3].outputSwimlanes.length, 3); + assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[3].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[3].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[3].outputSwimlanes[1].color, colorRegistry[1]); + assert.strictEqual(viewModels[3].outputSwimlanes[2].id, 'g'); + assert.strictEqual(viewModels[3].outputSwimlanes[2].color, colorRegistry[2]); + + // node e + assert.strictEqual(viewModels[4].inputSwimlanes.length, 3); + assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[4].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[4].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[4].inputSwimlanes[1].color, colorRegistry[1]); + assert.strictEqual(viewModels[4].inputSwimlanes[2].id, 'g'); + assert.strictEqual(viewModels[4].inputSwimlanes[2].color, colorRegistry[2]); + + assert.strictEqual(viewModels[4].outputSwimlanes.length, 3); + assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'g'); + assert.strictEqual(viewModels[4].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[4].outputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[4].outputSwimlanes[1].color, colorRegistry[1]); + assert.strictEqual(viewModels[4].outputSwimlanes[2].id, 'g'); + assert.strictEqual(viewModels[4].outputSwimlanes[2].color, colorRegistry[2]); + + // node d + assert.strictEqual(viewModels[5].inputSwimlanes.length, 3); + assert.strictEqual(viewModels[5].inputSwimlanes[0].id, 'g'); + assert.strictEqual(viewModels[5].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[5].inputSwimlanes[1].id, 'd'); + assert.strictEqual(viewModels[5].inputSwimlanes[1].color, colorRegistry[1]); + assert.strictEqual(viewModels[5].inputSwimlanes[2].id, 'g'); + assert.strictEqual(viewModels[5].inputSwimlanes[2].color, colorRegistry[2]); + + assert.strictEqual(viewModels[5].outputSwimlanes.length, 3); + assert.strictEqual(viewModels[5].outputSwimlanes[0].id, 'g'); + assert.strictEqual(viewModels[5].outputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[5].outputSwimlanes[1].id, 'g'); + assert.strictEqual(viewModels[5].outputSwimlanes[1].color, colorRegistry[1]); + assert.strictEqual(viewModels[5].outputSwimlanes[2].id, 'g'); + assert.strictEqual(viewModels[5].outputSwimlanes[2].color, colorRegistry[2]); + + // node g + assert.strictEqual(viewModels[6].inputSwimlanes.length, 3); + assert.strictEqual(viewModels[6].inputSwimlanes[0].id, 'g'); + assert.strictEqual(viewModels[6].inputSwimlanes[0].color, colorRegistry[0]); + assert.strictEqual(viewModels[6].inputSwimlanes[1].id, 'g'); + assert.strictEqual(viewModels[6].inputSwimlanes[1].color, colorRegistry[1]); + assert.strictEqual(viewModels[6].inputSwimlanes[2].id, 'g'); + assert.strictEqual(viewModels[6].inputSwimlanes[2].color, colorRegistry[2]); + + assert.strictEqual(viewModels[6].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[6].outputSwimlanes[0].id, 'h'); + assert.strictEqual(viewModels[6].outputSwimlanes[0].color, colorRegistry[0]); + }); + + /** + * * a(b) [topic] + * * b(c) + * * c(d) [origin/topic] + * * d(e) + * * e(f,g) + * |\ + * | * g(h) [origin/main] + */ + test('graph with color map', () => { + const models = [ + { id: 'a', parentIds: ['b'], labels: [{ title: 'topic' }] }, + { id: 'b', parentIds: ['c'] }, + { id: 'c', parentIds: ['d'], labels: [{ title: 'origin/topic' }] }, + { id: 'd', parentIds: ['e'] }, + { id: 'e', parentIds: ['f', 'g'] }, + { id: 'g', parentIds: ['h'], labels: [{ title: 'origin/main' }] } + ] as ISCMHistoryItem[]; + + const colorMap = new Map([ + ['topic', historyItemGroupLocal], + ['origin/topic', historyItemGroupRemote], + ['origin/main', historyItemGroupBase], + ]); + + const viewModels = toISCMHistoryItemViewModelArray(models, colorMap); + + assert.strictEqual(viewModels.length, 6); + + // node a + assert.strictEqual(viewModels[0].inputSwimlanes.length, 0); + + assert.strictEqual(viewModels[0].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[0].outputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[0].outputSwimlanes[0].color, historyItemGroupLocal); + + // node b + assert.strictEqual(viewModels[1].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[1].inputSwimlanes[0].id, 'b'); + assert.strictEqual(viewModels[1].inputSwimlanes[0].color, historyItemGroupLocal); + + assert.strictEqual(viewModels[1].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[1].outputSwimlanes[0].id, 'c'); + assert.strictEqual(viewModels[1].outputSwimlanes[0].color, historyItemGroupLocal); + + // node c + assert.strictEqual(viewModels[2].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[2].inputSwimlanes[0].id, 'c'); + assert.strictEqual(viewModels[2].inputSwimlanes[0].color, historyItemGroupLocal); + + assert.strictEqual(viewModels[2].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[2].outputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[2].outputSwimlanes[0].color, historyItemGroupRemote); + + // node d + assert.strictEqual(viewModels[3].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[3].inputSwimlanes[0].id, 'd'); + assert.strictEqual(viewModels[3].inputSwimlanes[0].color, historyItemGroupRemote); + + assert.strictEqual(viewModels[3].outputSwimlanes.length, 1); + assert.strictEqual(viewModels[3].outputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[3].outputSwimlanes[0].color, historyItemGroupRemote); + + // node e + assert.strictEqual(viewModels[4].inputSwimlanes.length, 1); + assert.strictEqual(viewModels[4].inputSwimlanes[0].id, 'e'); + assert.strictEqual(viewModels[4].inputSwimlanes[0].color, historyItemGroupRemote); + + assert.strictEqual(viewModels[4].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[4].outputSwimlanes[0].id, 'f'); + assert.strictEqual(viewModels[4].outputSwimlanes[0].color, historyItemGroupRemote); + assert.strictEqual(viewModels[4].outputSwimlanes[1].id, 'g'); + assert.strictEqual(viewModels[4].outputSwimlanes[1].color, historyItemGroupBase); + + // node g + assert.strictEqual(viewModels[5].inputSwimlanes.length, 2); + assert.strictEqual(viewModels[5].inputSwimlanes[0].id, 'f'); + assert.strictEqual(viewModels[5].inputSwimlanes[0].color, historyItemGroupRemote); + assert.strictEqual(viewModels[5].inputSwimlanes[1].id, 'g'); + assert.strictEqual(viewModels[5].inputSwimlanes[1].color, historyItemGroupBase); + + assert.strictEqual(viewModels[5].outputSwimlanes.length, 2); + assert.strictEqual(viewModels[5].outputSwimlanes[0].id, 'f'); + assert.strictEqual(viewModels[5].outputSwimlanes[0].color, historyItemGroupRemote); + assert.strictEqual(viewModels[5].outputSwimlanes[1].id, 'h'); + assert.strictEqual(viewModels[5].outputSwimlanes[1].color, historyItemGroupBase); + }); +}); diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index c9a116bd..65e79d23 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -87,7 +87,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider | undefined = undefined; + picker: IQuickPick | undefined = undefined; editorViewState = this._register(this.instantiationService.createInstance(PickerEditorState)); @@ -109,7 +109,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider): void { + set(picker: IQuickPick): void { // Picker for this run this.picker = picker; @@ -188,7 +188,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider, token: CancellationToken, runOptions?: AnythingQuickAccessProviderRunOptions): IDisposable { + override provide(picker: IQuickPick, token: CancellationToken, runOptions?: AnythingQuickAccessProviderRunOptions): IDisposable { const disposables = new DisposableStore(); // Update the pick state for this run @@ -319,13 +319,12 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider | Promise> | FastAndSlowPicks { - const configuration = { ...this.configuration, includeSymbols: options.includeSymbols ?? this.configuration.includeSymbols }; const query = prepareQuery(filter); // Return early if we have editor symbol picks. We support this by: // - having a previously active global pick (e.g. a file) // - the user typing `@` to start the local symbol query - if (options.enableEditorSymbolSearch && options.includeSymbols) { + if (options.enableEditorSymbolSearch) { const editorSymbolPicks = this.getEditorSymbolPicks(query, disposables, token); if (editorSymbolPicks) { return editorSymbolPicks; @@ -397,7 +396,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider options.filter?.(p)); } @@ -406,7 +405,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider 0 ? [ - { type: 'separator', label: configuration.includeSymbols ? localize('fileAndSymbolResultsSeparator', "file and symbol results") : localize('fileResultsSeparator', "file results") }, + { type: 'separator', label: this.configuration.includeSymbols ? localize('fileAndSymbolResultsSeparator', "file and symbol results") : localize('fileResultsSeparator', "file results") }, ...additionalPicks ] : []; })(), diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts index b3994629..5265cf2c 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts @@ -13,7 +13,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { INotebookSearchService } from 'vs/workbench/contrib/search/common/notebookSearch'; import { INotebookCellMatchWithModel, INotebookFileMatchWithModel, contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/notebookSearch/searchNotebookHelpers'; -import { ITextQuery, QueryType, ISearchProgressItem, ISearchComplete, ISearchConfigurationProperties, pathIncludedInQuery, ISearchService, IFolderQuery } from 'vs/workbench/services/search/common/search'; +import { ITextQuery, QueryType, ISearchProgressItem, ISearchComplete, ISearchConfigurationProperties, pathIncludedInQuery, ISearchService, IFolderQuery, DEFAULT_MAX_SEARCH_RESULTS } from 'vs/workbench/services/search/common/search'; import * as arrays from 'vs/base/common/arrays'; import { isNumber } from 'vs/base/common/types'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -117,22 +117,21 @@ export class NotebookSearchService implements INotebookSearchService { } private async doesFileExist(includes: string[], folderQueries: IFolderQuery[], token: CancellationToken): Promise { - const promises: Promise[] = includes.map(async includePattern => { + const promises: Promise[] = includes.map(async includePattern => { const query = this.queryBuilder.file(folderQueries.map(e => e.folder), { includePattern: includePattern.startsWith('/') ? includePattern : '**/' + includePattern, // todo: find cleaner way to ensure that globs match all appropriate filetypes - exists: true + exists: true, + onlyFileScheme: true, }); return this.searchService.fileSearch( query, token ).then((ret) => { - if (!ret.limitHit) { - throw Error('File not found'); - } + return !!ret.limitHit; }); }); - return Promise.any(promises).then(() => true).catch(() => false); + return Promise.any(promises); } private async getClosedNotebookResults(textQuery: ITextQuery, scannedFiles: ResourceSet, token: CancellationToken): Promise { @@ -232,7 +231,7 @@ export class NotebookSearchService implements INotebookSearchService { if (!widget.hasModel()) { continue; } - const askMax = isNumber(query.maxResults) ? query.maxResults + 1 : Number.MAX_SAFE_INTEGER; + const askMax = (isNumber(query.maxResults) ? query.maxResults : DEFAULT_MAX_SEARCH_RESULTS) + 1; const uri = widget.viewModel!.uri; if (!pathIncludedInQuery(query, uri.fsPath)) { diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/notebookSearch/searchNotebookHelpers.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/notebookSearch/searchNotebookHelpers.ts index 1211c0c3..d261e861 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/notebookSearch/searchNotebookHelpers.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/notebookSearch/searchNotebookHelpers.ts @@ -52,5 +52,5 @@ export function webviewMatchesToTextSearchMatches(webviewMatches: CellWebviewFin new Range(0, rawMatch.searchPreviewInfo.range.start, 0, rawMatch.searchPreviewInfo.range.end), undefined, rawMatch.index) : undefined - ).filter((e): e is ITextSearchMatch => !!e); + ).filter((e): e is TextSearchMatch => !!e); } diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index aa71685a..6a54da2e 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -17,7 +17,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchCompressibleObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { FastAndSlowPicks, IPickerQuickAccessItem, IPickerQuickAccessSeparator, PickerQuickAccessProvider, Picks, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { DefaultQuickAccessFilterValue, IQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess'; -import { IKeyMods, IQuickPick, IQuickPickItem, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput'; +import { IKeyMods, IQuickPick, IQuickPickItem, QuickInputButtonLocation, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { searchDetailsIcon, searchOpenInFileIcon, searchActivityBarIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; @@ -32,6 +32,7 @@ import { PickerEditorState } from 'vs/workbench/browser/quickaccess'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { Sequencer } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; +import { Codicon } from 'vs/base/common/codicons'; export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '%'; @@ -99,15 +100,18 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { + override provide(picker: IQuickPick, 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]; } - picker.customButton = true; - picker.customLabel = '$(go-to-search)'; + picker.buttons = [{ + location: QuickInputButtonLocation.Inline, + iconClass: ThemeIcon.asClassName(Codicon.goToSearch), + tooltip: localize('goToSearch', "See in Search Panel") + }]; this.editorViewState.reset(); - disposables.add(picker.onDidCustom(() => { + disposables.add(picker.onDidTriggerButton(() => { if (this.searchModel.searchResult.count() > 0) { this.moveToSearchViewlet(undefined); } else { diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/search.contribution.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/search.contribution.ts index 26e2d6cc..93b06712 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -26,7 +26,7 @@ import { registerContributions as searchWidgetContributions } from 'vs/workbench import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { ISearchViewModelWorkbenchService, SearchViewModelWorkbenchService } from 'vs/workbench/contrib/search/browser/searchModel'; -import { SearchSortOrder, SEARCH_EXCLUDE_CONFIG, VIEWLET_ID, ViewMode, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { SearchSortOrder, SEARCH_EXCLUDE_CONFIG, VIEWLET_ID, ViewMode, VIEW_ID, DEFAULT_MAX_SEARCH_RESULTS } from 'vs/workbench/services/search/common/search'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { assertType } from 'vs/base/common/types'; import { getWorkspaceSymbols, IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search'; @@ -183,13 +183,13 @@ configurationRegistry.registerConfiguration({ }, 'search.useGlobalIgnoreFiles': { type: 'boolean', - markdownDescription: nls.localize('useGlobalIgnoreFiles', "Controls whether to use your global gitignore file (for example, from `$HOME/.config/git/ignore`) when searching for files. Requires `#search.useIgnoreFiles#` to be enabled."), + markdownDescription: nls.localize('useGlobalIgnoreFiles', "Controls whether to use your global gitignore file (for example, from `$HOME/.config/git/ignore`) when searching for files. Requires {0} to be enabled.", '`#search.useIgnoreFiles#`'), default: false, scope: ConfigurationScope.RESOURCE }, 'search.useParentIgnoreFiles': { type: 'boolean', - markdownDescription: nls.localize('useParentIgnoreFiles', "Controls whether to use `.gitignore` and `.ignore` files in parent directories when searching for files. Requires `#search.useIgnoreFiles#` to be enabled."), + markdownDescription: nls.localize('useParentIgnoreFiles', "Controls whether to use `.gitignore` and `.ignore` files in parent directories when searching for files. Requires {0} to be enabled.", '`#search.useIgnoreFiles#`'), default: false, scope: ConfigurationScope.RESOURCE }, @@ -198,6 +198,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('search.quickOpen.includeSymbols', "Whether to include results from a global symbol search in the file results for Quick Open."), default: false }, + 'search.ripgrep.maxThreads': { + type: 'number', + description: nls.localize('search.ripgrep.maxThreads', "Number of threads to use for searching. When set to 0, the engine automatically determines this value."), + default: 0 + }, 'search.quickOpen.includeHistory': { type: 'boolean', description: nls.localize('search.quickOpen.includeHistory', "Whether to include results from recently opened files in the file results for Quick Open."), @@ -238,7 +243,7 @@ configurationRegistry.registerConfiguration({ }, 'search.maxResults': { type: ['number', 'null'], - default: 20000, + default: DEFAULT_MAX_SEARCH_RESULTS, markdownDescription: nls.localize('search.maxResults', "Controls the maximum number of search results, this can be set to `null` (empty) to return unlimited results.") }, 'search.collapseResults': { @@ -329,6 +334,11 @@ configurationRegistry.registerConfiguration({ default: 1, markdownDescription: nls.localize('search.searchEditor.defaultNumberOfContextLines', "The default number of surrounding context lines to use when creating new Search Editors. If using `#search.searchEditor.reusePriorSearchConfiguration#`, this can be set to `null` (empty) to use the prior Search Editor's configuration.") }, + 'search.searchEditor.focusResultsOnSearch': { + type: 'boolean', + default: false, + markdownDescription: nls.localize('search.searchEditor.focusResultsOnSearch', "When a search is triggered, focus the Search Editor results instead of the Search Editor input.") + }, 'search.sortOrder': { type: 'string', enum: [SearchSortOrder.Default, SearchSortOrder.FileNames, SearchSortOrder.Type, SearchSortOrder.Modified, SearchSortOrder.CountDescending, SearchSortOrder.CountAscending], diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/searchActionsFind.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/searchActionsFind.ts index e848ced9..a1fb65de 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/searchActionsFind.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/searchActionsFind.ts @@ -14,7 +14,6 @@ import * as Constants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { FileMatch, FolderMatchWithResource, Match, RenderableMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { URI } from 'vs/base/common/uri'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -34,6 +33,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { Schemas } from 'vs/base/common/network'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; //#region Interfaces @@ -49,6 +49,7 @@ export interface IFindInFilesArgs { matchWholeWord?: boolean; useExcludeSettingsAndIgnoreFiles?: boolean; onlyOpenEditors?: boolean; + showIncludesExcludes?: boolean; } //#endregion @@ -208,6 +209,7 @@ registerAction2(class FindInFilesAction extends Action2 { matchWholeWord: { 'type': 'boolean' }, useExcludeSettingsAndIgnoreFiles: { 'type': 'boolean' }, onlyOpenEditors: { 'type': 'boolean' }, + showIncludesExcludes: { 'type': 'boolean' } } } }, @@ -307,7 +309,6 @@ function expandSelectSubtree(accessor: ServicesAccessor) { } async function searchWithFolderCommand(accessor: ServicesAccessor, isFromExplorer: boolean, isIncludes: boolean, resource?: URI, folderMatch?: FolderMatchWithResource) { - const listService = accessor.get(IListService); const fileService = accessor.get(IFileService); const viewsService = accessor.get(IViewsService); const contextService = accessor.get(IWorkspaceContextService); @@ -318,9 +319,9 @@ async function searchWithFolderCommand(accessor: ServicesAccessor, isFromExplore let resources: URI[]; if (isFromExplorer) { - resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService)); + resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService)); } else { - const searchView = getSearchView(accessor.get(IViewsService)); + const searchView = getSearchView(viewsService); if (!searchView) { return; } @@ -407,6 +408,9 @@ export async function findInFilesCommand(accessor: ServicesAccessor, _args: IFin updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: typeof args.replace !== 'string' }); } openedView.setSearchParameters(args); + if (typeof args.showIncludesExcludes === 'boolean') { + openedView.toggleQueryDetails(false, args.showIncludesExcludes); + } openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText); } diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/searchModel.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/searchModel.ts index 82c166d7..39fa7bb4 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -41,7 +41,7 @@ import { contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches, I import { INotebookSearchService } from 'vs/workbench/contrib/search/common/notebookSearch'; import { rawCellPrefix, INotebookCellMatchNoModel, isINotebookFileMatchNoModel } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IAITextQuery, IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, QueryType, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { DEFAULT_MAX_SEARCH_RESULTS, IAITextQuery, IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, QueryType, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { getTextSearchMatchWithModelContext, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { CellSearchModel } from 'vs/workbench/contrib/search/common/cellSearchModel'; import { CellFindMatchModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; @@ -529,7 +529,7 @@ export class FileMatch extends Disposable implements IFileMatch { const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; const matches = this._model - .findMatches(this._query.pattern, this._model.getFullModelRange(), !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); + .findMatches(this._query.pattern, this._model.getFullModelRange(), !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? DEFAULT_MAX_SEARCH_RESULTS); this.updateMatches(matches, true, this._model, false); } @@ -550,7 +550,7 @@ export class FileMatch extends Disposable implements IFileMatch { oldMatches.forEach(match => this._textMatches.delete(match.id())); const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; - const matches = this._model.findMatches(this._query.pattern, range, !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); + const matches = this._model.findMatches(this._query.pattern, range, !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? DEFAULT_MAX_SEARCH_RESULTS); this.updateMatches(matches, modelChange, this._model, false); // await this.updateMatchesForEditorWidget(); @@ -2479,20 +2479,12 @@ export class RangeHighlightDecorations implements IDisposable { }); } - - function textSearchResultToMatches(rawMatch: ITextSearchMatch, fileMatch: FileMatch, isAiContributed: boolean): Match[] { - const previewLines = rawMatch.preview.text.split('\n'); - if (Array.isArray(rawMatch.ranges)) { - return rawMatch.ranges.map((r, i) => { - const previewRange: ISearchRange = (rawMatch.preview.matches)[i]; - return new Match(fileMatch, previewLines, previewRange, r, isAiContributed); - }); - } else { - const previewRange = rawMatch.preview.matches; - const match = new Match(fileMatch, previewLines, previewRange, rawMatch.ranges, isAiContributed); - return [match]; - } + const previewLines = rawMatch.previewText.split('\n'); + return rawMatch.rangeLocations.map((rangeLocation) => { + const previewRange: ISearchRange = rangeLocation.preview; + return new Match(fileMatch, previewLines, previewRange, rangeLocation.source, isAiContributed); + }); } // text search to notebook matches @@ -2500,18 +2492,12 @@ function textSearchResultToMatches(rawMatch: ITextSearchMatch, fileMatch: FileMa export function textSearchMatchesToNotebookMatches(textSearchMatches: ITextSearchMatch[], cell: CellMatch): MatchInNotebook[] { const notebookMatches: MatchInNotebook[] = []; textSearchMatches.forEach((textSearchMatch) => { - const previewLines = textSearchMatch.preview.text.split('\n'); - if (Array.isArray(textSearchMatch.ranges)) { - textSearchMatch.ranges.forEach((r, i) => { - const previewRange: ISearchRange = (textSearchMatch.preview.matches)[i]; - const match = new MatchInNotebook(cell, previewLines, previewRange, r, textSearchMatch.webviewIndex); - notebookMatches.push(match); - }); - } else { - const previewRange = textSearchMatch.preview.matches; - const match = new MatchInNotebook(cell, previewLines, previewRange, textSearchMatch.ranges, textSearchMatch.webviewIndex); + const previewLines = textSearchMatch.previewText.split('\n'); + textSearchMatch.rangeLocations.map((rangeLocation) => { + const previewRange: ISearchRange = rangeLocation.preview; + const match = new MatchInNotebook(cell, previewLines, previewRange, rangeLocation.source, textSearchMatch.webviewIndex); notebookMatches.push(match); - } + }); }); return notebookMatches; } diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/searchResultsView.ts index d7c088c4..dd790ca1 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -136,7 +136,7 @@ export class FolderMatchRenderer extends Disposable implements ICompressibleTree SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(false); SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(true); - const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain])); + const instantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { menuOptions: { shouldForwardArgs: true @@ -235,7 +235,7 @@ export class FileMatchRenderer extends Disposable implements ICompressibleTreeRe SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(true); SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(false); - const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain])); + const instantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { menuOptions: { shouldForwardArgs: true @@ -327,7 +327,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender SearchContext.FileFocusKey.bindTo(contextKeyServiceMain).set(false); SearchContext.FolderFocusKey.bindTo(contextKeyServiceMain).set(false); - const instantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain])); + const instantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyServiceMain]))); const actions = disposables.add(instantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.SearchActionMenu, { menuOptions: { shouldForwardArgs: true @@ -365,7 +365,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.after.textContent = preview.after; const title = (preview.fullBefore + (replace ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999); - templateData.disposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), templateData.parent, title)); + templateData.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), templateData.parent, title)); SearchContext.IsEditableItemKey.bindTo(templateData.contextKeyService).set(!(match instanceof MatchInNotebook && match.isReadonly())); @@ -377,7 +377,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.lineNumber.classList.toggle('show', (numLines > 0) || showLineNumbers); templateData.lineNumber.textContent = lineNumberStr + extraLinesStr; - templateData.disposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), templateData.lineNumber, this.getMatchTitle(match, showLineNumbers))); + templateData.disposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), templateData.lineNumber, this.getMatchTitle(match, showLineNumbers))); templateData.actions.context = { viewer: this.searchView.getControl(), element: match } satisfies ISearchActionContext; diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/searchView.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/searchView.ts index 69e8a910..c677f053 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/searchView.ts @@ -143,7 +143,7 @@ export class SearchView extends ViewPane { private currentSelectedFileMatch: FileMatch | undefined; private delayedRefresh: Delayer; - private changedWhileHidden: boolean = false; + private changedWhileHidden: boolean; private searchWithoutFolderMessageElement: HTMLElement | undefined; @@ -166,6 +166,9 @@ export class SearchView extends ViewPane { private _onSearchResultChangedDisposable: IDisposable | undefined; + private _stashedQueryDetailsVisibility: boolean | undefined = undefined; + private _stashedReplaceVisibility: boolean | undefined = undefined; + constructor( options: IViewPaneOptions, @IFileService private readonly fileService: IFileService, @@ -237,8 +240,8 @@ export class SearchView extends ViewPane { this.inputPatternExclusionsFocused = Constants.SearchContext.PatternExcludesFocusedKey.bindTo(this.contextKeyService); this.isEditableItem = Constants.SearchContext.IsEditableItemKey.bindTo(this.contextKeyService); - this.instantiationService = this.instantiationService.createChild( - new ServiceCollection([IContextKeyService, this.contextKeyService])); + this.instantiationService = this._register(this.instantiationService.createChild( + new ServiceCollection([IContextKeyService, this.contextKeyService]))); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('search.sortOrder')) { @@ -253,7 +256,7 @@ export class SearchView extends ViewPane { } })); - this.viewModel = this._register(this.searchViewModelWorkbenchService.searchModel); + this.viewModel = this.searchViewModelWorkbenchService.searchModel; this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); this.memento = new Memento(this.id, storageService); this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); @@ -296,6 +299,8 @@ export class SearchView extends ViewPane { this.searchWidget.prependReplaceHistory(restoredHistory.replace); } })); + + this.changedWhileHidden = this.hasSearchResults(); } get isTreeLayoutViewVisible(): boolean { @@ -327,7 +332,32 @@ export class SearchView extends ViewPane { if (visible === this.aiResultsVisible) { return; } + + if (visible) { + this._stashedQueryDetailsVisibility = this._queryDetailsHidden(); + this._stashedReplaceVisibility = this.searchWidget.isReplaceShown(); + + this.searchWidget.toggleReplace(false); + this.toggleQueryDetailsButton.style.display = 'none'; + + this.searchWidget.replaceButtonVisibility = false; + this.toggleQueryDetails(undefined, false); + } else { + this.toggleQueryDetailsButton.style.display = ''; + this.searchWidget.replaceButtonVisibility = true; + + if (this._stashedReplaceVisibility) { + this.searchWidget.toggleReplace(this._stashedReplaceVisibility); + } + + if (this._stashedQueryDetailsVisibility) { + this.toggleQueryDetails(undefined, this._stashedQueryDetailsVisibility); + } + } + this.aiResultsVisible = visible; + + if (this.viewModel.searchResult.isEmpty()) { return; } @@ -336,9 +366,8 @@ export class SearchView extends ViewPane { this.model.cancelAISearch(); if (visible) { await this.model.addAIResults(); - } else { - this.searchWidget.toggleReplace(false); } + this.onSearchResultsChanged(); this.onSearchComplete(() => { }, undefined, undefined, this.viewModel.searchResult.getCachedSearchComplete(visible)); } @@ -453,9 +482,10 @@ export class SearchView extends ViewPane { this.queryDetails = dom.append(this.searchWidgetsContainerElement, $('.query-details')); // Toggle query details button + const toggleQueryDetailsLabel = nls.localize('moreSearch', "Toggle Search Details"); this.toggleQueryDetailsButton = dom.append(this.queryDetails, - $('.more' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button' })); - this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('element'), this.toggleQueryDetailsButton, nls.localize('moreSearch', "Toggle Search Details"))); + $('.more' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button', 'aria-label': toggleQueryDetailsLabel })); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), this.toggleQueryDetailsButton, toggleQueryDetailsLabel)); this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => { dom.EventHelper.stop(e); @@ -1482,6 +1512,10 @@ export class SearchView extends ViewPane { } } + private _queryDetailsHidden() { + return this.queryDetails.classList.contains('more'); + } + searchInFolders(folderPaths: string[] = []): void { this._searchWithIncludeOrExclude(true, folderPaths); } @@ -1558,7 +1592,7 @@ export class SearchView extends ViewPane { } }; - const excludePattern = this.inputPatternExcludes.getValue(); + const excludePattern = [{ pattern: this.inputPatternExcludes.getValue() }]; const includePattern = this.inputPatternIncludes.getValue(); // Need the full match line to correctly calculate replace text, if this is a search/replace with regex group references ($1, $2, ...). @@ -2222,7 +2256,7 @@ class SearchLinkButton extends Disposable { constructor(label: string, handler: (e: dom.EventLike) => unknown, hoverService: IHoverService, tooltip?: string) { super(); this.element = $('a.pointer', { tabindex: 0 }, label); - this._register(hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.element, tooltip)); + this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, tooltip)); this.addEventHandlers(handler); } diff --git a/patched-vscode/src/vs/workbench/contrib/search/browser/searchWidget.ts b/patched-vscode/src/vs/workbench/contrib/search/browser/searchWidget.ts index cf86112b..f8beab04 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -16,7 +17,6 @@ import { Delayer } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/browser/findModel'; -import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -44,6 +44,7 @@ import { GroupModelChangeKind } from 'vs/workbench/common/editor'; import { SearchFindInput } from 'vs/workbench/contrib/search/browser/searchFindInput'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { NotebookFindScopeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; /** Specified in searchview.css */ const SingleLineInputHeight = 26; @@ -205,8 +206,7 @@ export class SearchWidget extends Widget { notebookOptions.isInNotebookMarkdownPreview, notebookOptions.isInNotebookCellInput, notebookOptions.isInNotebookCellOutput, - false, - [] + { findScopeType: NotebookFindScopeType.None } )); this._register( @@ -352,6 +352,12 @@ export class SearchWidget extends Widget { this.searchInput?.focusOnRegex(); } + set replaceButtonVisibility(val: boolean) { + if (this.toggleReplaceButton) { + this.toggleReplaceButton.element.style.display = val ? '' : 'none'; + } + } + private render(container: HTMLElement, options: ISearchWidgetOptions): void { this.domNode = dom.append(container, dom.$('.search-widget')); this.domNode.style.position = 'relative'; @@ -523,7 +529,7 @@ export class SearchWidget extends Widget { } })); - this.replaceInput.onKeyDown((keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent)); + this._register(this.replaceInput.onKeyDown((keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent))); this.replaceInput.setValue(options.replaceValue || ''); this._register(this.replaceInput.inputBox.onDidChange(() => this._onReplaceValueChanged.fire())); this._register(this.replaceInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire())); @@ -665,6 +671,25 @@ export class SearchWidget extends Widget { else if (keyboardEvent.equals(KeyCode.DownArrow)) { stopPropagationForMultiLineDownwards(keyboardEvent, this.searchInput?.getValue() ?? '', this.searchInput?.domNode.querySelector('textarea') ?? null); } + + else if (keyboardEvent.equals(KeyCode.PageUp)) { + const inputElement = this.searchInput?.inputBox.inputElement; + if (inputElement) { + inputElement.setSelectionRange(0, 0); + inputElement.focus(); + keyboardEvent.preventDefault(); + } + } + + else if (keyboardEvent.equals(KeyCode.PageDown)) { + const inputElement = this.searchInput?.inputBox.inputElement; + if (inputElement) { + const endOfText = inputElement.value.length; + inputElement.setSelectionRange(endOfText, endOfText); + inputElement.focus(); + keyboardEvent.preventDefault(); + } + } } private onCaseSensitiveKeyDown(keyboardEvent: IKeyboardEvent) { diff --git a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index 9b3b9e4f..2e98bb36 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Keybinding } from 'vs/base/common/keybindings'; import { OS } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; diff --git a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 17f9e88b..5c79a6d8 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchModel.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import * as arrays from 'vs/base/common/arrays'; import { DeferredPromise, timeout } from 'vs/base/common/async'; @@ -644,4 +644,3 @@ suite('SearchModel', () => { return notebookEditorWidgetService; } }); - diff --git a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts index a90875c1..fbd2327e 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IReadonlyTextBuffer } from 'vs/editor/common/model'; import { IFileMatch, ISearchRange, ITextSearchMatch, QueryType } from 'vs/workbench/services/search/common/search'; @@ -149,28 +149,28 @@ suite('searchNotebookHelpers', () => { codeWebviewResults = webviewMatchesToTextSearchMatches(codeCellFindMatch.webviewMatches); assert.strictEqual(markdownContentResults.length, 1); - assert.strictEqual(markdownContentResults[0].preview.text, '# Hello World Test\n'); - assertRangesEqual(markdownContentResults[0].preview.matches, [new Range(0, 14, 0, 18)]); - assertRangesEqual(markdownContentResults[0].ranges, [new Range(0, 14, 0, 18)]); + assert.strictEqual(markdownContentResults[0].previewText, '# Hello World Test\n'); + assertRangesEqual(markdownContentResults[0].rangeLocations.map(e => e.preview), [new Range(0, 14, 0, 18)]); + assertRangesEqual(markdownContentResults[0].rangeLocations.map(e => e.source), [new Range(0, 14, 0, 18)]); assert.strictEqual(codeContentResults.length, 2); - assert.strictEqual(codeContentResults[0].preview.text, 'print("test! testing!!")\n'); - assert.strictEqual(codeContentResults[1].preview.text, 'print("this is a Test")\n'); - assertRangesEqual(codeContentResults[0].preview.matches, [new Range(0, 7, 0, 11), new Range(0, 13, 0, 17)]); - assertRangesEqual(codeContentResults[0].ranges, [new Range(0, 7, 0, 11), new Range(0, 13, 0, 17)]); + assert.strictEqual(codeContentResults[0].previewText, 'print("test! testing!!")\n'); + assert.strictEqual(codeContentResults[1].previewText, 'print("this is a Test")\n'); + assertRangesEqual(codeContentResults[0].rangeLocations.map(e => e.preview), [new Range(0, 7, 0, 11), new Range(0, 13, 0, 17)]); + assertRangesEqual(codeContentResults[0].rangeLocations.map(e => e.source), [new Range(0, 7, 0, 11), new Range(0, 13, 0, 17)]); assert.strictEqual(codeWebviewResults.length, 3); - assert.strictEqual(codeWebviewResults[0].preview.text, 'test! testing!!'); - assert.strictEqual(codeWebviewResults[1].preview.text, 'test! testing!!'); - assert.strictEqual(codeWebviewResults[2].preview.text, 'this is a Test'); - - assertRangesEqual(codeWebviewResults[0].preview.matches, [new Range(0, 1, 0, 5)]); - assertRangesEqual(codeWebviewResults[1].preview.matches, [new Range(0, 7, 0, 11)]); - assertRangesEqual(codeWebviewResults[2].preview.matches, [new Range(0, 11, 0, 15)]); - assertRangesEqual(codeWebviewResults[0].ranges, [new Range(0, 1, 0, 5)]); - assertRangesEqual(codeWebviewResults[1].ranges, [new Range(0, 7, 0, 11)]); - assertRangesEqual(codeWebviewResults[2].ranges, [new Range(0, 11, 0, 15)]); + assert.strictEqual(codeWebviewResults[0].previewText, 'test! testing!!'); + assert.strictEqual(codeWebviewResults[1].previewText, 'test! testing!!'); + assert.strictEqual(codeWebviewResults[2].previewText, 'this is a Test'); + + assertRangesEqual(codeWebviewResults[0].rangeLocations.map(e => e.preview), [new Range(0, 1, 0, 5)]); + assertRangesEqual(codeWebviewResults[1].rangeLocations.map(e => e.preview), [new Range(0, 7, 0, 11)]); + assertRangesEqual(codeWebviewResults[2].rangeLocations.map(e => e.preview), [new Range(0, 11, 0, 15)]); + assertRangesEqual(codeWebviewResults[0].rangeLocations.map(e => e.source), [new Range(0, 1, 0, 5)]); + assertRangesEqual(codeWebviewResults[1].rangeLocations.map(e => e.source), [new Range(0, 7, 0, 11)]); + assertRangesEqual(codeWebviewResults[2].rangeLocations.map(e => e.source), [new Range(0, 11, 0, 15)]); }); test('convert ITextSearchMatch to MatchInNotebook', () => { diff --git a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index e71fab4b..600cc67f 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchResult.test.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 * as assert from 'assert'; +import assert from 'assert'; import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Match, FileMatch, SearchResult, SearchModel, FolderMatch, CellMatch } from 'vs/workbench/contrib/search/browser/searchModel'; diff --git a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 4feb3d34..d63cc7c4 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.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 * as assert from 'assert'; +import 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'; @@ -60,21 +60,24 @@ suite('Search - Viewlet', () => { result.add([{ resource: createFileUriFromPathFromRoot('/foo'), results: [{ - preview: { - text: 'bar', - matches: { - startLineNumber: 0, - startColumn: 0, - endLineNumber: 0, - endColumn: 1 + + previewText: 'bar', + rangeLocations: [ + { + preview: { + startLineNumber: 0, + startColumn: 0, + endLineNumber: 0, + endColumn: 1 + }, + source: { + startLineNumber: 1, + startColumn: 0, + endLineNumber: 1, + endColumn: 1 + } } - }, - ranges: { - startLineNumber: 1, - startColumn: 0, - endLineNumber: 1, - endColumn: 1 - } + ] }] }], '', false); diff --git a/patched-vscode/src/vs/workbench/contrib/search/test/common/cacheState.test.ts b/patched-vscode/src/vs/workbench/contrib/search/test/common/cacheState.test.ts index a986de9b..ea1241be 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/test/common/cacheState.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/test/common/cacheState.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as errors from 'vs/base/common/errors'; import { QueryType, IFileQuery } from 'vs/workbench/services/search/common/search'; import { FileQueryCacheState } from 'vs/workbench/contrib/search/common/cacheState'; diff --git a/patched-vscode/src/vs/workbench/contrib/search/test/common/extractRange.test.ts b/patched-vscode/src/vs/workbench/contrib/search/test/common/extractRange.test.ts index 4651bbc6..0758b486 100644 --- a/patched-vscode/src/vs/workbench/contrib/search/test/common/extractRange.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/search/test/common/extractRange.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { extractRangeFromFilter } from 'vs/workbench/contrib/search/common/search'; @@ -98,4 +98,3 @@ suite('extractRangeFromFilter', () => { } }); }); - diff --git a/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index c0f0f27d..32f8ac94 100644 --- a/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -141,7 +141,7 @@ export class SearchEditor extends AbstractTextCodeEditor this.createQueryEditor( this.queryEditorContainer, - this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])), + this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService]))), SearchContext.InputBoxFocusedKey.bindTo(scopedContextKeyService) ); } @@ -165,8 +165,9 @@ export class SearchEditor extends AbstractTextCodeEditor this.includesExcludesContainer = DOM.append(container, DOM.$('.includes-excludes')); // Toggle query details button - this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button' })); - this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('element'), this.toggleQueryDetailsButton, localize('moreSearch', "Toggle Search Details"))); + const toggleQueryDetailsLabel = localize('moreSearch', "Toggle Search Details"); + this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button', 'aria-label': toggleQueryDetailsLabel })); + this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), this.toggleQueryDetailsButton, toggleQueryDetailsLabel)); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.CLICK, e => { DOM.EventHelper.stop(e); this.toggleIncludesExcludes(); @@ -483,8 +484,21 @@ export class SearchEditor extends AbstractTextCodeEditor } async triggerSearch(_options?: { resetCursor?: boolean; delay?: number; focusResults?: boolean }) { + const focusResults = this.searchConfig.searchEditor.focusResultsOnSearch; + + // If _options don't define focusResult field, then use the setting + if (_options === undefined) { + _options = { focusResults: focusResults }; + } else if (_options.focusResults === undefined) { + _options.focusResults = focusResults; + } + const options = { resetCursor: true, delay: 0, ..._options }; + if (!(this.queryEditorWidget.searchInput?.inputBox.isInputValid())) { + return; + } + if (!this.pauseSearching) { await this.runSearchDelayer.trigger(async () => { this.toggleRunAgainMessage(false); @@ -550,15 +564,14 @@ export class SearchEditor extends AbstractTextCodeEditor maxResults: this.searchConfig.maxResults ?? undefined, disregardIgnoreFiles: !config.useExcludeSettingsAndIgnoreFiles || undefined, disregardExcludeSettings: !config.useExcludeSettingsAndIgnoreFiles || undefined, - excludePattern: config.filesToExclude, + excludePattern: [{ pattern: config.filesToExclude }], includePattern: config.filesToInclude, onlyOpenEditors: config.onlyOpenEditors, previewOptions: { matchLines: 1, charsPerLine: 1000 }, - afterContext: config.contextLines, - beforeContext: config.contextLines, + surroundingContext: config.contextLines, isSmartCase: this.searchConfig.smartCase, expandPatterns: true, notebookSearchConfig: { @@ -776,7 +789,7 @@ export class SearchEditor extends AbstractTextCodeEditor } } -const searchEditorTextInputBorder = registerColor('searchEditor.textInputBorder', { dark: inputBorder, light: inputBorder, hcDark: inputBorder, hcLight: inputBorder }, localize('textInputBoxBorder', "Search editor text input box border.")); +const searchEditorTextInputBorder = registerColor('searchEditor.textInputBorder', inputBorder, localize('textInputBoxBorder', "Search editor text input box border.")); function findNextRange(matchRanges: Range[], currentPosition: Position) { for (const matchRange of matchRanges) { diff --git a/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 5ab5da3f..970ea589 100644 --- a/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -219,6 +219,6 @@ export const createEditorFromSearchResult = } else { const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { from: 'rawData', resultsContents: '', config: { ...config, contextLines } }); const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; - editor.triggerSearch({ focusResults: true }); + editor.triggerSearch(); } }; diff --git a/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 2085c52e..68b40814 100644 --- a/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/patched-vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -128,7 +128,7 @@ export class SearchEditorInput extends EditorInput { } this.memento = new Memento(SearchEditorInput.ID, storageService); - storageService.onWillSaveState(() => this.memento.saveMemento()); + this._register(storageService.onWillSaveState(() => this.memento.saveMemento())); const input = this; const workingCopyAdapter = new class implements IWorkingCopy { diff --git a/patched-vscode/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/patched-vscode/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 77dad6ef..198ec6d5 100644 --- a/patched-vscode/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/patched-vscode/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -8,6 +8,7 @@ import { extname } from 'vs/base/common/path'; import { basename, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { getIconClassesForLanguageId } from 'vs/editor/common/services/getIconClasses'; import * as nls from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; @@ -115,7 +116,8 @@ async function computePicks(snippetService: ISnippetsService, userDataProfileSer label: languageId, description: `(${label})`, filepath: joinPath(dir, `${languageId}.json`), - hint: true + hint: true, + iconClasses: getIconClassesForLanguageId(languageId) }); } } @@ -225,10 +227,10 @@ export class ConfigureSnippetsAction extends SnippetsAction { constructor() { super({ id: 'workbench.action.openSnippets', - title: nls.localize2('openSnippet.label', "Configure User Snippets"), + title: nls.localize2('openSnippet.label', "Configure Snippets"), shortTitle: { - ...nls.localize2('userSnippets', "User Snippets"), - mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), + ...nls.localize2('userSnippets', "Snippets"), + mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "&&Snippets"), }, f1: true, menu: [ diff --git a/patched-vscode/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts b/patched-vscode/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts index 192845aa..fea6822a 100644 --- a/patched-vscode/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts +++ b/patched-vscode/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts @@ -11,6 +11,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { Event } from 'vs/base/common/event'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export async function pickSnippet(accessor: ServicesAccessor, languageIdOrSnippets: string | Snippet[]): Promise { @@ -77,16 +78,17 @@ export async function pickSnippet(accessor: ServicesAccessor, languageIdOrSnippe return result; }; - const picker = quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const picker = disposables.add(quickInputService.createQuickPick({ useSeparators: true })); picker.placeholder = nls.localize('pick.placeholder', "Select a snippet"); picker.matchOnDetail = true; picker.ignoreFocusOut = false; picker.keepScrollPosition = true; - picker.onDidTriggerItemButton(ctx => { + disposables.add(picker.onDidTriggerItemButton(ctx => { const isEnabled = snippetService.isEnabled(ctx.item.snippet); snippetService.updateEnablement(ctx.item.snippet, !isEnabled); picker.items = makeSnippetPicks(); - }); + })); picker.items = makeSnippetPicks(); if (!picker.items.length) { picker.validationMessage = nls.localize('pick.noSnippetAvailable', "No snippet available"); @@ -96,6 +98,6 @@ export async function pickSnippet(accessor: ServicesAccessor, languageIdOrSnippe // wait for an item to be picked or the picker to become hidden await Promise.race([Event.toPromise(picker.onDidAccept), Event.toPromise(picker.onDidHide)]); const result = picker.selectedItems[0]?.snippet; - picker.dispose(); + disposables.dispose(); return result; } diff --git a/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts b/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts index 82e14ccc..6b5bef90 100644 --- a/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { SnippetFile, Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { URI } from 'vs/base/common/uri'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; diff --git a/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts b/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts index 8c185e76..d4824fdf 100644 --- a/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { getNonWhitespacePrefix } from 'vs/workbench/contrib/snippets/browser/snippetsService'; import { Position } from 'vs/editor/common/core/position'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; diff --git a/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts b/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts index 5ac2018e..39b490f2 100644 --- a/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { generateUuid } from 'vs/base/common/uuid'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; diff --git a/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index ce61496e..297836af 100644 --- a/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { SnippetCompletion, SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; diff --git a/patched-vscode/src/vs/workbench/contrib/speech/test/common/speechService.test.ts b/patched-vscode/src/vs/workbench/contrib/speech/test/common/speechService.test.ts index d757eace..16a4f0d9 100644 --- a/patched-vscode/src/vs/workbench/contrib/speech/test/common/speechService.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/speech/test/common/speechService.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { speechLanguageConfigToLanguage } from 'vs/workbench/contrib/speech/common/speechService'; diff --git a/patched-vscode/src/vs/workbench/contrib/splash/browser/partsSplash.ts b/patched-vscode/src/vs/workbench/contrib/splash/browser/partsSplash.ts index 6982a2cd..8b2dd0dc 100644 --- a/patched-vscode/src/vs/workbench/contrib/splash/browser/partsSplash.ts +++ b/patched-vscode/src/vs/workbench/contrib/splash/browser/partsSplash.ts @@ -110,8 +110,6 @@ export class PartsSplash { // remove initial colors const defaultStyles = mainWindow.document.head.getElementsByClassName('initialShellColors'); - if (defaultStyles.length) { - mainWindow.document.head.removeChild(defaultStyles[0]); - } + defaultStyles[0]?.remove(); } } diff --git a/patched-vscode/src/vs/workbench/contrib/surveys/browser/ces.contribution.ts b/patched-vscode/src/vs/workbench/contrib/surveys/browser/ces.contribution.ts deleted file mode 100644 index b24d91f8..00000000 --- a/patched-vscode/src/vs/workbench/contrib/surveys/browser/ces.contribution.ts +++ /dev/null @@ -1,161 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { language } from 'vs/base/common/platform'; -import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; -import { URI } from 'vs/base/common/uri'; -import { platform } from 'vs/base/common/process'; -import { ThrottledDelayer } from 'vs/base/common/async'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; - -const WAIT_TIME_TO_SHOW_SURVEY = 1000 * 60 * 60; // 1 hour -const MIN_WAIT_TIME_TO_SHOW_SURVEY = 1000 * 60 * 2; // 2 minutes -const MAX_INSTALL_AGE = 1000 * 60 * 60 * 24; // 24 hours -const REMIND_LATER_DELAY = 1000 * 60 * 60 * 4; // 4 hours -const SKIP_SURVEY_KEY = 'ces/skipSurvey'; -const REMIND_LATER_DATE_KEY = 'ces/remindLaterDate'; - -class CESContribution extends Disposable implements IWorkbenchContribution { - - private promptDelayer = this._register(new ThrottledDelayer(0)); - private readonly tasExperimentService: IWorkbenchAssignmentService | undefined; - - constructor( - @IStorageService private readonly storageService: IStorageService, - @INotificationService private readonly notificationService: INotificationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IOpenerService private readonly openerService: IOpenerService, - @IProductService private readonly productService: IProductService, - @IWorkbenchAssignmentService tasExperimentService: IWorkbenchAssignmentService, - ) { - super(); - - this.tasExperimentService = tasExperimentService; - - if (!productService.cesSurveyUrl) { - return; - } - - const skipSurvey = storageService.get(SKIP_SURVEY_KEY, StorageScope.APPLICATION, ''); - if (skipSurvey) { - return; - } - - this.schedulePrompt(); - } - - private async promptUser() { - const isCandidate = await this.tasExperimentService?.getTreatment('CESSurvey'); - if (!isCandidate) { - this.skipSurvey(); - return; - } - - const sendTelemetry = (userReaction: 'accept' | 'remindLater' | 'neverShowAgain' | 'cancelled') => { - /* __GDPR__ - "cesSurvey:popup" : { - "owner": "digitarald", - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('cesSurvey:popup', { userReaction }); - }; - - const message = await this.tasExperimentService?.getTreatment('CESSurveyMessage') ?? nls.localize('cesSurveyQuestion', 'Got a moment to help the VS Code team? Please tell us about your experience with VS Code so far.'); - const button = await this.tasExperimentService?.getTreatment('CESSurveyButton') ?? nls.localize('giveFeedback', "Give Feedback"); - - const notification = this.notificationService.prompt( - Severity.Info, - message, - [{ - label: button, - run: () => { - sendTelemetry('accept'); - let surveyUrl = `${this.productService.cesSurveyUrl}?o=${encodeURIComponent(platform)}&v=${encodeURIComponent(this.productService.version)}&m=${encodeURIComponent(this.telemetryService.machineId)}`; - - const usedParams = this.productService.surveys - ?.filter(surveyData => surveyData.surveyId && surveyData.languageId) - // Counts provided by contrib/surveys/browser/languageSurveys - .filter(surveyData => this.storageService.getNumber(`${surveyData.surveyId}.editedCount`, StorageScope.APPLICATION, 0) > 0) - .map(surveyData => `${encodeURIComponent(surveyData.languageId)}Lang=1`) - .join('&'); - if (usedParams) { - surveyUrl += `&${usedParams}`; - } - this.openerService.open(URI.parse(surveyUrl)); - this.skipSurvey(); - } - }, { - label: nls.localize('remindLater', "Remind Me Later"), - run: () => { - sendTelemetry('remindLater'); - this.storageService.store(REMIND_LATER_DATE_KEY, new Date().toUTCString(), StorageScope.APPLICATION, StorageTarget.USER); - this.schedulePrompt(); - } - }], - { - sticky: true, - onCancel: () => { - sendTelemetry('cancelled'); - this.skipSurvey(); - } - } - ); - - await Event.toPromise(notification.onDidClose); - } - - private async schedulePrompt(): Promise { - let waitTimeToShowSurvey = 0; - const remindLaterDate = this.storageService.get(REMIND_LATER_DATE_KEY, StorageScope.APPLICATION, ''); - if (remindLaterDate) { - const timeToRemind = new Date(remindLaterDate).getTime() + REMIND_LATER_DELAY - Date.now(); - if (timeToRemind > 0) { - waitTimeToShowSurvey = timeToRemind; - } - } else { - const timeFromInstall = Date.now() - new Date(this.telemetryService.firstSessionDate).getTime(); - const isNewInstall = !isNaN(timeFromInstall) && timeFromInstall < MAX_INSTALL_AGE; - - // Installation is older than MAX_INSTALL_AGE - if (!isNewInstall) { - this.skipSurvey(); - return; - } - if (timeFromInstall < WAIT_TIME_TO_SHOW_SURVEY) { - waitTimeToShowSurvey = WAIT_TIME_TO_SHOW_SURVEY - timeFromInstall; - } - } - /* __GDPR__ - "cesSurvey:schedule" : { - "owner": "digitarald" - } - */ - this.telemetryService.publicLog('cesSurvey:schedule'); - - this.promptDelayer.trigger(async () => { - await this.promptUser(); - }, Math.max(waitTimeToShowSurvey, MIN_WAIT_TIME_TO_SHOW_SURVEY)); - } - - private skipSurvey(): void { - this.storageService.store(SKIP_SURVEY_KEY, this.productService.version, StorageScope.APPLICATION, StorageTarget.USER); - } -} - -if (language === 'en') { - const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); - workbenchRegistry.registerWorkbenchContribution(CESContribution, LifecyclePhase.Restored); -} diff --git a/patched-vscode/src/vs/workbench/contrib/tags/common/javaWorkspaceTags.ts b/patched-vscode/src/vs/workbench/contrib/tags/common/javaWorkspaceTags.ts index 5e20a341..5f2f1064 100644 --- a/patched-vscode/src/vs/workbench/contrib/tags/common/javaWorkspaceTags.ts +++ b/patched-vscode/src/vs/workbench/contrib/tags/common/javaWorkspaceTags.ts @@ -55,6 +55,25 @@ export const JavaLibrariesToLookFor: { predicate: (groupId: string, artifactId: // event hubs { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId.startsWith('azure-messaging-eventhubs'), 'tag': 'azure-eventhubs' }, { 'predicate': (groupId, artifactId) => groupId === 'com.azure.spring' && artifactId.includes('eventhubs'), 'tag': 'azure-eventhubs' }, + // ai related libraries + { 'predicate': (groupId, artifactId) => groupId === 'dev.langchain4j', 'tag': 'langchain4j' }, + { 'predicate': (groupId, artifactId) => groupId === 'io.springboot.ai', 'tag': 'springboot-ai' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.microsoft.semantic-kernel', 'tag': 'semantic-kernel' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-anomalydetector', 'tag': 'azure-ai-anomalydetector' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-formrecognizer', 'tag': 'azure-ai-formrecognizer' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-documentintelligence', 'tag': 'azure-ai-documentintelligence' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-translation-document', 'tag': 'azure-ai-translation-document' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-personalizer', 'tag': 'azure-ai-personalizer' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-translation-text', 'tag': 'azure-ai-translation-text' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-contentsafety', 'tag': 'azure-ai-contentsafety' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-vision-imageanalysis', 'tag': 'azure-ai-vision-imageanalysis' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-textanalytics', 'tag': 'azure-ai-textanalytics' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-search-documents', 'tag': 'azure-search-documents' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-documenttranslator', 'tag': 'azure-ai-documenttranslator' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-vision-face', 'tag': 'azure-ai-vision-face' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.azure' && artifactId === 'azure-ai-openai-assistants', 'tag': 'azure-ai-openai-assistants' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.microsoft.azure.cognitiveservices', 'tag': 'azure-cognitiveservices' }, + { 'predicate': (groupId, artifactId) => groupId === 'com.microsoft.cognitiveservices.speech', 'tag': 'azure-cognitiveservices-speech' }, // open ai { 'predicate': (groupId, artifactId) => groupId === 'com.theokanning.openai-gpt3-java', 'tag': 'openai' }, // azure open ai diff --git a/patched-vscode/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/patched-vscode/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index b560ed89..9523e6c7 100644 --- a/patched-vscode/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/patched-vscode/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -160,6 +160,7 @@ const ModulesToLookFor = [ '@azure/attestation', '@azure/data-tables', '@azure/arm-appservice', + '@azure-rest/ai-inference', '@azure-rest/arm-appservice', '@azure/arm-appcontainers', '@azure/arm-rediscache', @@ -178,7 +179,59 @@ const ModulesToLookFor = [ '@azure/web-pubsub-express', '@azure/openai', '@azure/arm-hybridkubernetes', - '@azure/arm-kubernetesconfiguration' + '@azure/arm-kubernetesconfiguration', + //AI and vector db dev packages + '@anthropic-ai/sdk', + '@anthropic-ai/tokenizer', + '@arizeai/openinference-instrumentation-langchain', + '@arizeai/openinference-instrumentation-openai', + '@aws-sdk-client-bedrock-runtime', + '@aws-sdk/client-bedrock', + '@datastax/astra-db-ts', + 'fireworks-js', + '@google-cloud/aiplatform', + '@huggingface/inference', + 'humanloop', + '@langchain/anthropic', + 'langsmith', + 'llamaindex', + 'mongodb', + 'neo4j-driver', + 'ollama', + 'onnxruntime-node', + 'onnxruntime-web', + 'pg', + 'postgresql', + 'redis', + '@supabase/supabase-js', + '@tensorflow/tfjs', + '@xenova/transformers', + 'tika', + 'weaviate-client', + '@zilliz/milvus2-sdk-node', + //Azure AI + '@azure-rest/ai-anomaly-detector', + '@azure-rest/ai-content-safety', + '@azure-rest/ai-document-intelligence', + '@azure-rest/ai-document-translator', + '@azure-rest/ai-personalizer', + '@azure-rest/ai-translation-text', + '@azure-rest/ai-vision-image-analysis', + '@azure/ai-anomaly-detector', + '@azure/ai-form-recognizer', + '@azure/ai-language-conversations', + '@azure/ai-language-text', + '@azure/ai-text-analytics', + '@azure/arm-botservice', + '@azure/arm-cognitiveservices', + '@azure/arm-machinelearning', + '@azure/cognitiveservices-contentmoderator', + '@azure/cognitiveservices-customvision-prediction', + '@azure/cognitiveservices-customvision-training', + '@azure/cognitiveservices-face', + '@azure/cognitiveservices-translatortext', + 'microsoft-cognitiveservices-speech-sdk', + '@google/generative-ai' ]; const PyMetaModulesToLookFor = [ @@ -197,6 +250,7 @@ const PyMetaModulesToLookFor = [ const PyModulesToLookFor = [ 'azure', + 'azure-ai-inference', 'azure-ai-language-conversations', 'azure-ai-language-questionanswering', 'azure-ai-ml', @@ -311,7 +365,39 @@ const PyModulesToLookFor = [ 'guidance', 'openai', 'semantic-kernel', - 'sentence-transformers' + 'sentence-transformers', + // AI and vector db dev packages + 'anthropic', + 'aporia', + 'arize', + 'deepchecks', + 'fireworks-ai', + 'langchain-fireworks', + 'humanloop', + 'pymongo', + 'langchain-anthropic', + 'langchain-huggingface', + 'langchain-fireworks', + 'ollama', + 'onnxruntime', + 'pgvector', + 'sentence-transformers', + 'tika', + 'trulens', + 'trulens-eval', + 'wandb', + // Azure AI Services + 'azure-ai-contentsafety', + 'azure-ai-documentintelligence', + 'azure-ai-translation-text', + 'azure-ai-vision', + 'azure-cognitiveservices-language-luis', + 'azure-cognitiveservices-speech', + 'azure-cognitiveservices-vision-contentmoderator', + 'azure-cognitiveservices-vision-face', + 'azure-mgmt-cognitiveservices', + 'azure-mgmt-search', + 'google-generativeai' ]; const GoModulesToLookFor = [ @@ -428,6 +514,12 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@angular/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.vue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@anthropic-ai/sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@anthropic-ai/tokenizer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@arizeai/openinference-instrumentation-langchain" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@arizeai/openinference-instrumentation-openai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@aws-sdk-client-bedrock-runtime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@aws-sdk/client-bedrock" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.aws-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.aws-amplify-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -439,11 +531,13 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.@azure/keyvault" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/search" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@google-cloud/aiplatform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@google-cloud/common" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.firebase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.heroku-cli" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@huggingface/inference" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@microsoft/teams-js" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@microsoft/office-js" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@microsoft/office-js-helpers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -470,15 +564,35 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.cypress" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.chroma" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.faiss" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.fireworks-js" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@datastax/astra-db-ts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.humanloop" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.langchain" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@langchain/anthropic" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.langsmith" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.llamaindex" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.milvus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.mongodb" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.neo4j-driver" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.ollama" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.onnxruntime-node" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.onnxruntime-web" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.openai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.pinecone" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.postgresql" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.pg" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.qdrant" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.redis" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@supabase/supabase-js" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@tensorflow/tfjs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@xenova/transformers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.weaviate-client" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@zilliz/milvus2-sdk-node" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.nightwatch" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.protractor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.puppeteer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.selenium-webdriver" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.tika" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.webdriverio" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.gherkin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/app-configuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -490,6 +604,27 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.@azure/synapse-artifacts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/synapse-access-control" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/ai-metrics-advisor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure-rest/ai-anomaly-detector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure-rest/ai-content-safety" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure-rest/ai-document-intelligence" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure-rest/ai-document-translator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure-rest/ai-personalizer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure-rest/ai-translation-text" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure-rest/ai-vision-image-analysis" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/ai-anomaly-detector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/ai-form-recognizer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/ai-language-conversations" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/ai-language-text" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/ai-text-analytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/arm-botservice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/arm-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/arm-machinelearning" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/cognitiveservices-contentmoderator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/cognitiveservices-customvision-prediction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/cognitiveservices-customvision-training" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/cognitiveservices-face" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/cognitiveservices-translatortext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.microsoft-cognitiveservices-speech-sdk" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/service-bus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/keyvault-secrets" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/keyvault-keys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -538,6 +673,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.@azure/communication-administration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/attestation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/data-tables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure-rest/ai-inference" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure-rest/arm-appservice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/arm-appservice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/arm-appcontainers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -559,6 +695,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.@azure/arm-kubernetesconfiguration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.react-native-macos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.react-native-windows" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@google/generative-ai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.bower" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.yeoman.code.ext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.cordova.high" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -586,6 +723,24 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.gradle.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.gradle.azure-servicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.gradle.azure-eventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.langchain4j" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.springboot-ai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.semantic-kernel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-anomalydetector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-formrecognizer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-documentintelligence" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-translation-document" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-personalizer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-translation-text" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-contentsafety" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-vision-imageanalysis" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-textanalytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-search-documents" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-documenttranslator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-vision-face" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-ai-openai-assistants" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.gradle.azure-cognitiveservices-speech" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.gradle.openai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.gradle.azure-openai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.gradle.azure-functions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -607,6 +762,24 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.pom.azure-storage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.pom.azure-servicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.pom.azure-eventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.langchain4j" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.springboot-ai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.semantic-kernel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-anomalydetector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-formrecognizer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-documentintelligence" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-translation-document" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-personalizer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-translation-text" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-contentsafety" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-vision-imageanalysis" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-textanalytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-search-documents" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-documenttranslator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-vision-face" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-ai-openai-assistants" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.pom.azure-cognitiveservices-speech" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.pom.openai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.pom.azure-openai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.pom.azure-functions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -627,9 +800,20 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.pulumi-azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-ai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-ai-inference" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-ai-language-conversations" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-ai-language-questionanswering" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-ai-ml" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-ai-contentsafety" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-ai-documentintelligence" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-ai-translation-text" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-ai-vision" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-cognitiveservices-language-luis" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-cognitiveservices-speech" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-cognitiveservices-vision-contentmoderator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-cognitiveservices-vision-face" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-mgmt-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.azure-mgmt-search" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-ai-translation-document" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-cognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -745,13 +929,31 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.azure-messaging-webpubsubservice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-data-nspkg" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-data-tables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.arize" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.aporia" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.anthropic" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.deepchecks" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.fireworks-ai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.transformers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.humanloop" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.langchain" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.langchain-anthropic" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.langchain-fireworks" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.langchain-huggingface" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.llama-index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.guidance" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.ollama" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.onnxruntime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.openai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.pymongo" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.pgvector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.semantic-kernel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.sentence-transformers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.tika" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.trulens" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.trulens-eval" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.wandb" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.py.google-generativeai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, diff --git a/patched-vscode/src/vs/workbench/contrib/tags/test/node/workspaceTags.test.ts b/patched-vscode/src/vs/workbench/contrib/tags/test/node/workspaceTags.test.ts index 13993422..d166a151 100644 --- a/patched-vscode/src/vs/workbench/contrib/tags/test/node/workspaceTags.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/tags/test/node/workspaceTags.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import assert from 'assert'; import * as crypto from 'crypto'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { getHashedRemotesFromConfig as baseGetHashedRemotesFromConfig } from 'vs/workbench/contrib/tags/common/workspaceTags'; diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/patched-vscode/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 0e9b7879..4670c5aa 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -55,7 +55,7 @@ import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/com import * as TaskConfig from '../common/taskConfiguration'; import { TerminalTaskSystem } from './terminalTaskSystem'; -import { IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickSeparator, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; @@ -234,6 +234,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _onDidChangeTaskConfig: Emitter = new Emitter(); public onDidChangeTaskConfig: Event = this._onDidChangeTaskConfig.event; public get isReconnected(): boolean { return this._tasksReconnected; } + private _onDidChangeTaskProviders = this._register(new Emitter()); + public onDidChangeTaskProviders = this._onDidChangeTaskProviders.event; constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -316,15 +318,22 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._registerCommands().then(() => TaskCommandsRegistered.bindTo(this._contextKeyService).set(true)); ServerlessWebContext.bindTo(this._contextKeyService).set(Platform.isWeb && !remoteAgentService.getConnection()?.remoteAuthority); this._configurationResolverService.contributeVariable('defaultBuildTask', async (): Promise => { - let tasks = await this._getTasksForGroup(TaskGroup.Build); + // delay provider activation, we might find a single default build task in the tasks.json file + let tasks = await this._getTasksForGroup(TaskGroup.Build, true); if (tasks.length > 0) { const defaults = this._getDefaultTasks(tasks); if (defaults.length === 1) { return defaults[0]._label; - } else if (defaults.length) { - tasks = defaults; } } + // activate all providers, we haven't found the default build task in the tasks.json file + tasks = await this._getTasksForGroup(TaskGroup.Build); + const defaults = this._getDefaultTasks(tasks); + if (defaults.length === 1) { + return defaults[0]._label; + } else if (defaults.length) { + tasks = defaults; + } let entry: ITaskQuickPickEntry | null | undefined; if (tasks && tasks.length > 0) { @@ -608,6 +617,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer // We need to first wait for extensions to be registered because we might read // the `TaskDefinitionRegistry` in case `type` is `undefined` await this._extensionService.whenInstalledExtensionsRegistered(); + this._log('Activating task providers ' + (type ?? 'all')); await raceTimeout( Promise.all(this._getActivationEvents(type).map(activationEvent => this._extensionService.activateByEvent(activationEvent))), 5000, @@ -672,10 +682,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const handle = AbstractTaskService._nextHandle++; this._providers.set(handle, provider); this._providerTypes.set(handle, type); + this._onDidChangeTaskProviders.fire(); return { dispose: () => { this._providers.delete(handle); this._providerTypes.delete(handle); + this._onDidChangeTaskProviders.fire(); } }; } @@ -870,29 +882,15 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!this._versionAndEngineCompatible(filter)) { return Promise.resolve([]); } - return this._getGroupedTasks(filter).then((map) => { - if (!filter || !filter.type) { - return map.all(); - } - const result: Task[] = []; - map.forEach((tasks) => { - for (const task of tasks) { - if (ContributedTask.is(task) && ((task.defines.type === filter.type) || (task._source.label === filter.type))) { - result.push(task); - } else if (CustomTask.is(task)) { - if (task.type === filter.type) { - result.push(task); - } else { - const customizes = task.customizes(); - if (customizes && customizes.type === filter.type) { - result.push(task); - } - } - } - } - }); - return result; - }); + return this._getGroupedTasks(filter).then((map) => this.applyFilterToTaskMap(filter, map)); + } + + public async getKnownTasks(filter?: ITaskFilter): Promise { + if (!this._versionAndEngineCompatible(filter)) { + return Promise.resolve([]); + } + + return this._getGroupedTasks(filter, true, true).then((map) => this.applyFilterToTaskMap(filter, map)); } public taskTypes(): string[] { @@ -955,6 +953,30 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._recentlyUsedTasksV1; } + private applyFilterToTaskMap(filter: ITaskFilter | undefined, map: TaskMap): Task[] { + if (!filter || !filter.type) { + return map.all(); + } + const result: Task[] = []; + map.forEach((tasks) => { + for (const task of tasks) { + if (ContributedTask.is(task) && ((task.defines.type === filter.type) || (task._source.label === filter.type))) { + result.push(task); + } else if (CustomTask.is(task)) { + if (task.type === filter.type) { + result.push(task); + } else { + const customizes = task.customizes(); + if (customizes && customizes.type === filter.type) { + result.push(task); + } + } + } + } + }); + return result; + } + private _getTasksFromStorage(type: 'persistent' | 'historical'): LRUCache { return type === 'persistent' ? this._getPersistentTasks() : this._getRecentTasks(); } @@ -1418,8 +1440,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return task; } - private async _getTasksForGroup(group: TaskGroup): Promise { - const groups = await this._getGroupedTasks(); + private async _getTasksForGroup(group: TaskGroup, waitToActivate?: boolean): Promise { + const groups = await this._getGroupedTasks(undefined, waitToActivate); const result: Task[] = []; groups.forEach(tasks => { for (const task of tasks) { @@ -2001,11 +2023,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return !definition || !definition.when || this._contextKeyService.contextMatchesRules(definition.when); } - private async _getGroupedTasks(filter?: ITaskFilter): Promise { + private async _getGroupedTasks(filter?: ITaskFilter, waitToActivate?: boolean, knownOnlyOrTrusted?: boolean): Promise { await this._waitForAllSupportedExecutions; const type = filter?.type; const needsRecentTasksMigration = this._needsRecentTasksMigration(); - await this._activateTaskProviders(filter?.type); + if (!waitToActivate) { + await this._activateTaskProviders(filter?.type); + } const validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; @@ -2086,141 +2110,144 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } try { - const customTasks = await this.getWorkspaceTasks(); - const customTasksKeyValuePairs = Array.from(customTasks); - const customTasksPromises = customTasksKeyValuePairs.map(async ([key, folderTasks]) => { - const contributed = contributedTasks.get(key); - if (!folderTasks.set) { - if (contributed) { - result.add(key, ...contributed); + let tasks: [string, IWorkspaceFolderTaskResult][] = []; + // prevent workspace trust dialog from being shown in unexpected cases #224881 + if (!knownOnlyOrTrusted || this._workspaceTrustManagementService.isWorkspaceTrusted()) { + tasks = Array.from(await this.getWorkspaceTasks()); + } + await Promise.all(this._getCustomTaskPromises(tasks, filter, result, contributedTasks, waitToActivate)); + if (needsRecentTasksMigration) { + // At this point we have all the tasks and can migrate the recently used tasks. + await this._migrateRecentTasks(result.all()); + } + return result; + } catch { + // If we can't read the tasks.json file provide at least the contributed tasks + const result: TaskMap = new TaskMap(); + for (const set of contributedTaskSets) { + for (const task of set.tasks) { + const folder = task.getWorkspaceFolder(); + if (folder) { + result.add(folder, task); } - return; } + } + return result; + } + } + private _getCustomTaskPromises(customTasksKeyValuePairs: [string, IWorkspaceFolderTaskResult][], filter: ITaskFilter | undefined, result: TaskMap, contributedTasks: TaskMap, waitToActivate: boolean | undefined) { + return customTasksKeyValuePairs.map(async ([key, folderTasks]) => { + const contributed = contributedTasks.get(key); + if (!folderTasks.set) { + if (contributed) { + result.add(key, ...contributed); + } + return; + } - if (this._contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - result.add(key, ...folderTasks.set.tasks); - } else { - const configurations = folderTasks.configurations; - const legacyTaskConfigurations = folderTasks.set ? this._getLegacyTaskConfigurations(folderTasks.set) : undefined; - const customTasksToDelete: Task[] = []; - if (configurations || legacyTaskConfigurations) { - const unUsedConfigurations: Set = new Set(); - if (configurations) { - Object.keys(configurations.byIdentifier).forEach(key => unUsedConfigurations.add(key)); + if (this._contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + result.add(key, ...folderTasks.set.tasks); + } else { + const configurations = folderTasks.configurations; + const legacyTaskConfigurations = folderTasks.set ? this._getLegacyTaskConfigurations(folderTasks.set) : undefined; + const customTasksToDelete: Task[] = []; + if (configurations || legacyTaskConfigurations) { + const unUsedConfigurations: Set = new Set(); + if (configurations) { + Object.keys(configurations.byIdentifier).forEach(key => unUsedConfigurations.add(key)); + } + for (const task of contributed) { + if (!ContributedTask.is(task)) { + continue; } - for (const task of contributed) { - if (!ContributedTask.is(task)) { - continue; - } - if (configurations) { - const configuringTask = configurations.byIdentifier[task.defines._key]; - if (configuringTask) { - unUsedConfigurations.delete(task.defines._key); - result.add(key, TaskConfig.createCustomTask(task, configuringTask)); - } else { - result.add(key, task); - } - } else if (legacyTaskConfigurations) { - const configuringTask = legacyTaskConfigurations[task.defines._key]; - if (configuringTask) { - result.add(key, TaskConfig.createCustomTask(task, configuringTask)); - customTasksToDelete.push(configuringTask); - } else { - result.add(key, task); - } + if (configurations) { + const configuringTask = configurations.byIdentifier[task.defines._key]; + if (configuringTask) { + unUsedConfigurations.delete(task.defines._key); + result.add(key, TaskConfig.createCustomTask(task, configuringTask)); } else { result.add(key, task); } - } - if (customTasksToDelete.length > 0) { - const toDelete = customTasksToDelete.reduce>((map, task) => { - map[task._id] = true; - return map; - }, Object.create(null)); - for (const task of folderTasks.set.tasks) { - if (toDelete[task._id]) { - continue; - } + } else if (legacyTaskConfigurations) { + const configuringTask = legacyTaskConfigurations[task.defines._key]; + if (configuringTask) { + result.add(key, TaskConfig.createCustomTask(task, configuringTask)); + customTasksToDelete.push(configuringTask); + } else { result.add(key, task); } } else { - result.add(key, ...folderTasks.set.tasks); + result.add(key, task); } + } + if (customTasksToDelete.length > 0) { + const toDelete = customTasksToDelete.reduce>((map, task) => { + map[task._id] = true; + return map; + }, Object.create(null)); + for (const task of folderTasks.set.tasks) { + if (toDelete[task._id]) { + continue; + } + result.add(key, task); + } + } else { + result.add(key, ...folderTasks.set.tasks); + } - const unUsedConfigurationsAsArray = Array.from(unUsedConfigurations); + const unUsedConfigurationsAsArray = Array.from(unUsedConfigurations); - const unUsedConfigurationPromises = unUsedConfigurationsAsArray.map(async (value) => { - const configuringTask = configurations!.byIdentifier[value]; - if (type && (type !== configuringTask.configures.type)) { - return; - } + const unUsedConfigurationPromises = unUsedConfigurationsAsArray.map(async (value) => { + const configuringTask = configurations!.byIdentifier[value]; + if (filter?.type && (filter.type !== configuringTask.configures.type)) { + return; + } - let requiredTaskProviderUnavailable: boolean = false; + let requiredTaskProviderUnavailable: boolean = false; - for (const [handle, provider] of this._providers) { - const providerType = this._providerTypes.get(handle); - if (configuringTask.type === providerType) { - if (providerType && !this._isTaskProviderEnabled(providerType)) { - requiredTaskProviderUnavailable = true; - continue; - } + for (const [handle, provider] of this._providers) { + const providerType = this._providerTypes.get(handle); + if (configuringTask.type === providerType) { + if (providerType && !this._isTaskProviderEnabled(providerType)) { + requiredTaskProviderUnavailable = true; + continue; + } - try { - const resolvedTask = await provider.resolveTask(configuringTask); - if (resolvedTask && (resolvedTask._id === configuringTask._id)) { - result.add(key, TaskConfig.createCustomTask(resolvedTask, configuringTask)); - return; - } - } catch (error) { - // Ignore errors. The task could not be provided by any of the providers. + try { + const resolvedTask = await provider.resolveTask(configuringTask); + if (resolvedTask && (resolvedTask._id === configuringTask._id)) { + result.add(key, TaskConfig.createCustomTask(resolvedTask, configuringTask)); + return; } + } catch (error) { + // Ignore errors. The task could not be provided by any of the providers. } } + } + if (requiredTaskProviderUnavailable) { + this._log(nls.localize( + 'TaskService.providerUnavailable', + 'Warning: {0} tasks are unavailable in the current environment.', + configuringTask.configures.type + )); + } else if (!waitToActivate) { + this._log(nls.localize( + 'TaskService.noConfiguration', + 'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.', + configuringTask.configures.type, + JSON.stringify(configuringTask._source.config.element, undefined, 4) + )); + this._showOutput(); + } + }); - if (requiredTaskProviderUnavailable) { - this._log(nls.localize( - 'TaskService.providerUnavailable', - 'Warning: {0} tasks are unavailable in the current environment.', - configuringTask.configures.type - )); - } else { - this._log(nls.localize( - 'TaskService.noConfiguration', - 'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.', - configuringTask.configures.type, - JSON.stringify(configuringTask._source.config.element, undefined, 4) - )); - this._showOutput(); - } - }); - - await Promise.all(unUsedConfigurationPromises); - } else { - result.add(key, ...folderTasks.set.tasks); - result.add(key, ...contributed); - } - } - }); - - await Promise.all(customTasksPromises); - if (needsRecentTasksMigration) { - // At this point we have all the tasks and can migrate the recently used tasks. - await this._migrateRecentTasks(result.all()); - } - return result; - } catch { - // If we can't read the tasks.json file provide at least the contributed tasks - const result: TaskMap = new TaskMap(); - for (const set of contributedTaskSets) { - for (const task of set.tasks) { - const folder = task.getWorkspaceFolder(); - if (folder) { - result.add(folder, task); - } + await Promise.all(unUsedConfigurationPromises); + } else { + result.add(key, ...folderTasks.set.tasks); + result.add(key, ...contributed); } } - return result; - } + }); } private _getLegacyTaskConfigurations(workspaceTasks: ITaskSet): IStringDictionary | undefined { @@ -2718,34 +2745,22 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer entries.push(additionalEntries[0]); } - const picker: IQuickPick = this._quickInputService.createQuickPick(); - picker.placeholder = placeHolder; - picker.matchOnDescription = true; - if (name) { - picker.value = name; - } - picker.onDidTriggerItemButton(context => { - const task = context.item.task; - this._quickInputService.cancel(); - if (ContributedTask.is(task)) { - this.customize(task, undefined, true); - } else if (CustomTask.is(task)) { - this.openConfig(task); - } - }); - picker.items = entries; - picker.show(); - - return new Promise(resolve => { - this._register(picker.onDidAccept(async () => { - const selectedEntry = picker.selectedItems ? picker.selectedItems[0] : undefined; - picker.dispose(); - if (!selectedEntry) { - resolve(undefined); - } - resolve(selectedEntry); - })); - }); + return this._quickInputService.pick( + entries, + { + value: name, + placeHolder, + matchOnDescription: true, + onDidTriggerItemButton: context => { + const task = context.item.task; + this._quickInputService.cancel(); + if (ContributedTask.is(task)) { + this.customize(task, undefined, true); + } else if (CustomTask.is(task)) { + this.openConfig(task); + } + }, + }); } private _needsRecentTasksMigration(): boolean { @@ -3241,7 +3256,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let configFileCreated = false; this._fileService.stat(resource).then((stat) => stat, () => undefined).then(async (stat) => { const fileExists: boolean = !!stat; - const configValue = this._configurationService.inspect('tasks'); + const configValue = this._configurationService.inspect('tasks', { resource }); let tasksExistInFile: boolean; let target: ConfigurationTarget; switch (taskSource) { diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/patched-vscode/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 08e8292b..88059901 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -23,7 +23,7 @@ import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/se import { ITaskEvent, TaskEventKind, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService, TaskCommandsRegistered, TaskExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -109,7 +109,7 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench } if (promise && (event.kind === TaskEventKind.Active) && (this._activeTasksCount === 1)) { - this._progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks', type: 'loading' }, progress => { + this._progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks' }, progress => { progress.report({ message: nls.localize('building', 'Building...') }); return promise!; }).then(() => { @@ -347,7 +347,7 @@ class UserTasksGlobalActionContribution extends Disposable implements IWorkbench private registerActions() { const id = 'workbench.action.tasks.openUserTasks'; - const title = nls.localize('userTasks', "User Tasks"); + const title = nls.localize('tasks', "Tasks"); this._register(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { command: { id, @@ -431,15 +431,24 @@ schema.oneOf = [...(schemaVersion2.oneOf || []), ...(schemaVersion1.oneOf || []) const jsonRegistry = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); jsonRegistry.registerSchema(tasksSchemaId, schema); -ProblemMatcherRegistry.onMatcherChanged(() => { - updateProblemMatchers(); - jsonRegistry.notifySchemaChanged(tasksSchemaId); -}); +export class TaskRegistryContribution extends Disposable implements IWorkbenchContribution { + static ID = 'taskRegistryContribution'; + constructor() { + super(); + + this._register(ProblemMatcherRegistry.onMatcherChanged(() => { + updateProblemMatchers(); + jsonRegistry.notifySchemaChanged(tasksSchemaId); + })); + + this._register(TaskDefinitionRegistry.onDefinitionsChanged(() => { + updateTaskDefinitions(); + jsonRegistry.notifySchemaChanged(tasksSchemaId); + })); + } +} +registerWorkbenchContribution2(TaskRegistryContribution.ID, TaskRegistryContribution, WorkbenchPhase.AfterRestored); -TaskDefinitionRegistry.onDefinitionsChanged(() => { - updateTaskDefinitions(); - jsonRegistry.notifySchemaChanged(tasksSchemaId); -}); const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/patched-vscode/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index fe0e1641..37ae4fe3 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -11,7 +11,7 @@ import * as Types from 'vs/base/common/types'; import { ITaskService, IWorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; import { IQuickPickItem, QuickPickInput, IQuickPick, IQuickInputButton, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Codicon } from 'vs/base/common/codicons'; @@ -229,11 +229,12 @@ export class TaskQuickPick extends Disposable { } public async show(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, startAtType?: string, name?: string): Promise { - const picker: IQuickPick = this._quickInputService.createQuickPick(); + const disposables = new DisposableStore(); + const picker = disposables.add(this._quickInputService.createQuickPick({ useSeparators: true })); picker.placeholder = placeHolder; picker.matchOnDescription = true; picker.ignoreFocusOut = false; - picker.onDidTriggerItemButton(async (context) => { + disposables.add(picker.onDidTriggerItemButton(async (context) => { const task = context.item.task; if (context.button.iconClass === ThemeIcon.asClassName(removeTaskIcon)) { const key = (task && !Types.isString(task)) ? task.getKey() : undefined; @@ -260,7 +261,7 @@ export class TaskQuickPick extends Disposable { } } } - }); + })); if (name) { picker.value = name; } @@ -269,37 +270,37 @@ export class TaskQuickPick extends Disposable { // First show recent tasks configured tasks. Other tasks will be available at a second level const topLevelEntriesResult = await this.getTopLevelEntries(defaultEntry); if (topLevelEntriesResult.isSingleConfigured && this._configurationService.getValue(QUICKOPEN_SKIP_CONFIG)) { - picker.dispose(); + disposables.dispose(); return this._toTask(topLevelEntriesResult.isSingleConfigured); } const taskQuickPickEntries: QuickPickInput[] = topLevelEntriesResult.entries; - firstLevelTask = await this._doPickerFirstLevel(picker, taskQuickPickEntries); + firstLevelTask = await this._doPickerFirstLevel(picker, taskQuickPickEntries, disposables); } do { if (Types.isString(firstLevelTask)) { if (name) { - await this._doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries); - picker.dispose(); + await this._doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries, disposables); + disposables.dispose(); return undefined; } - const selectedEntry = await this.doPickerSecondLevel(picker, firstLevelTask); + const selectedEntry = await this.doPickerSecondLevel(picker, disposables, firstLevelTask); // Proceed to second level of quick pick if (selectedEntry && !selectedEntry.settingType && selectedEntry.task === null) { // The user has chosen to go back to the first level picker.value = ''; - firstLevelTask = await this._doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries); + firstLevelTask = await this._doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries, disposables); } else if (selectedEntry && Types.isString(selectedEntry.settingType)) { - picker.dispose(); + disposables.dispose(); return this.handleSettingOption(selectedEntry.settingType); } else { - picker.dispose(); + disposables.dispose(); return (selectedEntry?.task && !Types.isString(selectedEntry?.task)) ? this._toTask(selectedEntry?.task) : undefined; } } else if (firstLevelTask) { - picker.dispose(); + disposables.dispose(); return this._toTask(firstLevelTask); } else { - picker.dispose(); + disposables.dispose(); return firstLevelTask; } } while (1); @@ -308,18 +309,18 @@ export class TaskQuickPick extends Disposable { - private async _doPickerFirstLevel(picker: IQuickPick, taskQuickPickEntries: QuickPickInput[]): Promise { + private async _doPickerFirstLevel(picker: IQuickPick, taskQuickPickEntries: QuickPickInput[], disposables: DisposableStore): Promise { picker.items = taskQuickPickEntries; - showWithPinnedItems(this._storageService, runTaskStorageKey, picker, true); + disposables.add(showWithPinnedItems(this._storageService, runTaskStorageKey, picker, true)); const firstLevelPickerResult = await new Promise(resolve => { - Event.once(picker.onDidAccept)(async () => { + disposables.add(Event.once(picker.onDidAccept)(async () => { resolve(picker.selectedItems ? picker.selectedItems[0] : undefined); - }); + })); }); return firstLevelPickerResult?.task; } - public async doPickerSecondLevel(picker: IQuickPick, type: string, name?: string) { + public async doPickerSecondLevel(picker: IQuickPick, disposables: DisposableStore, type: string, name?: string) { picker.busy = true; if (type === SHOW_ALL) { const items = (await this._taskService.tasks()).filter(t => !t.configurationProperties.hide).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task)); @@ -332,9 +333,9 @@ export class TaskQuickPick extends Disposable { await picker.show(); picker.busy = false; const secondLevelPickerResult = await new Promise(resolve => { - Event.once(picker.onDidAccept)(async () => { + disposables.add(Event.once(picker.onDidAccept)(async () => { resolve(picker.selectedItems ? picker.selectedItems[0] : undefined); - }); + })); }); return secondLevelPickerResult; } diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/patched-vscode/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 12ed7950..6919111b 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -562,9 +562,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return { exitCode: 0 }; }); }).finally(() => { - if (this._activeTasks[mapKey] === activeTask) { - delete this._activeTasks[mapKey]; - } + delete this._activeTasks[mapKey]; }); const lastInstance = this._getInstances(task).pop(); const count = lastInstance?.count ?? { count: 0 }; @@ -1046,9 +1044,9 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._viewsService.openView(Markers.MARKERS_VIEW_ID); } else if (task.command.presentation && (task.command.presentation.focus || task.command.presentation.reveal === RevealKind.Always)) { this._terminalService.setActiveInstance(terminal); - await this._terminalService.revealActiveTerminal(); + await this._terminalService.revealTerminal(terminal); if (task.command.presentation.focus) { - this._terminalService.focusActiveInstance(); + this._terminalService.focusInstance(terminal); } } this._activeTasks[task.getMapKey()].terminal = terminal; diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/patched-vscode/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index 0bb6027f..591f2011 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -1195,6 +1195,212 @@ export namespace Schemas { } } }; + + export const WatchingPattern: IJSONSchema = { + type: 'object', + additionalProperties: false, + properties: { + regexp: { + type: 'string', + description: localize('WatchingPatternSchema.regexp', 'The regular expression to detect the begin or end of a background task.') + }, + file: { + type: 'integer', + description: localize('WatchingPatternSchema.file', 'The match group index of the filename. Can be omitted.') + }, + } + }; + + export const PatternType: IJSONSchema = { + anyOf: [ + { + type: 'string', + description: localize('PatternTypeSchema.name', 'The name of a contributed or predefined pattern') + }, + Schemas.ProblemPattern, + Schemas.MultiLineProblemPattern + ], + description: localize('PatternTypeSchema.description', 'A problem pattern or the name of a contributed or predefined problem pattern. Can be omitted if base is specified.') + }; + + export const ProblemMatcher: IJSONSchema = { + type: 'object', + additionalProperties: false, + properties: { + base: { + type: 'string', + description: localize('ProblemMatcherSchema.base', 'The name of a base problem matcher to use.') + }, + owner: { + type: 'string', + description: localize('ProblemMatcherSchema.owner', 'The owner of the problem inside Code. Can be omitted if base is specified. Defaults to \'external\' if omitted and base is not specified.') + }, + source: { + type: 'string', + description: localize('ProblemMatcherSchema.source', 'A human-readable string describing the source of this diagnostic, e.g. \'typescript\' or \'super lint\'.') + }, + severity: { + type: 'string', + enum: ['error', 'warning', 'info'], + description: localize('ProblemMatcherSchema.severity', 'The default severity for captures problems. Is used if the pattern doesn\'t define a match group for severity.') + }, + applyTo: { + type: 'string', + enum: ['allDocuments', 'openDocuments', 'closedDocuments'], + description: localize('ProblemMatcherSchema.applyTo', 'Controls if a problem reported on a text document is applied only to open, closed or all documents.') + }, + pattern: PatternType, + fileLocation: { + oneOf: [ + { + type: 'string', + enum: ['absolute', 'relative', 'autoDetect', 'search'] + }, + { + type: 'array', + prefixItems: [ + { + type: 'string', + enum: ['absolute', 'relative', 'autoDetect', 'search'] + }, + ], + minItems: 1, + maxItems: 1, + additionalItems: false + }, + { + type: 'array', + prefixItems: [ + { type: 'string', enum: ['relative', 'autoDetect'] }, + { type: 'string' }, + ], + minItems: 2, + maxItems: 2, + additionalItems: false, + examples: [ + ['relative', '${workspaceFolder}'], + ['autoDetect', '${workspaceFolder}'], + ] + }, + { + type: 'array', + prefixItems: [ + { type: 'string', enum: ['search'] }, + { + type: 'object', + properties: { + 'include': { + oneOf: [ + { type: 'string' }, + { type: 'array', items: { type: 'string' } } + ] + }, + 'exclude': { + oneOf: [ + { type: 'string' }, + { type: 'array', items: { type: 'string' } } + ] + }, + }, + required: ['include'] + } + ], + minItems: 2, + maxItems: 2, + additionalItems: false, + examples: [ + ['search', { 'include': ['${workspaceFolder}'] }], + ['search', { 'include': ['${workspaceFolder}'], 'exclude': [] }] + ], + } + ], + description: localize('ProblemMatcherSchema.fileLocation', 'Defines how file names reported in a problem pattern should be interpreted. A relative fileLocation may be an array, where the second element of the array is the path of the relative file location. The search fileLocation mode, performs a deep (and, possibly, heavy) file system search within the directories specified by the include/exclude properties of the second element (or the current workspace directory if not specified).') + }, + background: { + type: 'object', + additionalProperties: false, + description: localize('ProblemMatcherSchema.background', 'Patterns to track the begin and end of a matcher active on a background task.'), + properties: { + activeOnStart: { + type: 'boolean', + description: localize('ProblemMatcherSchema.background.activeOnStart', 'If set to true the background monitor is in active mode when the task starts. This is equals of issuing a line that matches the beginsPattern') + }, + beginsPattern: { + oneOf: [ + { + type: 'string' + }, + Schemas.WatchingPattern + ], + description: localize('ProblemMatcherSchema.background.beginsPattern', 'If matched in the output the start of a background task is signaled.') + }, + endsPattern: { + oneOf: [ + { + type: 'string' + }, + Schemas.WatchingPattern + ], + description: localize('ProblemMatcherSchema.background.endsPattern', 'If matched in the output the end of a background task is signaled.') + } + } + }, + watching: { + type: 'object', + additionalProperties: false, + deprecationMessage: localize('ProblemMatcherSchema.watching.deprecated', 'The watching property is deprecated. Use background instead.'), + description: localize('ProblemMatcherSchema.watching', 'Patterns to track the begin and end of a watching matcher.'), + properties: { + activeOnStart: { + type: 'boolean', + description: localize('ProblemMatcherSchema.watching.activeOnStart', 'If set to true the watcher is in active mode when the task starts. This is equals of issuing a line that matches the beginPattern') + }, + beginsPattern: { + oneOf: [ + { + type: 'string' + }, + Schemas.WatchingPattern + ], + description: localize('ProblemMatcherSchema.watching.beginsPattern', 'If matched in the output the start of a watching task is signaled.') + }, + endsPattern: { + oneOf: [ + { + type: 'string' + }, + Schemas.WatchingPattern + ], + description: localize('ProblemMatcherSchema.watching.endsPattern', 'If matched in the output the end of a watching task is signaled.') + } + } + } + } + }; + + export const LegacyProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher); + LegacyProblemMatcher.properties = Objects.deepClone(LegacyProblemMatcher.properties) || {}; + LegacyProblemMatcher.properties['watchedTaskBeginsRegExp'] = { + type: 'string', + deprecationMessage: localize('LegacyProblemMatcherSchema.watchedBegin.deprecated', 'This property is deprecated. Use the watching property instead.'), + description: localize('LegacyProblemMatcherSchema.watchedBegin', 'A regular expression signaling that a watched tasks begins executing triggered through file watching.') + }; + LegacyProblemMatcher.properties['watchedTaskEndsRegExp'] = { + type: 'string', + deprecationMessage: localize('LegacyProblemMatcherSchema.watchedEnd.deprecated', 'This property is deprecated. Use the watching property instead.'), + description: localize('LegacyProblemMatcherSchema.watchedEnd', 'A regular expression signaling that a watched tasks ends executing.') + }; + + export const NamedProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher); + NamedProblemMatcher.properties = Objects.deepClone(NamedProblemMatcher.properties) || {}; + NamedProblemMatcher.properties.name = { + type: 'string', + description: localize('NamedProblemMatcherSchema.name', 'The name of the problem matcher used to refer to it.') + }; + NamedProblemMatcher.properties.label = { + type: 'string', + description: localize('NamedProblemMatcherSchema.label', 'A human readable label of the problem matcher.') + }; } const problemPatternExtPoint = ExtensionsRegistry.registerExtensionPoint({ @@ -1630,216 +1836,6 @@ export class ProblemMatcherParser extends Parser { } } -export namespace Schemas { - - export const WatchingPattern: IJSONSchema = { - type: 'object', - additionalProperties: false, - properties: { - regexp: { - type: 'string', - description: localize('WatchingPatternSchema.regexp', 'The regular expression to detect the begin or end of a background task.') - }, - file: { - type: 'integer', - description: localize('WatchingPatternSchema.file', 'The match group index of the filename. Can be omitted.') - }, - } - }; - - - export const PatternType: IJSONSchema = { - anyOf: [ - { - type: 'string', - description: localize('PatternTypeSchema.name', 'The name of a contributed or predefined pattern') - }, - Schemas.ProblemPattern, - Schemas.MultiLineProblemPattern - ], - description: localize('PatternTypeSchema.description', 'A problem pattern or the name of a contributed or predefined problem pattern. Can be omitted if base is specified.') - }; - - export const ProblemMatcher: IJSONSchema = { - type: 'object', - additionalProperties: false, - properties: { - base: { - type: 'string', - description: localize('ProblemMatcherSchema.base', 'The name of a base problem matcher to use.') - }, - owner: { - type: 'string', - description: localize('ProblemMatcherSchema.owner', 'The owner of the problem inside Code. Can be omitted if base is specified. Defaults to \'external\' if omitted and base is not specified.') - }, - source: { - type: 'string', - description: localize('ProblemMatcherSchema.source', 'A human-readable string describing the source of this diagnostic, e.g. \'typescript\' or \'super lint\'.') - }, - severity: { - type: 'string', - enum: ['error', 'warning', 'info'], - description: localize('ProblemMatcherSchema.severity', 'The default severity for captures problems. Is used if the pattern doesn\'t define a match group for severity.') - }, - applyTo: { - type: 'string', - enum: ['allDocuments', 'openDocuments', 'closedDocuments'], - description: localize('ProblemMatcherSchema.applyTo', 'Controls if a problem reported on a text document is applied only to open, closed or all documents.') - }, - pattern: PatternType, - fileLocation: { - oneOf: [ - { - type: 'string', - enum: ['absolute', 'relative', 'autoDetect', 'search'] - }, - { - type: 'array', - prefixItems: [ - { - type: 'string', - enum: ['absolute', 'relative', 'autoDetect', 'search'] - }, - ], - minItems: 1, - maxItems: 1, - additionalItems: false - }, - { - type: 'array', - prefixItems: [ - { type: 'string', enum: ['relative', 'autoDetect'] }, - { type: 'string' }, - ], - minItems: 2, - maxItems: 2, - additionalItems: false, - examples: [ - ['relative', '${workspaceFolder}'], - ['autoDetect', '${workspaceFolder}'], - ] - }, - { - type: 'array', - prefixItems: [ - { type: 'string', enum: ['search'] }, - { - type: 'object', - properties: { - 'include': { - oneOf: [ - { type: 'string' }, - { type: 'array', items: { type: 'string' } } - ] - }, - 'exclude': { - oneOf: [ - { type: 'string' }, - { type: 'array', items: { type: 'string' } } - ] - }, - }, - required: ['include'] - } - ], - minItems: 2, - maxItems: 2, - additionalItems: false, - examples: [ - ['search', { 'include': ['${workspaceFolder}'] }], - ['search', { 'include': ['${workspaceFolder}'], 'exclude': [] }] - ], - } - ], - description: localize('ProblemMatcherSchema.fileLocation', 'Defines how file names reported in a problem pattern should be interpreted. A relative fileLocation may be an array, where the second element of the array is the path of the relative file location. The search fileLocation mode, performs a deep (and, possibly, heavy) file system search within the directories specified by the include/exclude properties of the second element (or the current workspace directory if not specified).') - }, - background: { - type: 'object', - additionalProperties: false, - description: localize('ProblemMatcherSchema.background', 'Patterns to track the begin and end of a matcher active on a background task.'), - properties: { - activeOnStart: { - type: 'boolean', - description: localize('ProblemMatcherSchema.background.activeOnStart', 'If set to true the background monitor is in active mode when the task starts. This is equals of issuing a line that matches the beginsPattern') - }, - beginsPattern: { - oneOf: [ - { - type: 'string' - }, - Schemas.WatchingPattern - ], - description: localize('ProblemMatcherSchema.background.beginsPattern', 'If matched in the output the start of a background task is signaled.') - }, - endsPattern: { - oneOf: [ - { - type: 'string' - }, - Schemas.WatchingPattern - ], - description: localize('ProblemMatcherSchema.background.endsPattern', 'If matched in the output the end of a background task is signaled.') - } - } - }, - watching: { - type: 'object', - additionalProperties: false, - deprecationMessage: localize('ProblemMatcherSchema.watching.deprecated', 'The watching property is deprecated. Use background instead.'), - description: localize('ProblemMatcherSchema.watching', 'Patterns to track the begin and end of a watching matcher.'), - properties: { - activeOnStart: { - type: 'boolean', - description: localize('ProblemMatcherSchema.watching.activeOnStart', 'If set to true the watcher is in active mode when the task starts. This is equals of issuing a line that matches the beginPattern') - }, - beginsPattern: { - oneOf: [ - { - type: 'string' - }, - Schemas.WatchingPattern - ], - description: localize('ProblemMatcherSchema.watching.beginsPattern', 'If matched in the output the start of a watching task is signaled.') - }, - endsPattern: { - oneOf: [ - { - type: 'string' - }, - Schemas.WatchingPattern - ], - description: localize('ProblemMatcherSchema.watching.endsPattern', 'If matched in the output the end of a watching task is signaled.') - } - } - } - } - }; - - export const LegacyProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher); - LegacyProblemMatcher.properties = Objects.deepClone(LegacyProblemMatcher.properties) || {}; - LegacyProblemMatcher.properties['watchedTaskBeginsRegExp'] = { - type: 'string', - deprecationMessage: localize('LegacyProblemMatcherSchema.watchedBegin.deprecated', 'This property is deprecated. Use the watching property instead.'), - description: localize('LegacyProblemMatcherSchema.watchedBegin', 'A regular expression signaling that a watched tasks begins executing triggered through file watching.') - }; - LegacyProblemMatcher.properties['watchedTaskEndsRegExp'] = { - type: 'string', - deprecationMessage: localize('LegacyProblemMatcherSchema.watchedEnd.deprecated', 'This property is deprecated. Use the watching property instead.'), - description: localize('LegacyProblemMatcherSchema.watchedEnd', 'A regular expression signaling that a watched tasks ends executing.') - }; - - export const NamedProblemMatcher: IJSONSchema = Objects.deepClone(ProblemMatcher); - NamedProblemMatcher.properties = Objects.deepClone(NamedProblemMatcher.properties) || {}; - NamedProblemMatcher.properties.name = { - type: 'string', - description: localize('NamedProblemMatcherSchema.name', 'The name of the problem matcher used to refer to it.') - }; - NamedProblemMatcher.properties.label = { - type: 'string', - description: localize('NamedProblemMatcherSchema.label', 'A human readable label of the problem matcher.') - }; -} - const problemMatchersExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'problemMatchers', deps: [problemPatternExtPoint], diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/patched-vscode/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 0de3b0a3..6670671e 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -133,6 +133,7 @@ export interface IPresentationOptionsConfig { /** * Controls whether the terminal that the task runs in is closed when the task completes. + * Note that if the terminal process exits with a non-zero exit code, it will not close. */ close?: boolean; } diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/common/taskService.ts b/patched-vscode/src/vs/workbench/contrib/tasks/common/taskService.ts index 3db39c22..dab0931a 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -64,6 +64,8 @@ export interface IWorkspaceFolderTaskResult extends IWorkspaceTaskResult { export interface ITaskService { readonly _serviceBrand: undefined; onDidStateChange: Event; + /** Fired when task providers are registered or unregistered */ + onDidChangeTaskProviders: Event; isReconnected: boolean; onDidReconnectToTasks: Event; supportsMultipleTaskExecutions: boolean; @@ -75,6 +77,11 @@ export interface ITaskService { getBusyTasks(): Promise; terminate(task: Task): Promise; tasks(filter?: ITaskFilter): Promise; + /** + * Gets tasks currently known to the task system. Unlike {@link tasks}, + * this does not activate extensions or prompt for workspace trust. + */ + getKnownTasks(filter?: ITaskFilter): Promise; taskTypes(): string[]; getWorkspaceTasks(runSource?: TaskRunSource): Promise>; getSavedTasks(type: 'persistent' | 'historical'): Promise<(Task | ConfiguringTask)[]>; diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts b/patched-vscode/src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts index 6b41d88c..eba74599 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as matchers from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import * as assert from 'assert'; +import assert from 'assert'; import { ValidationState, IProblemReporter, ValidationStatus } from 'vs/base/common/parsers'; class ProblemReporter implements IProblemReporter { diff --git a/patched-vscode/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts b/patched-vscode/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts index 816894f6..7aad1e55 100644 --- a/patched-vscode/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts +++ b/patched-vscode/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as assert from 'assert'; +import assert from 'assert'; import Severity from 'vs/base/common/severity'; import * as UUID from 'vs/base/common/uuid'; diff --git a/patched-vscode/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/patched-vscode/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index c397b896..65b23014 100644 --- a/patched-vscode/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -31,7 +31,7 @@ import { mainWindow } from 'vs/base/browser/window'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { isBoolean, isNumber, isString } from 'vs/base/common/types'; import { LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; -import { AutoUpdateConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; +import { AutoRestartConfigurationKey, AutoUpdateConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -431,13 +431,22 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc ? 'default' : 'custom'; this.telemetryService.publicLog2('window.systemColorTheme', { settingValue, source }); + }>('window.newWindowProfile', { settingValue, source }); return; } + + case AutoRestartConfigurationKey: + this.telemetryService.publicLog2('extensions.autoRestart', { settingValue: this.getValueToReport(key, target), source }); + return; } } diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts b/patched-vscode/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts index e355b287..606202e8 100644 --- a/patched-vscode/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts @@ -39,7 +39,7 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo { private _getActions(): ITerminalStatusHoverAction[] { return [{ - label: localize('relaunchTerminalLabel', "Relaunch terminal"), + label: localize('relaunchTerminalLabel', "Relaunch Terminal"), run: () => this._terminalService.getInstanceFromId(this._terminalId)?.relaunch(), commandId: TerminalCommandId.Relaunch }]; @@ -77,7 +77,7 @@ export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariabl private _getActions(scope: EnvironmentVariableScope | undefined): ITerminalStatusHoverAction[] { return [{ - label: localize('showEnvironmentContributions', "Show environment contributions"), + label: localize('showEnvironmentContributions', "Show Environment Contributions"), run: () => this._commandService.executeCommand(TerminalCommandId.ShowEnvironmentContributions, scope), commandId: TerminalCommandId.ShowEnvironmentContributions }]; diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/CodeTabExpansion.psm1 b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/CodeTabExpansion.psm1 new file mode 100644 index 00000000..559c9d3c --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/CodeTabExpansion.psm1 @@ -0,0 +1,62 @@ +# --------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# --------------------------------------------------------------------------------------------- + +# TODO: Dynamically enable depending on quality? +Microsoft.PowerShell.Core\Register-ArgumentCompleter -CommandName "code","code-insiders" -Native -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + # TODO: These tooltips could be localized? + # TODO: Context aware suggestions + # TODO: Auto-generate this + # TODO: Subcommands + @( + [System.Management.Automation.CompletionResult]::new("--add", "--add", 'ParameterName', 'Add folder(s) to the last active window.'), + [System.Management.Automation.CompletionResult]::new("--category", "--category", 'ParameterName', 'Filters installed extensions by provided category, when using --list-extensions.'), + [System.Management.Automation.CompletionResult]::new("--diff", "--diff", 'ParameterName', 'Compare two files with each other.'), + [System.Management.Automation.CompletionResult]::new("--disable-chromium-sandbox", "--disable-chromium-sandbox", 'ParameterName', 'Use this option only when there is requirement to launch the application as sudo user on Linux or when running as an elevated user in an applocker environment on Windows.'), + [System.Management.Automation.CompletionResult]::new("--disable-extension", "--disable-extension", 'ParameterName', 'Disable the provided extension. This option is not persisted and is effective only when the command opens a new window.'), + [System.Management.Automation.CompletionResult]::new("--disable-extensions", "--disable-extensions", 'ParameterName', 'Disable all installed extensions. This option is not persisted and is effective only when the command opens a new window.'), + [System.Management.Automation.CompletionResult]::new("--disable-gpu", "--disable-gpu", 'ParameterName', 'Disable GPU hardware acceleration.'), + [System.Management.Automation.CompletionResult]::new("--disable-lcd-text", "--disable-lcd-text", 'ParameterName', 'Disable LCD font rendering.'), + [System.Management.Automation.CompletionResult]::new("--enable-proposed-api", "--enable-proposed-api", 'ParameterName', 'Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.'), + [System.Management.Automation.CompletionResult]::new("--extensions-dir", "--extensions-dir", 'ParameterName', 'Set the root path for extensions.'), + [System.Management.Automation.CompletionResult]::new("--goto", "--goto", 'ParameterName', 'Open a file at the path on the specified line and character position.'), + [System.Management.Automation.CompletionResult]::new("--help", "--help", 'ParameterName', 'Print usage.'), + [System.Management.Automation.CompletionResult]::new("--inspect-brk-extensions", "--inspect-brk-extensions", 'ParameterName', 'Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.'), + [System.Management.Automation.CompletionResult]::new("--inspect-extensions", "--inspect-extensions", 'ParameterName', 'Allow debugging and profiling of extensions. Check the developer tools for the connection URI.'), + [System.Management.Automation.CompletionResult]::new("--install-extension", "--install-extension", 'ParameterName', 'Installs or updates an extension. The argument is either an extension id or a path to a VSIX. The identifier of an extension is ''${publisher}.${name}''. Use ''--force'' argument to update to latest version. To install a specific version provide ''@${version}''. For example: ''vscode.csharp@1.2.3''.'), + [System.Management.Automation.CompletionResult]::new("--list-extensions", "--list-extensions", 'ParameterName', 'List the installed extensions.'), + [System.Management.Automation.CompletionResult]::new("--locale", "--locale", 'ParameterName', 'The locale to use (e.g. en-US or zh-TW).'), + [System.Management.Automation.CompletionResult]::new("--log", "--log", 'ParameterName', 'Log level to use. Default is ''info''. Allowed values are ''critical'', ''error'', ''warn'', ''info'', ''debug'', ''trace'', ''off''. You can also configure the log level of an extension by passing extension id and log level in the following format:\n\n ''${publisher}.${name}:${logLevel}''. For example: ''vscode.csharp:trace''. Can receive one or more such entries.'), + [System.Management.Automation.CompletionResult]::new("--merge", "--merge", 'ParameterName', 'Perform a three-way merge by providing paths for two modified versions of a file, the common origin of both modified versions and the output file to save merge results.'), + [System.Management.Automation.CompletionResult]::new("--new-window", "--new-window", 'ParameterName', 'Force to open a new window.'), + [System.Management.Automation.CompletionResult]::new("--pre-release", "--pre-release", 'ParameterName', 'Installs the pre-release version of the extension, when using'), + [System.Management.Automation.CompletionResult]::new("--prof-startup", "--prof-startup", 'ParameterName', 'Run CPU profiler during startup.'), + [System.Management.Automation.CompletionResult]::new("--profile", "--profile", 'ParameterName', 'Opens the provided folder or workspace with the given profile and associates the profile with the workspace. If the profile does not exist, a new empty one is created.'), + [System.Management.Automation.CompletionResult]::new("--reuse-window", "--reuse-window", 'ParameterName', 'Force to open a file or folder in an already opened window.'), + [System.Management.Automation.CompletionResult]::new("--show-versions", "--show-versions", 'ParameterName', 'Show versions of installed extensions, when using --list-extensions.'), + [System.Management.Automation.CompletionResult]::new("--status", "--status", 'ParameterName', 'Print process usage and diagnostics information.'), + [System.Management.Automation.CompletionResult]::new("--sync", "--sync", 'ParameterName', 'Turn sync on or off.'), + [System.Management.Automation.CompletionResult]::new("--telemetry", "--telemetry", 'ParameterName', 'Shows all telemetry events which VS code collects.'), + [System.Management.Automation.CompletionResult]::new("--uninstall-extension", "--uninstall-extension", 'ParameterName', 'Uninstalls an extension.'), + [System.Management.Automation.CompletionResult]::new("--update-extensions", "--update-extensions", 'ParameterName', 'Update the installed extensions.'), + [System.Management.Automation.CompletionResult]::new("--user-data-dir", "--user-data-dir", 'ParameterName', 'Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.'), + [System.Management.Automation.CompletionResult]::new("--verbose", "--verbose", 'ParameterName', 'Print verbose output (implies --wait).'), + [System.Management.Automation.CompletionResult]::new("--version", "--version", 'ParameterName', 'Print version.'), + [System.Management.Automation.CompletionResult]::new("--wait", "--wait", 'ParameterName', 'Wait for the files to be closed before returning.'), + [System.Management.Automation.CompletionResult]::new("-a", "-a", 'ParameterName', 'Add folder(s) to the last active window.'), + [System.Management.Automation.CompletionResult]::new("-d", "-d", 'ParameterName', 'Compare two files with each other.'), + [System.Management.Automation.CompletionResult]::new("-g", "-g", 'ParameterName', 'Open a file at the path on the specified line and character position.'), + [System.Management.Automation.CompletionResult]::new("-h", "-h", 'ParameterName', 'Print usage.'), + [System.Management.Automation.CompletionResult]::new("-m", "-m", 'ParameterName', 'Perform a three-way merge by providing paths for two modified versions of a file, the common origin of both modified versions and the output file to save merge results.'), + [System.Management.Automation.CompletionResult]::new("-n", "-n", 'ParameterName', 'Force to open a new window.'), + [System.Management.Automation.CompletionResult]::new("-r", "-r", 'ParameterName', 'Force to open a file or folder in an already opened window.'), + [System.Management.Automation.CompletionResult]::new("-s", "-s", 'ParameterName', 'Print process usage and diagnostics information.'), + [System.Management.Automation.CompletionResult]::new("-v", "-v", 'ParameterName', 'Print version.'), + [System.Management.Automation.CompletionResult]::new("-w", "-w", 'ParameterName', 'Wait for the files to be closed before returning.'), + [System.Management.Automation.CompletionResult]::new("serve-web", "serve-web", 'Command', 'Run a server that displays the editor UI in browsers.') + [System.Management.Automation.CompletionResult]::new("tunnel", "tunnel", 'Command', 'Make the current machine accessible from vscode.dev or other machines through a secure tunnel') + ) +} diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/GitTabExpansion.psm1 b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/GitTabExpansion.psm1 new file mode 100644 index 00000000..ad61806a --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/GitTabExpansion.psm1 @@ -0,0 +1,666 @@ +# --------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# --------------------------------------------------------------------------------------------- + +# This is a fork of posh-git that has been modified to add additional features and custom VS Code +# specific integrations. +# +# Copyright (c) 2010-2018 Keith Dahlby, Keith Hill, and contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +function dbg($Message, [Diagnostics.Stopwatch]$Stopwatch) { + if ($Stopwatch) { + Write-Verbose ('{0:00000}:{1}' -f $Stopwatch.ElapsedMilliseconds,$Message) -Verbose # -ForegroundColor Yellow + } +} + +function Get-AliasPattern($cmd) { + $aliases = @($cmd) + @(Get-Alias | Where-Object { $_.Definition -match "^$cmd(\.exe)?$" } | Foreach-Object Name) + "($($aliases -join '|'))" +} + +$Global:GitTabSettings = New-Object PSObject -Property @{ + AllCommands = $false + KnownAliases = @{ + '!f() { exec vsts code pr "$@"; }; f' = 'vsts.pr' + } + EnableLogging = $false + LogPath = Join-Path ([System.IO.Path]::GetTempPath()) posh-git_tabexp.log + RegisteredCommands = "" +} + +$subcommands = @{ + bisect = "start bad good skip reset visualize replay log run" + notes = 'add append copy edit get-ref list merge prune remove show' + 'vsts.pr' = 'create update show list complete abandon reactivate reviewers work-items set-vote policies' + reflog = "show delete expire" + remote = " + add rename remove set-head set-branches + get-url set-url show prune update + " + rerere = "clear forget diff remaining status gc" + stash = 'push save list show apply clear drop pop create branch' + submodule = "add status init deinit update summary foreach sync" + svn = " + init fetch clone rebase dcommit log find-rev + set-tree commit-diff info create-ignore propget + proplist show-ignore show-externals branch tag blame + migrate mkdirs reset gc + " + tfs = " + list-remote-branches clone quick-clone bootstrap init + clone fetch pull quick-clone unshelve shelve-list labels + rcheckin checkin checkintool shelve shelve-delete + branch + info cleanup cleanup-workspaces help verify autotag subtree reset-remote checkout + " + flow = "init feature bugfix release hotfix support help version" + worktree = "add list lock move prune remove unlock" +} + +$gitflowsubcommands = @{ + init = 'help' + feature = 'list start finish publish track diff rebase checkout pull help delete' + bugfix = 'list start finish publish track diff rebase checkout pull help delete' + release = 'list start finish track publish help delete' + hotfix = 'list start finish track publish help delete' + support = 'list start help' + config = 'list set base' +} + +function script:gitCmdOperations($commands, $command, $filter) { + $commands[$command].Trim() -split '\s+' | Where-Object { $_ -like "$filter*" } +} + +$script:someCommands = @( + 'add','am','annotate','archive','bisect','blame','branch','bundle','checkout','cherry', + 'cherry-pick','citool','clean','clone','commit','config','describe','diff','difftool','fetch', + 'format-patch','gc','grep','gui','help','init','instaweb','log','merge','mergetool','mv', + 'notes','prune','pull','push','rebase','reflog','remote','rerere','reset','restore','revert','rm', + 'shortlog','show','stash','status','submodule','svn','switch','tag','whatchanged', 'worktree' +) + +# Based on git help -a output +$script:someCommandsDescriptions = @{ + # Main Porcelain Commands" + add = "Add file contents to the index" + am = "Apply a series of patches from a mailbox" + archive = "Create an archive of files from a named tree" + bisect = "Use binary search to find the commit that introduced a bug" + branch = "List, create, or delete branches" + bundle = "Move objects and refs by archive" + checkout = "Switch branches or restore working tree files" + "cherry-pick" = "Apply the changes introduced by some existing commits" + citool = "Graphical alternative to git-commit" + clean = "Remove untracked files from the working tree" + clone = "Clone a repository into a new directory" + commit = "Record changes to the repository" + describe = "Give an object a human readable name based on an available ref" + diff = "Show changes between commits, commit and working tree, etc" + fetch = "Download objects and refs from another repository" + "format-patch" = "Prepare patches for e-mail submission" + gc = "Cleanup unnecessary files and optimize the local repository" + grep = "Print lines matching a pattern" + gui = "A portable graphical interface to Git" + init = "Create an empty Git repository or reinitialize an existing one" + log = "Show commit logs" + merge = "Join two or more development histories together" + mv = "Move or rename a file, a directory, or a symlink" + notes = "Add or inspect object notes" + pull = "Fetch from and integrate with another repository or a local branch" + push = "Update remote refs along with associated objects" + rebase = "Reapply commits on top of another base tip" + reset = "Reset current HEAD to the specified state" + restore = "Restore working tree files" + revert = "Revert some existing commits" + rm = "Remove files from the working tree and from the index" + shortlog = "Summarize 'git log' output" + show = "Show various types of objects" + stash = "Stash the changes in a dirty working directory away" + status = "Show the working tree status" + submodule = "Initialize, update or inspect submodules" + switch = "Switch branches" + tag = "Create, list, delete or verify a tag object signed with GPG" + worktree = "Manage multiple working trees" + + # Ancillary Commands / Manipulators + config = "Get and set repository or global options" + mergetool = "Run merge conflict resolution tools to resolve merge conflicts" + "pack-refs" = "Pack heads and tags for efficient repository access" + prune = "Prune all unreachable objects from the object database" + reflog = "Manage reflog information" + remote = "Manage set of tracked repositories" + + # Ancillary Commands / Interrogators + annotate = "Annotate file lines with commit information" + blame = "Show what revision and author last modified each line of a file" + difftool = "Show changes using common diff tools" + help = "Display help information about Git" + rerere = "Reuse recorded resolution of conflicted merges" + whatchanged = "Show logs with differences each commit introduces" + + # Low-level Commands / Interrogators + cherry = "Find commits yet to be applied to upstream" +} + + + +if ((($PSVersionTable.PSVersion.Major -eq 5) -or $IsWindows) -and ($script:GitVersion -ge [System.Version]'2.16.2')) { + $script:someCommands += 'update-git-for-windows' +} + +$script:gitCommandsWithLongParams = $longGitParams.Keys -join '|' +$script:gitCommandsWithShortParams = $shortGitParams.Keys -join '|' +$script:gitCommandsWithParamValues = $gitParamValues.Keys -join '|' +$script:vstsCommandsWithShortParams = $shortVstsParams.Keys -join '|' +$script:vstsCommandsWithLongParams = $longVstsParams.Keys -join '|' + +try { + if ($null -ne (git help -a 2>&1 | Select-String flow)) { + $script:someCommands += 'flow' + } +} +catch { + Write-Debug "Search for 'flow' in 'git help' output failed with error: $_" +} + +filter quoteStringWithSpecialChars { + if ($_ -and ($_ -match '\s+|#|@|\$|;|,|''|\{|\}|\(|\)')) { + $str = $_ -replace "'", "''" + "'$str'" + } + else { + $_ + } +} + +function script:gitCommands($filter, $includeAliases) { + $cmdList = @() + if (-not $global:GitTabSettings.AllCommands) { + $cmdList += $someCommands -like "$filter*" + } + else { + $cmdList += git help --all | + Where-Object { $_ -match '^\s{2,}\S.*' } | + ForEach-Object { $_.Split(' ', [StringSplitOptions]::RemoveEmptyEntries) } | + Where-Object { $_ -like "$filter*" } + } + + $completions = $cmdList | Sort-Object | ForEach-Object { + $command = $_ + if ($script:someCommandsDescriptions.ContainsKey($command)) { + [System.Management.Automation.CompletionResult]::new($command, $command, 'Method', $script:someCommandsDescriptions[$command]) + } else { + [System.Management.Automation.CompletionResult]::new($command, $command, 'Method', $command) + } + } + + if ($includeAliases) { + $completions += gitAliases $filter + } + + $completions +} + +function script:gitRemotes($filter) { + git remote | + Where-Object { $_ -like "$filter*" } | + quoteStringWithSpecialChars +} + +function script:gitBranches($filter, $includeHEAD = $false, $prefix = '') { + if ($filter -match "^(?\S*\.{2,3})(?.*)") { + $prefix += $matches['from'] + $filter = $matches['to'] + } + + $branches = @(git branch --no-color | ForEach-Object { if (($_ -notmatch "^\* \(HEAD detached .+\)$") -and ($_ -match "^[\*\+]?\s*(?\S+)(?: -> .+)?")) { $matches['ref'] } }) + + @(git branch --no-color -r | ForEach-Object { if ($_ -match "^ (?\S+)(?: -> .+)?") { $matches['ref'] } }) + + @(if ($includeHEAD) { 'HEAD','FETCH_HEAD','ORIG_HEAD','MERGE_HEAD' }) + + $branches | + Where-Object { $_ -ne '(no branch)' -and $_ -like "$filter*" } | + ForEach-Object { $prefix + $_ } | + quoteStringWithSpecialChars +} + +function script:gitRemoteUniqueBranches($filter) { + git branch --no-color -r | + ForEach-Object { if ($_ -match "^ (?[^/]+)/(?\S+)(?! -> .+)?$") { $matches['branch'] } } | + Group-Object -NoElement | + Where-Object { $_.Count -eq 1 } | + Select-Object -ExpandProperty Name | + Where-Object { $_ -like "$filter*" } | + quoteStringWithSpecialChars +} + +function script:gitConfigKeys($section, $filter, $defaultOptions = '') { + $completions = @($defaultOptions -split ' ') + + git config --name-only --get-regexp ^$section\..* | + ForEach-Object { $completions += ($_ -replace "$section\.","") } + + return $completions | + Where-Object { $_ -like "$filter*" } | + Sort-Object | + quoteStringWithSpecialChars +} + +function script:gitTags($filter, $prefix = '') { + git tag | + Where-Object { $_ -like "$filter*" } | + ForEach-Object { $prefix + $_ } | + quoteStringWithSpecialChars +} + +function script:gitFeatures($filter, $command) { + $featurePrefix = git config --local --get "gitflow.prefix.$command" + $branches = @(git branch --no-color | ForEach-Object { if ($_ -match "^\*?\s*$featurePrefix(?.*)") { $matches['ref'] } }) + $branches | + Where-Object { $_ -ne '(no branch)' -and $_ -like "$filter*" } | + ForEach-Object { $featurePrefix + $_ } | + quoteStringWithSpecialChars +} + +function script:gitRemoteBranches($remote, $ref, $filter, $prefix = '') { + git branch --no-color -r | + Where-Object { $_ -like " $remote/$filter*" } | + ForEach-Object { $prefix + $ref + ($_ -replace " $remote/","") } | + quoteStringWithSpecialChars +} + +function script:gitStashes($filter) { + (git stash list) -replace ':.*','' | + Where-Object { $_ -like "$filter*" } | + quoteStringWithSpecialChars +} + +function script:gitTfsShelvesets($filter) { + (git tfs shelve-list) | + Where-Object { $_ -like "$filter*" } | + quoteStringWithSpecialChars +} + +function script:gitFiles($filter, $files) { + $files | Sort-Object | + Where-Object { $_ -like "$filter*" } | + quoteStringWithSpecialChars +} + +function script:gitIndex($GitStatus, $filter) { + gitFiles $filter $GitStatus.Index +} + +function script:gitAddFiles($GitStatus, $filter) { + gitFiles $filter (@($GitStatus.Working.Unmerged) + @($GitStatus.Working.Modified) + @($GitStatus.Working.Added)) +} + +function script:gitCheckoutFiles($GitStatus, $filter) { + gitFiles $filter (@($GitStatus.Working.Unmerged) + @($GitStatus.Working.Modified) + @($GitStatus.Working.Deleted)) +} + +function script:gitDeleted($GitStatus, $filter) { + gitFiles $filter $GitStatus.Working.Deleted +} + +function script:gitDiffFiles($GitStatus, $filter, $staged) { + if ($staged) { + gitFiles $filter $GitStatus.Index.Modified + } + else { + gitFiles $filter (@($GitStatus.Working.Unmerged) + @($GitStatus.Working.Modified) + @($GitStatus.Index.Modified)) + } +} + +function script:gitMergeFiles($GitStatus, $filter) { + gitFiles $filter $GitStatus.Working.Unmerged +} + +function script:gitRestoreFiles($GitStatus, $filter, $staged) { + if ($staged) { + gitFiles $filter (@($GitStatus.Index.Added) + @($GitStatus.Index.Modified) + @($GitStatus.Index.Deleted)) + } + else { + gitFiles $filter (@($GitStatus.Working.Unmerged) + @($GitStatus.Working.Modified) + @($GitStatus.Working.Deleted)) + } +} + +function script:gitAliases($filter) { + git config --get-regexp ^alias\. | ForEach-Object { + if ($_ -match "^alias\.(?\S+) (?.+)") { + $alias = $Matches['alias'] + $expanded = $Matches['expanded'] + if ($alias -like "$filter*") { + [System.Management.Automation.CompletionResult]::new($alias, $alias, 'Variable', $expanded) + } + } + } +} + +function script:expandGitAlias($cmd, $rest) { + $alias = git config "alias.$cmd" + + if ($alias) { + $known = $Global:GitTabSettings.KnownAliases[$alias] + if ($known) { + return "git $known$rest" + } + + return "git $alias$rest" + } + else { + return "git $cmd$rest" + } +} + +function script:expandLongParams($hash, $cmd, $filter) { + $hash[$cmd].Trim() -split ' ' | + Where-Object { $_ -like "$filter*" } | + Sort-Object | + ForEach-Object { -join ("--", $_) } +} + +function script:expandShortParams($hash, $cmd, $filter) { + $hash[$cmd].Trim() -split ' ' | + Where-Object { $_ -like "$filter*" } | + Sort-Object | + ForEach-Object { -join ("-", $_) } +} + +function script:expandParamValues($cmd, $param, $filter) { + $paramValues = $gitParamValues[$cmd][$param] + + $completions = if ($paramValues -is [scriptblock]) { + & $paramValues $filter + } + else { + $paramValues.Trim() -split ' ' | Where-Object { $_ -like "$filter*" } | Sort-Object + } + + $completions | ForEach-Object { -join ("--", $param, "=", $_) } +} + +function Expand-GitCommand($Command) { + # Parse all Git output as UTF8, including tab completion output - https://github.com/dahlbyk/posh-git/pull/359 + $res = GitTabExpansionInternal $Command $Global:GitStatus + $res +} + +function GitTabExpansionInternal($lastBlock, $GitStatus = $null) { + $ignoreGitParams = '(?\s+-(?:[aA-zZ0-9]+|-[aA-zZ0-9][aA-zZ0-9-]*)(?:=\S+)?)*' + + if ($lastBlock -match "^$(Get-AliasPattern git) (?\S+)(? .*)$") { + $lastBlock = expandGitAlias $Matches['cmd'] $Matches['args'] + } + + # Handles tgit (tortoisegit) + if ($lastBlock -match "^$(Get-AliasPattern tgit) (?\S*)$") { + # Need return statement to prevent fall-through. + return $Global:TortoiseGitSettings.TortoiseGitCommands.Keys.GetEnumerator() | Sort-Object | Where-Object { $_ -like "$($matches['cmd'])*" } + } + + # Handles gitk + if ($lastBlock -match "^$(Get-AliasPattern gitk).* (?\S*)$") { + return gitBranches $matches['ref'] $true + } + + switch -regex ($lastBlock -replace "^$(Get-AliasPattern git) ","") { + + # Handles git + "^(?$($subcommands.Keys -join '|'))\s+(?\S*)$" { + gitCmdOperations $subcommands $matches['cmd'] $matches['op'] + } + + # Handles git flow + "^flow (?$($gitflowsubcommands.Keys -join '|'))\s+(?\S*)$" { + gitCmdOperations $gitflowsubcommands $matches['cmd'] $matches['op'] + } + + # Handles git flow + "^flow (?\S*)\s+(?\S*)\s+(?\S*)$" { + gitFeatures $matches['name'] $matches['command'] + } + + # Handles git remote (rename|rm|remove|set-head|set-branches|set-url|show|prune) + "^remote.* (?:rename|rm|remove|set-head|set-branches|set-url|show|prune).* (?\S*)$" { + gitRemotes $matches['remote'] | ConvertTo-VscodeCompletion -Type 'remote' + } + + # Handles git stash (show|apply|drop|pop|branch) + "^stash (?:show|apply|drop|pop|branch).* (?\S*)$" { + gitStashes $matches['stash'] | ConvertTo-VscodeCompletion -Type 'stash' + } + + # Handles git bisect (bad|good|reset|skip) + "^bisect (?:bad|good|reset|skip).* (?\S*)$" { + gitBranches $matches['ref'] $true | ConvertTo-VscodeCompletion -Type 'branch' + } + + # Handles git tfs unshelve + "^tfs +unshelve.* (?\S*)$" { + gitTfsShelvesets $matches['shelveset'] + } + + # Handles git branch -d|-D|-m|-M + # Handles git branch + "^branch.* (?\S*)$" { + gitBranches $matches['branch'] | ConvertTo-VscodeCompletion -Type 'branch' + } + + # Handles git (commands & aliases) + "^(?\S*)$" { + gitCommands $matches['cmd'] $TRUE + } + + # Handles git help (commands only) + "^help (?\S*)$" { + gitCommands $matches['cmd'] $FALSE + } + + # Handles git push remote : + # Handles git push remote +: + "^push${ignoreGitParams}\s+(?[^\s-]\S*).*\s+(?\+?)(?[^\s\:]*\:)(?\S*)$" { + gitRemoteBranches $matches['remote'] $matches['ref'] $matches['branch'] -prefix $matches['force'] | ConvertTo-VscodeCompletion -Type 'branch' + } + + # Handles git push remote + # Handles git push remote + + # Handles git pull remote + "^(?:push|pull)${ignoreGitParams}\s+(?[^\s-]\S*).*\s+(?\+?)(?[^\s\:]*)$" { + gitBranches $matches['ref'] -prefix $matches['force'] | ConvertTo-VscodeCompletion -Type 'branch' + gitTags $matches['ref'] -prefix $matches['force'] | ConvertTo-VscodeCompletion -Type 'tag' + } + + # Handles git pull + # Handles git push + # Handles git fetch + "^(?:push|pull|fetch)${ignoreGitParams}\s+(?\S*)$" { + gitRemotes $matches['remote'] | ConvertTo-VscodeCompletion -Type 'remote' + } + + # Handles git reset HEAD + # Handles git reset HEAD -- + "^reset.* HEAD(?:\s+--)? (?\S*)$" { + gitIndex $GitStatus $matches['path'] + } + + # Handles git + "^commit.*-C\s+(?\S*)$" { + gitBranches $matches['ref'] $true | ConvertTo-VscodeCompletion -Type 'branch' + } + + # Handles git add + "^add.* (?\S*)$" { + gitAddFiles $GitStatus $matches['files'] + } + + # Handles git checkout -- + "^checkout.* -- (?\S*)$" { + gitCheckoutFiles $GitStatus $matches['files'] + } + + # Handles git restore -s / --source= - must come before the next regex case + "^restore.* (?-i)(-s\s*|(?--source=))(?\S*)$" { + gitBranches $matches['ref'] $true $matches['source'] + gitTags $matches['ref'] + break + } + + # Handles git restore + "^restore(?:.* (?(?:(?-i)-S|--staged))|.*) (?\S*)$" { + gitRestoreFiles $GitStatus $matches['files'] $matches['staged'] + } + + # Handles git rm + "^rm.* (?\S*)$" { + gitDeleted $GitStatus $matches['index'] + } + + # Handles git diff/difftool + "^(?:diff|difftool)(?:.* (?(?:--cached|--staged))|.*) (?\S*)$" { + gitDiffFiles $GitStatus $matches['files'] $matches['staged'] + } + + # Handles git merge/mergetool + "^(?:merge|mergetool).* (?\S*)$" { + gitMergeFiles $GitStatus $matches['files'] + } + + # Handles git checkout|switch + "^(?:checkout|switch).* (?\S*)$" { + if ($lastBlock -match "-b\s[^\s]*$") { + $null # Force zero results + } else { + [System.Management.Automation.CompletionResult]::new('.', '.', 'ParameterName', "Discard changes in working directory") + $lastCheckout = [System.Management.Automation.CompletionResult]::new('-', '-', 'ParameterName', "The last branch or commit that was checked out") + $lastCheckout | Add-Member -NotePropertyName 'CustomIcon' -NotePropertyValue 'gitBranch' + $lastCheckout + gitBranches $matches['ref'] $true | ConvertTo-VscodeCompletion -Type 'branch' + gitRemoteUniqueBranches $matches['ref'] | ConvertTo-VscodeCompletion -Type 'branch' + gitTags $matches['ref'] | ConvertTo-VscodeCompletion -Type 'tag' + } + } + + # Handles git worktree add + "^worktree add.* (?\S+) (?\S*)$" { + gitBranches $matches['ref'] | ConvertTo-VscodeCompletion -Type 'branch' + } + + # Handles git + "^(?:cherry|cherry-pick|diff|difftool|log|merge|rebase|reflog\s+show|reset|revert|show).* (?\S*)$" { + gitBranches $matches['ref'] $true | ConvertTo-VscodeCompletion -Type 'branch' + gitTags $matches['ref'] | ConvertTo-VscodeCompletion -Type 'tag' + } + + # Handles git --= + "^(?$gitCommandsWithParamValues).* --(?[^=]+)=(?\S*)$" { + expandParamValues $matches['cmd'] $matches['param'] $matches['value'] + } + + # Handles git -- + "^(?$gitCommandsWithLongParams).* --(?\S*)$" { + expandLongParams $longGitParams $matches['cmd'] $matches['param'] + } + + # Handles git - + "^(?$gitCommandsWithShortParams).* -(?\S*)$" { + expandShortParams $shortGitParams $matches['cmd'] $matches['shortparam'] + } + + # Handles git pr alias + "vsts\.pr\s+(?\S*)$" { + gitCmdOperations $subcommands 'vsts.pr' $matches['op'] + } + + # Handles git pr -- + "vsts\.pr\s+(?$vstsCommandsWithLongParams).*--(?\S*)$" + { + expandLongParams $longVstsParams $matches['cmd'] $matches['param'] + } + + # Handles git pr - + "vsts\.pr\s+(?$vstsCommandsWithShortParams).*-(?\S*)$" + { + expandShortParams $shortVstsParams $matches['cmd'] $matches['shortparam'] + } + } +} + +function ConvertTo-VscodeCompletion { + Param( + [Parameter(ValueFromPipeline=$true)] + $CompletionText, + [string] + $Type, + [string] + $CustomIcon + ) + + Process { + $completionMappings = @{ + "branch" = "gitBranch" + "stash" = "gitStash" + "remote" = "remote" + "tag" = "tag" + } + $CompletionText | ForEach-Object { + $result = [System.Management.Automation.CompletionResult]::new($_, $_, [System.Management.Automation.CompletionResultType]::DynamicKeyword, "$Type $_") + $result | Add-Member -NotePropertyName 'CustomIcon' -NotePropertyValue $completionMappings[$Type] + $result + } + } +} + +function WriteTabExpLog([string] $Message) { + if (!$global:GitTabSettings.EnableLogging) { return } + + $timestamp = Get-Date -Format HH:mm:ss + "[$timestamp] $Message" | Out-File -Append $global:GitTabSettings.LogPath +} + +# if ($PSVersionTable.PSVersion.Major -ge 6) { +$cmdNames = "git","tgit","gitk" + +# Create regex pattern from $cmdNames: ^(git|git\.exe|tgit|tgit\.exe|gitk|gitk\.exe)$ +$cmdNamesPattern = "^($($cmdNames -join '|'))(\.exe)?$" +$cmdNames += Get-Alias | Where-Object { $_.Definition -match $cmdNamesPattern } | Foreach-Object Name + +$global:GitTabSettings.RegisteredCommands = $cmdNames -join ", " + +Microsoft.PowerShell.Core\Register-ArgumentCompleter -CommandName $cmdNames -Native -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + # The PowerShell completion has a habit of stripping the trailing space when completing: + # git checkout + # The Expand-GitCommand expects this trailing space, so pad with a space if necessary. + $padLength = $cursorPosition - $commandAst.Extent.StartOffset + $textToComplete = $commandAst.ToString().PadRight($padLength, ' ').Substring(0, $padLength) + + WriteTabExpLog "Expand: command: '$($commandAst.Extent.Text)', padded: '$textToComplete', padlen: $padLength" + $result = Expand-GitCommand $textToComplete + if ($null -eq $result) { + ,@() + } else { + $result + } +} + + diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/cgmanifest.json b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/cgmanifest.json new file mode 100644 index 00000000..0d773545 --- /dev/null +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/cgmanifest.json @@ -0,0 +1,48 @@ +{ + "registrations": [ + { + "component": { + "type": "other", + "other": { + "name": "posh-git", + "downloadUrl": "https://github.com/dahlbyk/posh-git", + "version": "70e44dc0c2cdaf10c0cc8eb9ef5a9ca65ab63dcf" + } + }, + "licenseDetail": [ + "Oniguruma LICENSE", + "-----------------", + "", + "Copyright (c) 2002-2020 K.Kosako ", + "All rights reserved.", + "", + "The BSD License", + "", + "Redistribution and use in source and binary forms, with or without", + "modification, are permitted provided that the following conditions", + "are met:", + "1. Redistributions of source code must retain the above copyright", + " notice, this list of conditions and the following disclaimer.", + "2. Redistributions in binary form must reproduce the above copyright", + " notice, this list of conditions and the following disclaimer in the", + " documentation and/or other materials provided with the distribution.", + "", + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND", + "ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE", + "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE", + "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE", + "FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL", + "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS", + "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)", + "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT", + "LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY", + "OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF", + "SUCH DAMAGE." + ], + "isOnlyProductionDependency": true, + "license": "MIT", + "version": "70e44dc0c2cdaf10c0cc8eb9ef5a9ca65ab63dcf" + } + ], + "version": 1 +} diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish index a771735a..0377d700 100644 --- a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish @@ -24,7 +24,7 @@ set --global VSCODE_SHELL_INTEGRATION 1 # Apply any explicit path prefix (see #99878) if status --is-login; and set -q VSCODE_PATH_PREFIX - fish_add_path -p $VSCODE_PATH_PREFIX + set -gx PATH "$VSCODE_PATH_PREFIX$PATH" end set -e VSCODE_PATH_PREFIX diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css deleted file mode 100644 index 2a1f155f..00000000 --- a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css +++ /dev/null @@ -1,45 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .xterm-viewport { - /* Use the hack presented in https://stackoverflow.com/a/38748186/1156119 to get opacity transitions working on the scrollbar */ - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - transition: background-color 800ms linear; -} - -.monaco-workbench .xterm-viewport { - scrollbar-width: thin; -} - -.monaco-workbench .xterm-viewport::-webkit-scrollbar { - width: 10px; -} - -.monaco-workbench .xterm-viewport::-webkit-scrollbar-track { - opacity: 0; -} - -.monaco-workbench .xterm-viewport::-webkit-scrollbar-thumb { - min-height: 20px; - background-color: inherit; -} - -.monaco-workbench .force-scrollbar .xterm .xterm-viewport, -.monaco-workbench .xterm.focus .xterm-viewport, -.monaco-workbench .xterm:focus .xterm-viewport, -.monaco-workbench .xterm:hover .xterm-viewport { - transition: opacity 100ms linear; - cursor: default; -} - -.monaco-workbench .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { - transition: opacity 0ms linear; -} - -.monaco-workbench .xterm .xterm-viewport::-webkit-scrollbar-thumb:window-inactive { - background-color: inherit; -} diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index 1b6d1f62..fbeaa22f 100755 --- a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -34,7 +34,7 @@ if [ "$VSCODE_INJECTION" == "1" ]; then # Apply any explicit path prefix (see #99878) if [ -n "${VSCODE_PATH_PREFIX:-}" ]; then - export PATH=$VSCODE_PATH_PREFIX$PATH + export PATH="$VSCODE_PATH_PREFIX$PATH" builtin unset VSCODE_PATH_PREFIX fi fi @@ -112,15 +112,15 @@ __vsc_escape_value() { fi # Process text byte by byte, not by codepoint. - local -r LC_ALL=C - local -r str="${1}" - local -ir len="${#str}" + builtin local -r LC_ALL=C + builtin local -r str="${1}" + builtin local -ir len="${#str}" - local -i i - local -i val - local byte - local token - local out='' + builtin local -i i + builtin local -i val + builtin local byte + builtin local token + builtin local out='' for (( i=0; i < "${#str}"; ++i )); do # Escape backslashes, semi-colons specially, then special ASCII chars below space (0x20). @@ -143,7 +143,8 @@ __vsc_escape_value() { } # Send the IsWindows property if the environment looks like Windows -if [[ "$(uname -s)" =~ ^CYGWIN*|MINGW*|MSYS* ]]; then +__vsc_regex_environment="^CYGWIN*|MINGW*|MSYS*" +if [[ "$(uname -s)" =~ $__vsc_regex_environment ]]; then builtin printf '\e]633;P;IsWindows=True\a' __vsc_is_windows=1 else @@ -152,12 +153,16 @@ fi # Allow verifying $BASH_COMMAND doesn't have aliases resolved via history when the right HISTCONTROL # configuration is used -if [[ "$HISTCONTROL" =~ .*(erasedups|ignoreboth|ignoredups).* ]]; then +__vsc_regex_histcontrol=".*(erasedups|ignoreboth|ignoredups).*" +if [[ "$HISTCONTROL" =~ $__vsc_regex_histcontrol ]]; then __vsc_history_verify=0 else __vsc_history_verify=1 fi +builtin unset __vsc_regex_environment +builtin unset __vsc_regex_histcontrol + __vsc_initialized=0 __vsc_original_PS1="$PS1" __vsc_original_PS2="$PS2" @@ -170,8 +175,14 @@ __vsc_current_command="" __vsc_nonce="$VSCODE_NONCE" unset VSCODE_NONCE +# Some features should only work in Insiders +__vsc_stable="$VSCODE_STABLE" +unset VSCODE_STABLE + # Report continuation prompt -builtin printf "\e]633;P;ContinuationPrompt=$(echo "$PS2" | sed 's/\x1b/\\\\x1b/g')\a" +if [ "$__vsc_stable" = "0" ]; then + builtin printf "\e]633;P;ContinuationPrompt=$(echo "$PS2" | sed 's/\x1b/\\\\x1b/g')\a" +fi __vsc_report_prompt() { # Expand the original PS1 similarly to how bash would normally @@ -204,7 +215,7 @@ __vsc_update_cwd() { } __vsc_command_output_start() { - if [[ -z "$__vsc_first_prompt" ]]; then + if [[ -z "${__vsc_first_prompt-}" ]]; then builtin return fi builtin printf '\e]633;E;%s;%s\a' "$(__vsc_escape_value "${__vsc_current_command}")" $__vsc_nonce @@ -220,7 +231,7 @@ __vsc_continuation_end() { } __vsc_command_complete() { - if [[ -z "$__vsc_first_prompt" ]]; then + if [[ -z "${__vsc_first_prompt-}" ]]; then builtin return fi if [ "$__vsc_current_command" = "" ]; then @@ -252,7 +263,10 @@ __vsc_update_prompt() { __vsc_precmd() { __vsc_command_complete "$__vsc_status" __vsc_current_command="" - __vsc_report_prompt + # Report prompt is a work in progress, currently encoding is too slow + if [ "$__vsc_stable" = "0" ]; then + __vsc_report_prompt + fi __vsc_first_prompt=1 __vsc_update_prompt } @@ -314,10 +328,10 @@ __vsc_restore_exit_code() { __vsc_prompt_cmd_original() { __vsc_status="$?" + builtin local cmd __vsc_restore_exit_code "${__vsc_status}" # Evaluate the original PROMPT_COMMAND similarly to how bash would normally # See https://unix.stackexchange.com/a/672843 for technique - local cmd for cmd in "${__vsc_original_prompt_command[@]}"; do eval "${cmd:-}" done diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh index 5a45b076..c25ded3d 100644 --- a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh @@ -2,15 +2,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # --------------------------------------------------------------------------------------------- -if [[ $options[norcs] = off && -o "login" && -f $USER_ZDOTDIR/.zprofile ]]; then - VSCODE_ZDOTDIR=$ZDOTDIR - ZDOTDIR=$USER_ZDOTDIR - . $USER_ZDOTDIR/.zprofile - ZDOTDIR=$VSCODE_ZDOTDIR +if [[ $options[norcs] = off && -o "login" ]]; then + if [[ -f $USER_ZDOTDIR/.zprofile ]]; then + VSCODE_ZDOTDIR=$ZDOTDIR + ZDOTDIR=$USER_ZDOTDIR + . $USER_ZDOTDIR/.zprofile + ZDOTDIR=$VSCODE_ZDOTDIR + fi # Apply any explicit path prefix (see #99878) if (( ${+VSCODE_PATH_PREFIX} )); then - export PATH=$VSCODE_PATH_PREFIX$PATH + export PATH="$VSCODE_PATH_PREFIX$PATH" fi builtin unset VSCODE_PATH_PREFIX fi diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh index c555d1ac..54a13d57 100644 --- a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh @@ -159,7 +159,7 @@ __vsc_update_prompt() { } __vsc_precmd() { - local __vsc_status="$?" + builtin local __vsc_status="$?" if [ -z "${__vsc_in_command_execution-}" ]; then # not in command execution __vsc_command_output_start diff --git a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 index c2971c0d..444df384 100644 --- a/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 +++ b/patched-vscode/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 @@ -21,6 +21,9 @@ $Global:__LastHistoryId = -1 $Nonce = $env:VSCODE_NONCE $env:VSCODE_NONCE = $null +$isStable = $env:VSCODE_STABLE +$env:VSCODE_STABLE = $null + $osVersion = [System.Environment]::OSVersion.Version $isWindows10 = $IsWindows -and $osVersion.Major -eq 10 -and $osVersion.Minor -eq 0 -and $osVersion.Build -lt 22000 @@ -52,7 +55,7 @@ if ($env:VSCODE_ENV_APPEND) { function Global:__VSCode-Escape-Value([string]$value) { # NOTE: In PowerShell v6.1+, this can be written `$value -replace '…', { … }` instead of `[regex]::Replace`. # Replace any non-alphanumeric characters. - [regex]::Replace($value, "[$([char]0x1b)\\\n;]", { param($match) + [regex]::Replace($value, "[$([char]0x00)-$([char]0x1f)\\\n;]", { param($match) # Encode the (ascii) matches as `\x` -Join ( [System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ } @@ -95,7 +98,9 @@ function Global:Prompt() { # Prompt # OSC 633 ; = ST - $Result += "$([char]0x1b)]633;P;Prompt=$(__VSCode-Escape-Value $OriginalPrompt)`a" + if ($isStable -eq "0") { + $Result += "$([char]0x1b)]633;P;Prompt=$(__VSCode-Escape-Value $OriginalPrompt)`a" + } # Write command started $Result += "$([char]0x1b)]633;B`a" @@ -142,9 +147,11 @@ else { } # Set ContinuationPrompt property -$ContinuationPrompt = (Get-PSReadLineOption).ContinuationPrompt -if ($ContinuationPrompt) { - [Console]::Write("$([char]0x1b)]633;P;ContinuationPrompt=$(__VSCode-Escape-Value $ContinuationPrompt)`a") +if ($isStable -eq "0") { + $ContinuationPrompt = (Get-PSReadLineOption).ContinuationPrompt + if ($ContinuationPrompt) { + [Console]::Write("$([char]0x1b)]633;P;ContinuationPrompt=$(__VSCode-Escape-Value $ContinuationPrompt)`a") + } } # Set always on key handlers which map to default VS Code keybindings @@ -163,14 +170,22 @@ function Set-MappedKeyHandler { } } +function Get-KeywordCompletionResult( + $Keyword, + $Description = $Keyword +) { + [System.Management.Automation.CompletionResult]::new($Keyword, $Keyword, [System.Management.Automation.CompletionResultType]::Keyword, $Description) +} + function Set-MappedKeyHandlers { Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a' Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b' Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c' Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d' - # Conditionally enable suggestions - if ($env:VSCODE_SUGGEST -eq '1') { + # Enable suggestions if the environment variable is set and Windows PowerShell is not being used + # as APIs are not available to support this feature + if ($env:VSCODE_SUGGEST -eq '1' -and $PSVersionTable.PSVersion -ge "7.0") { Remove-Item Env:VSCODE_SUGGEST # VS Code send completions request (may override Ctrl+Spacebar) @@ -178,20 +193,140 @@ function Set-MappedKeyHandlers { Send-Completions } - # TODO: When does this invalidate? Installing a new module could add new commands. We could expose a command to update? Track `(Get-Module).Count`? - # Commands are expensive to complete and send over, do this once for the empty string so we - # don't need to do it each time the user requests. Additionally we also want to do filtering - # and ranking on the client side with the full list of results. - $result = "$([char]0x1b)]633;CompletionsPwshCommands;commands;" - $result += [System.Management.Automation.CompletionCompleters]::CompleteCommand('') | ConvertTo-Json -Compress - $result += "`a" - Write-Host -NoNewLine $result + # VS Code send global completions request + Set-PSReadLineKeyHandler -Chord 'F12,f' -ScriptBlock { + # Get commands, convert to string array to reduce the payload size and send as JSON + $commands = @( + [System.Management.Automation.CompletionCompleters]::CompleteCommand('') + Get-KeywordCompletionResult -Keyword 'begin' + Get-KeywordCompletionResult -Keyword 'break' + Get-KeywordCompletionResult -Keyword 'catch' -Description "catch [[][',' ]*] {}" + Get-KeywordCompletionResult -Keyword 'class' -Description @" +class [: [][,]] { + [[] [hidden] [static] ...] + [([]) + {} ...] + [[] [hidden] [static] ...] +} +"@ + Get-KeywordCompletionResult -Keyword 'clean' + Get-KeywordCompletionResult -Keyword 'continue' + Get-KeywordCompletionResult -Keyword 'data' -Description @" +data [] [-supportedCommand ] { + +} +"@ + Get-KeywordCompletionResult -Keyword 'do' -Description @" +do {} while () +do {} until () +"@ + Get-KeywordCompletionResult -Keyword 'dynamicparam' -Description "dynamicparam {}" + Get-KeywordCompletionResult -Keyword 'else' -Description @" +if () + {} +[elseif () + {}] +[else + {}] +"@ + Get-KeywordCompletionResult -Keyword 'elseif' -Description @" +if () + {} +[elseif () + {}] +[else + {}] +"@ + Get-KeywordCompletionResult -Keyword 'end' + Get-KeywordCompletionResult -Keyword 'enum' -Description @" +[[]...] [Flag()] enum [ : ] { +